GS2-StateMachine

ステートマシン管理機能

GS2-Quest はクエストの開始・終了を管理し、開始したクエストに応じて終了時に報酬を受け取れる仕組みを提供しました。 しかし、インゲームのランダム性が強く報酬を事前に特定することが困難なゲーム仕様は、GS2-Quest ではうまく扱えない課題がありました。

GS2-StateMachine はより細かい粒度でインゲームの状態管理をおこなうために開発されました。

ステートマシン

インゲームの状態管理に使用するのはステートマシンです。

flowchart TD Start ----> MainStateMachine_Initialize MainStateMachine_Pass ----> Exit subgraph MainStateMachine MainStateMachine_Initialize[[Initialize]] -->|Pass| MainStateMachine_ChoiceSkill MainStateMachine_ChoiceSkill[/ChoiceSkill/] MainStateMachine_InGame([InGame]) -->|Pass| MainStateMachine_NextTurn MainStateMachine_InGame([InGame]) -->|Fail| MainStateMachine_Pass MainStateMachine_NextTurn[[NextTurn]] -->|Next| MainStateMachine_ChoiceSkill MainStateMachine_NextTurn[[NextTurn]] -->|Exit| MainStateMachine_Pass MainStateMachine_Pass[\Pass/] subgraph ChoiceSkill ChoiceSkill_Initialize[[Initialize]] -->|Pass| ChoiceSkill_LotterySkills ChoiceSkill_LotterySkills[[LotterySkills]] -->|Pass| ChoiceSkill_WaitChoiceSkill ChoiceSkill_WaitChoiceSkill([WaitChoiceSkill]) -->|ChoiceSkill| ChoiceSkill_ChoiceSkill ChoiceSkill_WaitChoiceSkill([WaitChoiceSkill]) -->|ReLotterySkill| ChoiceSkill_ReLotterySkill ChoiceSkill_ReLotterySkill[[ReLotterySkill]] -->|Pass| ChoiceSkill_LotterySkills ChoiceSkill_ReLotterySkill[[ReLotterySkill]] -->|AlreadyReLottery| ChoiceSkill_WaitChoiceSkill ChoiceSkill_ChoiceSkill[[ChoiceSkill]] -->|Pass| ChoiceSkill_Pass ChoiceSkill_ChoiceSkill[[ChoiceSkill]] -->|InvalidSkillIndex| ChoiceSkill_WaitChoiceSkill ChoiceSkill_Pass[\Pass/] end end MainStateMachine_ChoiceSkill --> ChoiceSkill_Initialize ChoiceSkill_Pass -->|Pass| MainStateMachine_InGame Player ----->|Interaction| MainStateMachine_InGame Player ----->|Interaction| ChoiceSkill_WaitChoiceSkill

あなたがプログラマーならプランナーから上記のようなフローチャートを受け取ったことがあるはずです。 このような状態遷移を表現したものがステートマシンです。

現在プレイヤーはどのステートにいるのか、ステートマシン内で使用できる変数はどのような値になっているのかを管理します。 ステートマシンはいずれ終了ステートに遷移し、その時のステートマシンが持つ状態変数によって報酬を確定できるというわけです。

イベントとトランジション

ステートマシンのステート間を結ぶのがトランジションです。 トランジションでは、特定のステートから次のステートに遷移する条件を設定します。

条件にはイベントの受信を設定でき、イベントの種類ごとに次に遷移するステートを変えることができます。 イベントはステートマシン内で実行しているスクリプトから発行することもできますが、プレイヤーからの発行を受け付けることができます。 これによって、プレイヤーがとった選択や、ゲーム結果に応じて処理を分岐させることができます。

イベントにはパラメーターを付けることができますので、選択肢ごとにイベントを用意しなくても 《選択肢から選択した》というイベントと《選択した内容》というパラメーターを渡すようにすることで、ステートマシンをシンプルに保つことも可能です。

ステートマシン定義言語

ステートマシンの定義には GS2 が独自に開発した GS2 States Language(GSL) を使用します。 GSL は以下のような記法で記述します。

StateMachine MainStateMachine {
  Variables {
    int turn;
    int choiceSkill;
    array skills;
  }

  EntryPoint Initialize;

  Task Initialize() {
    Event Pass();
    Event Error(string reason);
    Script grn:gs2:{region}:{ownerId}:script:statemachine-script:script:MainStateMachine_Initialize
  }

  SubStateMachineTask ChoiceSkill {
    using ChoiceSkill;
    in (turn <- turn);
    out (choiceSkill -> choiceSkill);
  }

  WaitTask InGame {
    Event Pass();
    Event Fail();
    Event Error(string reason);
  }

  Task NextTurn() {
    Event Next();
    Event Exit();
    Event Error(string reason);
    Script grn:gs2:{region}:{ownerId}:script:statemachine-script:script:MainStateMachine_NextTurn
  }

  PassTask Pass;

  ErrorTask Error(string reason);

  Transition Initialize handling Pass -> ChoiceSkill;
  Transition Initialize handling Error -> Error;
  Transition ChoiceSkill handling Pass -> InGame;
  Transition InGame handling Pass -> NextTurn;
  Transition InGame handling Fail -> Pass;
  Transition InGame handling Error -> Error;
  Transition NextTurn handling Next -> ChoiceSkill;
  Transition NextTurn handling Exit -> Pass;
  Transition NextTurn handling Error -> Error;
}

詳しい仕様は GS2 States Language の言語仕様について を参照してください。

実装例

ステートマシンを開始

ステートマシンを開始はゲームエンジン用の SDK では処理できません。 GS2-Quest などのマイクロサービスの報酬として設定してください。

ステートマシンにイベントを送信

    var result = await gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: status1.Name
    ).EmitAsync(
        eventName: "event-0001",
        args: "{\"value1\": \"value1\", \"value2\": 2.0, \"value3\": 3}"
    );
    var item = await result.ModelAsync();
    const auto Domain = Gs2->StateMachine->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
        "$status1.name" // statusName
    );
    const auto Future = Domain->Emit(
        "event-0001",
        "{\"value1\": \"value1\", \"value2\": 2.0, \"value3\": 3}" // args
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

    // obtain changed values / result values
    const auto Future2 = Future->GetTask().Result()->Model();
    Future2->StartSynchronousTask();
    if (!TestFalse(WHAT, Future2->GetTask().IsError())) return false;
    const auto Result = Future2->GetTask().Result();

ステートマシンの状態を取得

    var item = await gs2.StateMachine.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
        statusName: status1.Name
    ).ModelAsync();
    const auto item = Gs2->StateMachine->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
        "$status1.name" // statusName
    ).Model();

詳細なリファレンス