GS2-Showcase

商品販売機能

ゲーム内で商品を販売する際に使用します。

GS2-Exchange との違いは陳列棚が存在することです。 陳列棚には DisplayItem を陳列することができ、DisplayItem を購入するために必要な対価と、商品を購入した際に得られる報酬を設定できます。

陳列棚には2種類存在し、固定の DisplayItem が陳列される《スタンダード陳列棚》と、一定間隔で陳列内容がランダムに抽選される《ランダム陳列棚》が存在します。

graph TD
  Master["マスターデータ"] --> Showcase["スタンダード陳列棚<br/>(ShowcaseModel)"]
  Master --> RandomShowcase["ランダム陳列棚<br/>(RandomShowcaseModel)"]
  Showcase --> DisplayItem["DisplayItem<br/>(SalesItem / SalesItemGroup)"]
  RandomShowcase --> RandomDisplayItem["RandomDisplayItem<br/>(stock / weight)"]
  Player["プレイヤー"] -- Buy --> DisplayItem
  Player -- RandomShowcaseBuy --> RandomDisplayItem
  DisplayItem -- "consumeActions / acquireActions" --> Transaction["GS2-Distributor<br/>トランザクション実行"]
  RandomDisplayItem -- "consumeActions / acquireActions" --> Transaction

スタンダード陳列棚

スタンダード陳列棚は指定した DisplayItem が全て陳列されます。 DisplayItem には2種類存在し《SalesItem》と《SalesItemGroup》が存在します。

SalesItem

SalesItem には購入するするために必要な対価と、購入した際に得られる報酬を設定できます。

SalesItem は次の3種類のアクションで挙動を表現します。

項目 説明
verifyActions 購入条件の検証アクション。指定したマイクロサービスの状態を検証し、条件を満たさない場合は購入を拒否します。
consumeActions 購入時に消費するアクション。GS2-Money2 の通貨や GS2-Inventory のアイテムなどを対価として消費します。
acquireActions 購入時に入手するアクション。GS2-Inventory・GS2-Experience・GS2-Lottery など、任意のマイクロサービスへ報酬として配布します。

SalesItemGroup

SalesItemGroup は購入回数に応じて販売する SalesItem が変化する仕組みを実現します。

SalesItemGroup には複数の SalesItem を属させることができ、リストの最後の商品以外には GS2-Limit のカウンター上昇を対価に設定する必要があります。 SalesItemGroup を陳列棚に陳列する際には、内部の SalesItem が購入可能かを判定し、一番最初に購入可能と判定された商品が陳列されます。

この機能を利用することで、初回購入のみ半額で商品を販売したり、購入するごとに価格が高くなっていく商品を実現したり、10回目の購入にはおまけをつける。というような商品を実現できます。

graph LR
  Buy["購入要求"] --> Check1{"SalesItem 1<br/>(初回限定)"}
  Check1 -- 購入可能 --> Sell1["SalesItem 1 を販売"]
  Check1 -- 購入済み --> Check2{"SalesItem 2<br/>(2〜9回目)"}
  Check2 -- 購入可能 --> Sell2["SalesItem 2 を販売"]
  Check2 -- 上限到達 --> Check3{"SalesItem 3<br/>(10回目特典)"}
  Check3 -- 購入可能 --> Sell3["SalesItem 3 を販売"]

ランダム陳列棚

ランダム陳列棚は、マスターデータに指定した DisplayItem のうち指定した数がランダムに抽選されて陳列されます。

ランダム陳列棚は以下のパラメータで動作を制御します。

項目 説明
maximumNumberOfChoice 一度の抽選で陳列される DisplayItem の最大数
displayItems 抽選対象の RandomDisplayItem 一覧。各アイテムは weight(抽選重み)と stock(在庫数)を持ちます
baseTimestamp / resetIntervalHours 陳列内容の再抽選間隔。指定時刻を起点に一定時間ごとに陳列内容がリフレッシュされます
salesPeriodEventId GS2-Schedule のイベントID。販売可能期間を制限したい場合に指定します

抽選結果はプレイヤーごとに RandomShowcaseStatus として保存され、購入時には在庫が消費されます。再抽選間隔を迎えると次回アクセス時に陳列内容が更新されます。

バフによる補正

GS2-Buff と連携すると DisplayItem および RandomDisplayItemModel の acquireActionsverifyActionsconsumeActions を動的に補正でき、ランダム陳列商品では stock の上書きも可能です。イベントやキャンペーンに合わせて報酬や必要対価、在庫数を柔軟に変更できます。

スクリプトトリガー

ネームスペースに buyScript を設定すると、商品購入のタイミングでカスタムスクリプトを呼び出せます。

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

  • buyScript: 商品購入時

これらを活用することで、購入処理に独自のバリデーションや監査ログ出力、KPI集計などを差し込むことが可能です。

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

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

  • 消費アクション: 購入回数の加算
  • 入手アクション: 購入回数の減算、ランダム陳列棚の再抽選

「ランダム陳列棚の再抽選」を入手アクションとして利用することで、特定のアイテムを入手した際やクエストクリア時の報酬として、ショップの陳列内容を強制的に更新させるといった処理を、トランザクション内で安全に実行できます。これにより、プレイヤーの進捗に合わせたタイムリーな商品ラインナップの提供が可能になります。また、「購入回数の減算」を利用することで、商品の購入制限(限定商品の再販など)を個別に回復させるといった運用も容易になります。

マスターデータ運用

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

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

  • ShowcaseModel: スタンダード陳列棚の定義。DisplayItem として SalesItem または SalesItemGroup を陳列できます。
  • RandomShowcaseModel: ランダム陳列棚の定義。RandomDisplayItemModel のリストから maximumNumberOfChoice 件を抽選して陳列します。
  • SalesItem / SalesItemMaster: 単一の販売商品の定義。
  • SalesItemGroup / SalesItemGroupMaster: 購入状況によって陳列内容が変化する商品グループの定義。

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

以下はランダム陳列棚を含むマスターデータの JSON 例です。

{
  "version": "2019-09-13",
  "showcases": [
    {
      "name": "showcase-0001",
      "metadata": "通常ショップ",
      "salesPeriodEventId": null,
      "displayItems": [
        {
          "displayItemId": "display-item-0001",
          "type": "salesItem",
          "salesItemName": "item-0001"
        }
      ]
    }
  ],
  "randomShowcases": [
    {
      "name": "random-showcase-0001",
      "metadata": "日替わりショップ",
      "maximumNumberOfChoice": 4,
      "baseTimestamp": 1700000000000,
      "resetIntervalHours": 24,
      "displayItems": [
        {
          "name": "display-item-0001",
          "weight": 10,
          "stock": 3,
          "consumeActions": [],
          "acquireActions": []
        }
      ]
    }
  ]
}

実装例

スタンダード陳列棚

陳列棚を取得

    var item = await gs2.Showcase.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Showcase(
        showcaseName: "showcase-0001"
    ).ModelAsync();
    const auto Domain = Gs2->Showcase->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Showcase(
        "showcase-0001" // showcaseName
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Result = Future->GetTask().Result();

陳列棚一覧を取得

陳列棚一覧を取得する API はゲームエンジン用 SDK には提供されていません。 取得が必要な場合は、マネジメントコンソール / GS2 CLI / 各種言語向け一般 SDK (C# / Go / Python / TypeScript / PHP / Java) をご利用ください。

商品を購入

    var result = await gs2.Showcase.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Showcase(
        showcaseName: "showcase-0001"
    ).DisplayItem(
        displayItemId: "display-item-0001"
    ).BuyAsync(
        quantity: 1,
        config: null
    );

    await result.WaitAsync();
    const auto Domain = Gs2->Showcase->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Showcase(
        "showcase-0001" // showcaseName
    )->DisplayItem(
        "display-item-0001" // displayItemId
    );
    const auto Future = Domain->Buy(
        1, // quantity
        nullptr // config
    );
    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;

Buy は GS2-Distributor を介したトランザクション処理として実行されます。トランザクション結果を反映するには、戻り値である EzTransactionDomainWaitAsync / Wait を呼び出し、トランザクションの完了を待機してください。

ランダム陳列棚

陳列棚を取得

    var items = await gs2.Showcase.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).RandomShowcase(
        showcaseName: "showcase-0001"
    ).RandomDisplayItemsAsync(
    ).ToListAsync();
    const auto It = Gs2->Showcase->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->RandomShowcase(
        "showcase-0001" // showcaseName
    )->RandomDisplayItems(
    );
    TArray<Gs2::UE5::Showcase::Model::FEzRandomDisplayItemPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

商品を購入

    var result = await gs2.Showcase.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).RandomShowcase(
        showcaseName: "showcase-0001"
    ).RandomDisplayItem(
        displayItemName: "display-item-0001"
    ).RandomShowcaseBuyAsync(
        quantity: 1,
        config: null
    );

    await result.WaitAsync();
    const auto Future = Gs2->Showcase->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->RandomShowcase(
        "showcase-0001" // showcaseName
    )->RandomDisplayItem(
        "display-item-0001" // displayItemName
    )->RandomShowcaseBuy(
        1, // quantity
        nullptr // config
    );
    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;

ランダム陳列棚の状態を取得

ランダム陳列棚はプレイヤーごとに抽選結果と購入回数を RandomShowcaseStatus として保持しています。 次回の再抽選時刻や購入済み数を確認したい場合に取得します。

    var item = await gs2.Showcase.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).RandomShowcaseStatus(
        showcaseName: "showcase-0001"
    ).ModelAsync();
    const auto Domain = Gs2->Showcase->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->RandomShowcaseStatus(
        "showcase-0001" // showcaseName
    );
    const auto Future = Domain->Model();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;
    const auto Result = Future->GetTask().Result();

詳細なリファレンス