GS2-Mission

ミッション・実績機能

ゲーム内の蓄積した行動に基づいて、プレイヤーに報酬を与えるための仕組みです。 一般的に 実績・トロフィー・ミッション と呼ばれる機能を実現するための機能です。

graph TD
  Action["ゲーム内行動<br/>(クエストクリア / ガチャ実行 など)"] -- "カウンター上昇<br/>(IncreaseCounterByUserId)" --> Counter["ミッションカウンター<br/>(Counter / Scope)"]
  Counter -- "目標値到達" --> Task["ミッションタスク<br/>(MissionTaskModel)"]
  Task -- "ReceiveRewards" --> Reward["completeAcquireActions"]
  Reward --> Distributor["GS2-Distributor<br/>報酬配布"]
  Task --> Group["ミッショングループ<br/>(MissionGroupModel)"]
  Group -- "resetType に基づき<br/>受取フラグをリセット" --> Task

ミッションカウンター

プレイヤーの行動に基づく回数を数えるためのエンティティです。 「クエストをクリアした回数」「キャラクターを強化した回数」「ガチャを引いた回数」といったゲーム内の行動回数をカウントするためのカウンターを用意します。

カウンターにはスコープを設定できます。 スコープには以下の値を設定可能です。

スコープの種類 スコープの内容
リセットしない ゲームプレイ開始からの総計
毎日X時にリセット 当日実行した回数
毎週X曜日X時にリセット 今週実行した回数
毎月X日X時にリセット 今月実行した回数
一定日数ごとにリセット 基準時刻から指定日数ごとにリセット
検証アクションにマッチした時だけカウントアップ verifyAction の実行結果に基づいてカウンターを上昇

カウンターの値は各スコープで管理され、ミッションの達成条件には各スコープの値を利用できます。

graph LR
  Up["カウンター上昇要求"] --> Counter["Counter"]
  Counter --> Scope1["スコープ1<br/>リセットしない"]
  Counter --> Scope2["スコープ2<br/>毎日リセット"]
  Counter --> Scope3["スコープ3<br/>毎週リセット"]
  Scope1 -- ScopedValue --> Mission1["累計100回ミッション"]
  Scope2 -- ScopedValue --> Mission2["デイリー10回ミッション"]
  Scope3 -- ScopedValue --> Mission3["ウィークリー50回ミッション"]

検証アクションによるスコープ制御

scopeType=verifyAction を利用すると、conditionName と condition により任意条件を定義し、 他のマイクロサービスの検証結果に応じてカウンターを上昇するかの制御ができます。 これにより、特定のイベント期間だけカウンターを上昇するスコープや、特定のキャラクターを編成している状態でのみ上昇するスコープを作ることができます。

チャレンジ期間

CounterModel には challengePeriodEventId を設定でき、GS2-Schedule のイベント GRN を指定してカウンター操作期間を制限できます。 指定期間外はカウンターを更新できないため、期間限定イベントなどで使用する際に有効です。

ミッションタスク

プレイヤーに提示する目標を定義するマスターデータです。

「ミッションカウンター」 と 「スコープ」 と「目標値」、そしてその目標を達成した時に得られる「報酬」を設定します。 たとえば以下のような設定ができます。

ミッションカウンター スコープの種類 目標値 報酬
クエストをクリアした回数 毎日X時にリセット 10 アイテムA
クエストをクリアした回数 毎週X曜日X時にリセット 50 アイテムB
キャラクターを強化した回数 毎日X時にリセット 5 アイテムC

タスクの依存関係

premiseMissionTaskName を指定することで、先行するタスクを達成した後でのみ受け取れるミッションを定義できます。 「タスクAを達成するとタスクBが受け取り可能になる」といった段階的な達成フローを構築できます。

検証アクションを使用した達成判定

verifyCompleteTypeverifyActions を指定することで、他のマイクロサービスの検証アクションを満たしているかでタスクの達成判定が可能です。 この機能を利用する場合、GS2-Mission が提供する Complete オブジェクトによる達成判定(サーバーサイドでの自動判定)は行われません。そのため、達成状態はクライアントで計算した上で受け取りUIを制御し、報酬受け取りアクションを実行する必要があります。

サーバーサイドで達成可能性を再評価する場合は EvaluateComplete API を呼び出すことで Complete の状態を最新化できます。

チャレンジ期間

MissionTaskModelchallengePeriodEventId により、達成可能期間を GS2-Schedule のイベントで制限できます。 達成済みのタスクは期間を過ぎても受け取ることが可能ですが、ミッションタスクにリセット間隔が設定されている場合はリセットタイミングを迎えると受け取りができなくなります。

ミッショングループ

複数のミッションタスクを束ねるエンティティです。 ミッショングループには報酬の受け取りフラグのリセット周期を設定できます。

ミッションカウンター スコープの種類 目標値 報酬
クエストをクリアした回数 毎日X時にリセット 10 アイテムA
キャラクターを強化した回数 毎日X時にリセット 5 アイテムC

これらのミッションタスクを一つのミッショングループに関連付けて、ミッショングループの報酬の受け取りフラグのリセット周期にも「毎日X時にリセット」を設定することで ミッションタスクを毎日達成すると、毎日報酬を受け取れるようになります。

リセット種別

resetType には以下を指定できます。

resetType 説明
notReset リセットしない(実績・トロフィー的な利用)
daily 毎日 resetHour 時にリセット
weekly 毎週 resetDayOfWeekresetHour 時にリセット
monthly 毎月 resetDayOfMonthresetHour 時にリセット
days anchorTimestamp を起点に days 日ごとにリセット

任意日数でのリセット

resetTypedays を指定することで、anchorTimestamp で指定した起点時刻から指定日数ごとに報酬受取フラグをリセットできます。 例えば、イベント開始から3日ごとにリセットする、といった運用が可能です。

スクリプトトリガー

ネームスペースに missionCompleteScriptcounterIncrementScriptreceiveRewardsScript を設定すると、ミッション達成・カウンター上昇・報酬受け取りといったタイミングでカスタムスクリプトを呼び出せます。

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

  • missionCompleteScript: ミッション達成
  • counterIncrementScript: カウンター上昇
  • receiveRewardsScript: 報酬受け取り

これらを活用することで、達成時のBI連携、不正なカウンター操作の検知、報酬受取時の特殊処理などをカスタマイズできます。

プッシュ通知

設定できる主なプッシュ通知と設定名は以下の通りです。

  • completeNotification: ミッションタスク達成時に通知

オフライン端末へのモバイルプッシュ転送にも対応し、報酬受取を促すことができます。

マスターデータ運用

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

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

  • CounterModel: カウント対象とリセット周期を定義する CounterScopeModel のリスト
  • MissionGroupModel: グループ単位のリセット設定と所属するタスク
  • MissionTaskModel: 達成条件(カウンターと目標値)と達成報酬

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

以下はマスターデータの JSON 例です。

{
  "version": "2019-04-12",
  "counters": [
    {
      "name": "quest_complete",
      "metadata": "クエストクリア回数",
      "scopes": [
        {
          "scopeType": "resetTimingScope",
          "resetType": "daily",
          "resetHour": 5
        },
        {
          "scopeType": "resetTimingScope",
          "resetType": "weekly",
          "resetDayOfWeek": "monday",
          "resetHour": 5
        }
      ]
    }
  ],
  "missionGroups": [
    {
      "name": "daily-mission",
      "metadata": "デイリーミッション",
      "resetType": "daily",
      "resetHour": 5,
      "tasks": [
        {
          "name": "mission-task-0001",
          "metadata": "クエストを10回クリア",
          "counterName": "quest_complete",
          "targetResetType": "daily",
          "targetValue": 10,
          "completeAcquireActions": []
        }
      ]
    }
  ]
}

GS2-Buff 連携

GS2-Buff と連携すると、ミッションタスクモデルの completeAcquireActions をバフで補正し、イベントなどに応じて報酬量を柔軟に調整できます。

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

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

  • 検証アクション: ミッション達成状況の検証、カウンター値の検証
  • 消費アクション: 報酬の受け取り(一括含む)、カウンターの減算・リセット
  • 入手アクション: カウンターの加算・設定、報酬受け取り状態の復元(未受け取り化)

「カウンターの加算」を入手アクションとして利用することで、ショップでの商品購入時やクエストクリア時の報酬として、直接ミッションの進行度を進めるといった処理が可能になります。また、「カウンターの減算」を消費アクションとして利用することで、特定の特典を受けるためのコスト(ポイント消費型ミッションなど)としてミッションカウンターを消費するといった運用も、トランザクション内で安全に実現できます。

実装例

ミッションカウンターの上昇

ミッションカウンターの上昇はゲームエンジン用の SDK では処理できません。

GS2-Quest のクリア報酬や、GS2-Lottery の抽選報酬としてカウンターを上昇するといった方法で実装してください。クライアントから直接カウンターを上昇させたい場合は、サーバー側でトランザクションを発行する形で GS2-Distributor 経由の入手アクションとして実行します。

ミッションタスクの達成状況・報酬受け取り情報を取得

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

ミッション達成報酬を受け取り

    var result = await gs2.Mission.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Complete(
        missionGroupName: "mission-group-0001"
    ).ReceiveRewardsAsync(
        missionTaskName: "mission-task-0001"
    );

    await result.WaitAsync();
    const auto Domain = Gs2->Mission->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Complete(
        "mission-group-0001" // missionGroupName
    );
    const auto Future = Domain->ReceiveRewards(
        "mission-task-0001"
    );
    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;

達成済み報酬の一括受け取り

達成済みかつ未受領のタスクを一度に受け取りたい場合、BatchReceiveRewards を利用できます。 複数の missionTaskName を指定して、ひとつのトランザクションとして報酬を受け取ります。

    var result = await gs2.Mission.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Complete(
        missionGroupName: "mission-group-0001"
    ).BatchReceiveRewardsAsync(
        missionTaskNames: new [] {
            "mission-task-0001",
            "mission-task-0002",
        }
    );

    await result.WaitAsync();
    const auto Domain = Gs2->Mission->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Complete(
        "mission-group-0001" // missionGroupName
    );
    const auto Future = Domain->BatchReceiveRewards(
        []
        {
            auto v = MakeShared<TArray<FString>>();
            v->Add("mission-task-0001");
            v->Add("mission-task-0002");
            return v;
        }()
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

検証アクションを利用したタスクの達成判定再評価

verifyCompleteTypeverifyActions を指定したタスクの達成状況を、サーバー側で再評価したい場合に呼び出します。

    var result = await gs2.Mission.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Complete(
        missionGroupName: "mission-group-0001"
    ).EvaluateCompleteAsync(
    );
    const auto Domain = Gs2->Mission->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Complete(
        "mission-group-0001" // missionGroupName
    );
    const auto Future = Domain->EvaluateComplete();
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

ミッションカウンターの値を取得

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

ミッションカウンターのリセット

特定のスコープのカウンター値を手動でリセットできます。 イベント終了時にイベント関連カウンターを初期化する、といった用途で利用します。

    var result = await gs2.Mission.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Counter(
        counterName: "quest_complete"
    ).ResetCounterAsync(
        scopes: new [] {
            new Gs2.Unity.Gs2Mission.Model.EzScopedValue() {
                ResetType = "daily",
                Value = 0,
            },
        }
    );
    const auto Domain = Gs2->Mission->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Counter(
        "quest_complete" // counterName
    );
    const auto Future = Domain->ResetCounter(
        Scopes
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

ミッションの目標値を取得

    var items = await gs2.Mission.Namespace(
        namespaceName: "namespace-0001"
    ).MissionGroupModel(
        missionGroupName: "mission-group-0001"
    ).MissionTaskModelsAsync(
    ).ToListAsync();
    const auto Domain = Gs2->Mission->Namespace(
        "namespace-0001" // namespaceName
    )->MissionGroupModel(
        "mission-group-0001" // missionGroupName
    );
    const auto It = Domain->MissionTaskModels(
    );
    TArray<Gs2::UE5::Mission::Model::FEzMissionTaskModelPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

詳細なリファレンス