GS2-SkillTree

スキルツリー機能

キャラクターなどの成長要素として一般的に使用されるスキルツリー機能を実現するためのマイクロサービスです。 スキルツリーとは、以下のような木構造を持ち、ノードを解放することでキャラクターのパラメーターを向上させることができる機能を指します。 ノードの解放にはコストが必要で、GS2-SkillTree では各マイクロサービスが提供する《消費アクション》を設定できます。

flowchart TD
  Base --> Node1[STR+5]
  Node1 --> Node2[DEF+5]
  Node2 --> Node3[SPD+5]

  Node3 --> Node4[STR+5]
  Node4 --> Node5[DEF+5]
  Node5 --> Node6[STR+5]
  Node6 --> Node7[DEF+5]

  Node3 --> Node10[SPD+5]
  Node10 --> Node11[DEF+5]
  Node11 --> Node12[SPD+5]
  Node12 --> Node13[DEF+5]

  Node11 --> Node30[SPD+5]
  Node30 --> Node31[STR+5]
  Node31 --> Node32[SPD+5]

  Node7 --> Node20[STR+5]
  Node13 --> Node20
  Node20 --> Node21[STR+5]
  Node21 --> Node22[STR+5]

ノード定義(NodeModel)

スキルツリーを構成するノードはマスターデータ NodeModel として定義します。 各ノードは以下のフィールドで構成され、これによりノードの解放条件・コスト・効果・依存関係を表現します。

フィールド 役割
name ノードの識別子
metadata クライアントで表示する説明用テキスト
releaseVerifyActions 解放に必要な検証アクション(特定ランクであることなど。最大10件)
releaseConsumeActions 解放時に消費されるアクション(SP・アイテム・ゴールド等。1〜10件)
restrainReturnRate 未解放化(リストレイン)時のコスト返還率(0.0〜1.0、デフォルト 1.0)
premiseNodeNames 前提ノード(最大10個)

未解放化(リストレイン)時に返還される入手アクション(returnAcquireActions)は releaseConsumeActions を反転し、restrainReturnRate を乗じた内容として自動生成されます。 スキルの効果(ステータス加算など)は、releaseConsumeActions で消費するリソース(GS2-Experience のランクや GS2-Inventory のスキルポイントなど)を別マイクロサービスから連携する形で表現してください。

前提ノード

各ノードにはツリー構造を作るために《前提ノード》を設定できます。 前提ノードには最大10個のノードを設定でき、前提ノードに設定されたノードが解放状態でなければノードは解放することができません。

前提ノードにはそこに至るまでの全てのノードを設定する必要はなく、直前の1つだけ設定すれば木構造を作成できることにご注意ください。

flowchart LR
  A --> B
  B --> C
  C --> D

上記の例では D の premiseNodeNames には C だけを指定すれば十分です。 A・B は C を解放するために必然的に解放済みになっているため、依存関係として記述する必要はありません。

解放処理の流れ

sequenceDiagram
  participant Client
  participant SkillTree as GS2-SkillTree
  participant Other as 他マイクロサービス
  Client->>SkillTree: Release(nodeModelNames)
  SkillTree->>SkillTree: 前提ノード判定
  SkillTree->>SkillTree: releaseVerifyActions 検証
  SkillTree->>Other: releaseConsumeActions 実行
  SkillTree-->>Client: 解放済みノード一覧 (Status)

解放したノードを未解放に戻す

各ノード単位、もしくは全てのノードを未解放状態に戻すことができます。 プレイヤーがビルドを組み直す「振り直し」機能の実装に利用できます。

コストの返還

その際、ノードを解放するのに消費したコストを restrainReturnRate で指定した割合に基づいて返還することができます。

このとき注意しなければならないのは、トランザクションの消費アクションには「反転可能」と「反転不可能」の2種類が存在することです。 「反転可能」な消費アクションについては返還が行われますが「反転不可能」な消費アクションについては返還が行われません。

消費アクションが「反転可能」かは、各マイクロサービスのリファレンスの記載を確認してください。

ツリーの途中にあるノードの操作

ノードに依存しているノードが解放済みの場合、ノードを未解放状態に戻すことはできません。

たとえば、A → B → C と依存関係がある場合に C を解放したままで B を未解放にすることはできません。 振り直しを実現する際は、依存関係の末端から順に未解放化するか、後述の ResetAsync で全ノードを一括して未解放に戻してください。

ノードの一括解放

ノードの解放は複数のノードを一括で解放することができます。 その際依存関係について気をつけながらノードを指定する必要はなく、GS2-SkillTree 側で依存関係の順番を考慮しながら解放処理を行い、解放可能判定を行います。

スクリプトトリガー

ネームスペースに releaseScriptrestrainScript を設定すると、ノード解放や未解放への戻し処理のタイミングでカスタムスクリプトを呼び出せます。

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

  • releaseScript: ノード解放時
  • restrainScript: ノードを未解放に戻す時

マスターデータ運用

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

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

  • NodeModel: スキルツリーのノード定義

マスターデータの JSON 例

{
  "version": "2024-05-30",
  "nodeModels": [
    {
      "name": "str-001",
      "metadata": "STR+5",
      "premiseNodeNames": [],
      "releaseConsumeActions": [
        {
          "action": "Gs2Inventory:ConsumeItemSetByUserId",
          "request": "{\"namespaceName\":\"inventory-0001\",\"inventoryName\":\"skill_point\",\"itemName\":\"sp\",\"userId\":\"#{userId}\",\"consumeCount\":1}"
        }
      ],
      "restrainReturnRate": 1.0
    },
    {
      "name": "str-002",
      "metadata": "STR+10",
      "premiseNodeNames": ["str-001"],
      "releaseConsumeActions": [
        {
          "action": "Gs2Inventory:ConsumeItemSetByUserId",
          "request": "{\"namespaceName\":\"inventory-0001\",\"inventoryName\":\"skill_point\",\"itemName\":\"sp\",\"userId\":\"#{userId}\",\"consumeCount\":2}"
        }
      ],
      "restrainReturnRate": 1.0
    }
  ]
}

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

バフによる補正

GS2-Buff を利用すると、ノードモデルの releaseVerifyActionsreleaseConsumeActionsrestrainReturnRate をバフで補正し、解放条件やコスト、返還率をイベントに応じて調整できます。

たとえば、期間限定イベントとして「スキルポイント1個でも解放できる」「振り直し時の返還率を1.5倍にする」といった施策を、マスターデータを書き換えずにバフの適用だけで実現できます。

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

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

  • 消費アクション: ノードの未解放化
  • 入手アクション: ノードの解放済みの記録

「ノードの解放済みの記録」を入手アクションとして利用することで、ショップでの商品購入時やクエストクリア時の報酬として、直接スキルツリーの特定のノードを解放済みにするといった処理を、トランザクション内で安全に実行できます。これにより、特定の条件を達成したプレイヤーに対して、コストを消費させることなく特別なスキルを即座に付与するといった、柔軟なキャラクター成長の演出が可能になります。

実装例

ノードの解放状態を取得

Status には、現在解放済みのノード一覧(releasedNodeNames)が格納されています。 キャラクターごとにスキルツリーを分けて管理したい場合は、propertyId を指定することで複数のスキルツリー状態を保持できます。

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

ノードの解放

複数ノードを一括で渡すことができます。前提関係を含めて指定しても GS2-SkillTree 側で順序を解決して解放を試みます。

    var domain = gs2.SkillTree.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
    );
    var result = await domain.ReleaseAsync(
        nodeModelNames: new string[] {
            "node-0001",
        }
    );
    const auto Domain = Gs2->SkillTree->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
    );
    const auto Future = Domain->Release(
        []
        {
            const auto v = MakeShared<TArray<FString>>();
            v->Add("node-0001");
            return v;
        }()
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

ノードの解放状態を元に戻す

指定したノードを未解放状態に戻します。restrainReturnRate で指定された割合に基づき、消費したアクションが返還されます(反転可能な消費アクションのみ)。

    var domain = gs2.SkillTree.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
    );
    var result = await domain.RestrainAsync(
        nodeModelNames: new string[] {
            "node-0001",
        }
    );
    const auto Domain = Gs2->SkillTree->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
    );
    const auto Future = Domain->Restrain(
        []
        {
            const auto v = MakeShared<TArray<FString>>();
            v->Add("node-0001");
            return v;
        }()
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

ノードの解放状態をリセットする

解放済みノードを一括で未解放に戻します。「スキル振り直しアイテム」を消費する仕様などで利用できます。 すべてのノードに対して restrainReturnRate に従ったコスト返還が試行されます。

    var domain = gs2.SkillTree.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Status(
    );
    var result = await domain.ResetAsync(
    );
    const auto Domain = Gs2->SkillTree->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Status(
    );
    const auto Future = Domain->Reset(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError())
    {
        return false;
    }

キャラクター単位のスキルツリーを管理する

1つのアカウントの中で、複数のキャラクターそれぞれが独立したスキルツリーを持つようなケースでは、StatuspropertyId を活用します。 GS2-Dictionary や GS2-Inventory で管理しているキャラクターIDをそのまま propertyId として割り当てることで、キャラクターごとに独立した解放状態を保存できます。

詳細なリファレンス