GS2-Enhance

強化機能

この機能は開発者によっては全く理解できない機能でしょう。

しかし、日本をはじめとしたモバイルゲームのデファクトスタンダードのゲームシステムには必ず存在する機能です。 このゲームメカニクス自体は Game as a Service を実現する上で有用な知識になるはずですので、理解できない開発者向けにゲームメカニズムの解説を加えます。

強化機能のゲームメカニクスについて十分な知識がある場合はこの先の解説セクションは読み飛ばして問題ありません。

強化機能のゲームメカニクス

強化機能自体は非常にシンプルなゲームシステムで、素材アイテムを消費することで、対象の経験値を加算できる仕組みです。 バトルに参加して経験値を入手するという方法だけでなく、様々な方法でキャラクターや装備の育成ができるようになっています。 (装備にもレベルがあります)

さて、このゲームシステムがなぜ Game as a Service を実現するのに一役買っているかを説明しましょう。

一言で言えば、プレイ時間の水増しで活躍しています。

開発速度よりゲームのプレイ速度のほうが圧倒的に早いのは、みなさん十分理解しているでしょう。 私たちゲーム開発者が3年間かけて開発したゲームを、プレイヤーは10時間で終えてしまうわけです。 しかし、この差を埋めないと Game as a Service は成り立ちません。 つまり、プレイヤーのコンテンツ消費速度にデバフをかける必要があります。その魔法が強化のゲームシステムです。

一般的な Game as a Service のゲームは毎月1回はイベントを実施しています。 そのイベントには、なんらかのボスが存在し、プレイヤーはそのボスをイベント期間繰り返し討伐し続けることになります。 ボスはプレイヤーの成長段階に合わせて複数の難易度を用意し、討伐することでキャラクターや装備の強化素材を手に入れることができます。

集めた強化素材を使用して、キャラクターや装備の経験値に変換し成長させます。 キャラクターや装備を成長させると、より高難易度のボスにチャレンジできるようになります。

こうすることで、プレイヤー全員がイベントに参加しつつ、自分のキャラクターの成長段階にあわせた強化素材を入手し、キャラクターや装備を成長させる遊びを楽しむことができます。

実際には、強化素材を直接ボスがドロップすることは少ないかもしれません。 代わりにイベント期間だけチャレンジできるガチャがあり、そのガチャを引くためのポイントを集めて、ガチャを経由して素材を手に入れたり イベント期間だけ開店するショップで、ボスを討伐することで得られるゲーム内通貨を集めて、ショップから強化素材を購入するなど プレイヤーの判断でどこを優先して強化するかを決定できるようにするような工夫はありますが、最終的にはプレイヤーがキャラクターの育成に必要な時間を引き伸ばしてプレイ時間を水増ししています。

イベントを通して一貫して変わらないのは、プレイヤーのキャラクターが強くなるということです。 イベントが終わると強くなったキャラクターを使用して、より高難度の恒常コンテンツを楽しむなど、次の楽しみに繋げています。

graph TD
  BossBattle["Boss Battle"] -- Acquire Event Point --> Shop
  Shop -- Buy Enhance Materials --> Enhance["Enhance Character"]
  Enhance -- More Formidable --> BossBattle

アーキテクチャ

強化には素材となるアイテムを管理している GS2-Inventory と、強化対象のキャラクターや装備を管理している GS2-Inventory または GS2-Dictionary。 そして、そのキャラクターや装備の経験値・レベルを管理している GS2-Experience を GS2-Enhance を通して操作することで実現しています。

現在の GS2 では、1回の API リクエストで消費と経験値加算を完結させる DirectEnhance による強化が推奨されています。 以前はトランザクションの自動実行機能がなかったため、大成功が出るまで通信を遮断してやり直すような行為を防ぐ目的で、準備・実行・完了報告のステップに分かれた強化プロセス(Progress)を推奨していましたが、現在はトランザクションの自動実行やアトミックコミットの仕組みが整ったため、その懸念は解消されています。

actor Player
participant "GS2-Enhance#Namespace"
participant "GS2-Inventory#ItemSet(Material)"
participant "GS2-Experience#Status"
Player -> "GS2-Enhance#Namespace" : Direct Enhance(Materials/Target Character)
"GS2-Enhance#Namespace" -> "GS2-Inventory#ItemSet(Material)" : Get experience value from metadata
"GS2-Enhance#Namespace" -> "GS2-Inventory#ItemSet(Material)" : Consume
"GS2-Enhance#Namespace" -> "GS2-Experience#Status": Add experience(Key: Target Character/Equipment Id)
"GS2-Enhance#Namespace" -> Player : Enhance result

GS2-Enhance に「強化対象」「強化に使用する素材」をパラメータとして強化実行API(DirectEnhance)を呼び出します。

すると、GS2-Enhance は素材となるアイテムのマスターデータを GS2-Inventory から取得し、メタデータ内に記録してある素材として使用した際の経験値量を取得します。 経験値量が確定したら、アイテムを消費して経験値の加算処理を GS2-Experience で実行します。

強化対象を指定していますが、GS2-Experience の経験値を管理するキーに利用するだけで、強化対象の情報を直接使用することはありません。

DirectEnhance と Progress の違い

GS2-Enhance では2種類の強化フローを提供しています。

項目 DirectEnhance Progress (Start / End)
API 呼び出し回数 1回 2回(開始・終了)
推奨用途 全ての強化処理 大成功などの抽選結果を演出に反映するため、抽選結果を先に取得しておきたい場合
アトミック性 リクエスト内で消費・経験値加算が完結 サーバーで抽選した結果を保持し、後続の End で確定
通信遮断による不正利用 発生しない 進行中の Progress は同時に1件しか保持できないため、DeleteProgress 経由でしかリトライ不可

特別な要件がない限り、 DirectEnhance の利用を推奨します。

sequenceDiagram
  participant Player
  participant Enhance as GS2-Enhance
  participant Inventory as GS2-Inventory
  participant Experience as GS2-Experience
  Player ->> Enhance: DirectEnhance(rateName, targetItemSetId, materials)
  Enhance ->> Inventory: メタデータから経験値量取得
  Enhance ->> Inventory: 素材アイテム消費
  Enhance ->> Experience: 経験値加算
  Enhance -->> Player: 加算経験値・大成功倍率

強化レート

強化に使用できる素材や、強化対象を限定するために強化レートをマスターデータとして設定する必要があります。

マスターデータには素材にできるアイテムの GS2-Inventory のネームスペース名や、インベントリ名、 強化対象にできるアイテムの GS2-Inventory のネームスペース名や、インベントリ名 といった情報を記録しています。

マスターデータ及びは JSON 形式で管理します。

ここではItemModelのメタデータに { “experience”: 50 } のようなJSON形式で設定する例を示します。

RateModel の acquireExperienceHierarchy で JSONのキー(上記ではexperience)を定義します。 また、acquireExperienceHierarchyには階層の構造を定義できます。 例えば、{ “aaa”: { “bbb”: { “experienceValue”: 100 } } } というような構造のデータ定義でメタデータに設定したい場合は、acquireExperienceHierarchyには、[ “aaa”, “bbb”, “experienceValue” ] のように指定します。

RateModel の主な設定項目

項目 説明
name 強化レート名
targetInventoryModelId 強化対象アイテムが格納されている GS2-Inventory のインベントリモデル ID
materialInventoryModelId 強化素材アイテムが格納されている GS2-Inventory のインベントリモデル ID
acquireExperienceHierarchy 素材アイテムのメタデータから経験値量を取り出すための JSON パス
acquireExperienceSuffix 経験値モデル名のキーに付与するサフィックス(例: :level
experienceModelId 経験値を加算する GS2-Experience の経験値モデル ID
bonusRates 大成功時の倍率と抽選重み

GS2-Enhance RateModel マスターデータ メタデータに経験値を設定する例:


{
  "version": "2020-08-22",
  "rateModels": [
    {
      "name": "enhanceRate",
      "description": "",
      "metadata": "",
      "targetInventoryModelId": "grn:gs2:ap-northeast-1:YourOwnerId:inventory:enhance-inventory:model:character",
      "acquireExperienceSuffix": ":level",
      "materialInventoryModelId": "grn:gs2:ap-northeast-1:YourOwnerId:inventory:enhance-inventory:model:material",
      "acquireExperienceHierarchy": [
        "experience"
      ],
      "experienceModelId": "grn:gs2:ap-northeast-1:YourOwnerId:experience:enhance-experience:model:character",
      "bonusRates": [
        {
          "rate": 2,
          "weight": 1
        },
        {
          "rate": 1,
          "weight": 1
        }
      ]
    }
  ]
}

bonusRates には複数のエントリーを設定でき、各エントリーは抽選重み(weight)と倍率(rate)を持ちます。 上記の例では「2倍経験値」と「等倍経験値」が同じ重みで抽選され、おおよそ50%の確率で大成功(2倍)が発生します。

GS2-Experience ExperienceModel マスターデータ例:

{
  "version": "2019-01-11",
  "experienceModels": [
    {
      "name": "character",
      "metadata": "CHARACTER",
      "defaultExperience": 0,
      "defaultRankCap": 50,
      "maxRankCap": 80,
      "rankThreshold": {
        "metadata": "RANK_THRESHOLD",
        "values": [
          100,
          300,
          500,
          1000
        ]
      }
    }
  ]
}

GS2-Inventory ItemModel マスターデータ メタデータに経験値を設定する例:

{
  "version": "2019-02-05",
  "inventoryModels": [
    {
      "name": "character",
      "initialCapacity": 1,
      "maxCapacity": 1,
      "protectReferencedItem": false,
      "itemModels": [
        {
          "name": "character-0001",
          "stackingLimit": 1,
          "allowMultipleStacks": false,
          "sortValue": 0
        }
      ]
    },
    {
      "name": "material",
      "metadata": "",
      "initialCapacity": 10,
      "maxCapacity": 10,
      "protectReferencedItem": false,
      "itemModels": [
        {
          "name": "material-0001",
          "metadata": "{\"experience\":50}",
          "stackingLimit": 99,
          "allowMultipleStacks": false,
          "sortValue": 0
        }
      ]
    }
  ]
}

限界突破 (Unleash)

同種のアイテムなどを素材として消費し、対象のレベル上限を引き上げる「限界突破」機能も提供しています。 GS2-Experience が管理する rankCap(ランク上限)を一段階引き上げる仕組みで、UnleashRateModel のマスターデータで以下を設定します。

  • 限界突破対象のインベントリモデル
  • 紐づくグレードモデル(GS2-Grade)
  • 各グレードに到達するために必要な素材アイテムとその数量

これにより「同じキャラクターを4体集めると限界突破できる」「特定の素材アイテムを N 個消費するとレベル上限解放」といったゲームメカニクスを実装できます。

スクリプトトリガー

ネームスペースに enhanceScript を設定すると強化実行の前後でカスタムスクリプトを呼び出せます。処理の許可・否認や入手経験値の上書きが可能で、実行方式は同期・非同期を選択できます。非同期では GS2-Script や Amazon EventBridge を利用した外部連携にも対応します。

設定できる主なイベントトリガーとスクリプト設定名は以下の通りです。

  • enhanceScript(完了通知: enhanceDone): 強化実行の前後

スクリプト内では強化対象・素材アイテム・抽選される倍率を参照でき、ゲームバランス調整やイベント期間中のブースト倍率変更といった用途で活用できます。

マスターデータ運用

マスターデータを登録することでマイクロサービスで利用可能なデータや振る舞いを設定できます。

マスターデータの種類には以下があります。

  • RateModel: 強化素材や対象の定義
  • UnleashRateModel: 上限解放用のレート定義

マスターデータの登録はマネージメントコンソールから登録する他、GitHubからデータを反映したり、GS2-Deployを使ってCIから登録するようなワークフローを組むことが可能です。

トランザクションアクション

GS2-Enhance では以下のトランザクションアクションを提供しています。

  • 消費アクション: 実行中の強化進行情報(Progress)の削除
  • 入手アクション: 即時強化(DirectEnhance)の実行、限界突破(Unleash)の実行、強化(Progress)の開始

「即時強化(DirectEnhance)の実行」を入手アクションとして利用することで、特定のクエストクリア報酬やショップでのアイテム購入時の報酬として、直接キャラクターや装備に経験値を付与するといった処理が可能になります。また、「限界突破(Unleash)」を報酬として設定することで、特定のミッション達成時に自動的にレベル上限を引き上げるといった運用も可能です。

実装例

強化レート一覧の取得

    var items = await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).RateModelsAsync(
    ).ToListAsync();
    const auto It = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->RateModels();
    TArray<Gs2::UE5::Enhance::Model::FEzRateModelPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

強化の実行(DirectEnhance)

materials には消費する素材アイテムの ItemSetId と数量を指定します。 強化対象として targetItemSetId に強化したいキャラクター・装備の ItemSetId を渡します。

    var result = await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Enhance(
    ).EnhanceAsync(
        rateName: "rate-0001",
        targetItemSetId: "item-set-0001",
        materials: new [] {
            new Gs2.Unity.Gs2Enhance.Model.EzMaterial
            {
                MaterialItemSetId = "material-0001",
                Count = 1,
            },
        }
    );

    var transaction = result;
    await transaction.WaitAsync();
    const auto Domain = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Enhance(
    );
    const auto Future = Domain->Enhance(
        "rate-0001",
        "item-set-0001",
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::Enhance::Model::FMaterial>>>();
            v->Add(MakeShared<Gs2::Enhance::Model::FMaterial>()
                ->WithMaterialItemSetId(TOptional<FString>("material-0001"))
                ->WithCount(TOptional<int32>(1)));
            return v;
        }()
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    const auto Transaction = Future->GetTask().Result();
    const auto Future2 = Transaction->Wait();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;

強化(Progress)の開始

抽選結果を先に取得しておきたい場合のフローです。 進行中の Progress は1ユーザーあたり1件のみ保持できます。

    var result = await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Progress(
    ).StartAsync(
        rateName: "rate-0001",
        targetItemSetId: "item-set-0001",
        materials: new [] {
            new Gs2.Unity.Gs2Enhance.Model.EzMaterial
            {
                MaterialItemSetId = "material-0001",
                Count = 1,
            },
        }
    );
    const auto Future = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Progress(
    )->Start(
        "rate-0001", // rateName
        "item-set-0001", // targetItemSetId
        []
        {
            const auto v = MakeShared<TArray<TSharedPtr<Gs2::Enhance::Model::FMaterial>>>();
            v->Add(MakeShared<Gs2::Enhance::Model::FMaterial>()
                ->WithMaterialItemSetId(TOptional<FString>("material-0001"))
                ->WithCount(TOptional<int32>(1)));
            return v;
        }()
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

進行中の強化情報を取得

抽選済みの倍率(大成功フラグ)や入手経験値量を取得し、強化演出に活用できます。

    var item = await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Progress(
    ).ModelAsync();

    var experienceValue = item.ExperienceValue;
    var rate = item.Rate;
    const auto Domain = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Progress(
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    const auto Result = Future->GetTask().Result();
    const auto ExperienceValue = Result->GetExperienceValue();
    const auto Rate = Result->GetRate();

強化(Progress)の完了

Start で開始した強化を End で確定し、消費・経験値加算を実行します。

    var result = await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Progress(
    ).EndAsync();

    await result.WaitAsync();
    const auto Future = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Progress(
    )->End();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

    const auto Transaction = Future->GetTask().Result();
    const auto Future2 = Transaction->Wait();
    Future2->StartSynchronousTask();
    if (Future2->GetTask().IsError()) return false;

進行中の強化情報を破棄

通信が途切れて完了報告が行えなくなった場合などのリカバリーに使用します。

    await gs2.Enhance.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Progress(
    ).DeleteProgressAsync();
    const auto Future = Gs2->Enhance->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Progress(
    )->DeleteProgress();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

その他の機能

強化の大成功

強化の仕様として、一定確率で「大成功」が発生し、入手できる経験値量が 1.5倍 や 2倍になる仕様があることがあります。 GS2-Enhance では強化レートとして、大成功が発生する確率とその場合の経験値の獲得量の倍率を設定できます。

bonusRatesweight の合計値に対する各エントリーの weight の割合が、その倍率の発生確率となります。

Config を使用したスクリプトへのパラメータ受け渡し

EnhanceAsync / StartAsync には config パラメータを指定でき、スクリプトトリガー実行時に任意のキーと値のペアを渡せます。 ゲーム内のイベントブースト状態や、プレイヤーが選択した強化モードといった情報をスクリプトに伝えることができます。

投機的実行(Speculative Execute)

speculativeExecute 引数(デフォルト true)を有効にすると、API リクエストの応答を待つ前にクライアント側のキャッシュを更新し、UI 上では即座に経験値加算後の状態を表示できます。 通信遅延の影響を受けにくいテンポの良い操作感を実現できます。

詳細なリファレンス