GS2-Stamina

スタミナ機能

スタミナはゲーム内の回数制限機能を実現するものです。 GS2-Limit は「1日に3回」のように回数制限を表現しますが、GS2-Stamina は「8時間で1ポイント回復するスタミナを使用して1日3回に回数制限を実現する」というように、時間経過と消費アクションの組み合わせで制限を表現します。

スタミナの場合、消費するポイントに差をつけることで、Aという行動であれば1日3回、Bという行動であれば1日5回のような仕様を実現できます。 これは、スタミナ1あたりで得られる経験値やゲーム内通貨の指針を設けることで、ゲーム内のどのような行動をとっても、効率のいい行動というものを排除することができ、ゲームバランスを調整しやすくなります。

毎日ログインしてもらうように開発者は努力をするべきなので、あまりこのような仕様にはしませんが、 プレイヤーにとっては8時間で1ポイント回復し、最大15ポイントまで蓄積できる仕様であれば、毎日ログインしなくても5日に1回のログインでもポイントを無駄にすることなく使い切ることができます。

スタミナ

スタミナには「回復周期」「回復量」「最大値」を設定します。

graph LR
  Time["時間経過"] -- "recoverIntervalMinutes ごとに" --> Recover["回復<br/>(+recoverValue)"]
  Recover --> Value["現在値"]
  Consume["消費アクション"] -- "-consumeValue" --> Value
  Value -- "maxValue を超過" --> Overflow["オーバーフロー状態"]
パラメーター 説明
value 現在の保有スタミナ値
maxValue 自然回復で到達できる上限値
recoverIntervalMinutes 1ポイント回復するのに必要な分数
recoverValue 1回の回復で増加する値
overflowValue 最大値を超過している分の値
nextRecoverAt 次回回復の予定日時

オーバーフロー

スタミナは最大値を超えて回復させることができます。 最大値を超えた状態では時間経過による回復は発生しません。 また、最大値を超えた状態でもUIの都合などで、それ以上は加算させない「本当の最大値」を設定できます。

なぜオーバーフローさせるのですか?

この仕様はあまりスタミナを仕様に組み込んだゲームをプレイする機会のなかった開発者には奇妙に感じるかもしれません。

スタミナは一般的に回復するためのアイテムや、ゲーム内課金によって回復する手段が存在します。 そのアイテムを使用したり、スタミナを購入する際にプレイヤーのストレスを最小化しようとこのような仕様が生まれました。

具体的な例を示しましょう。

このゲームは5分で1ポイントスタミナが回復し、あなたのスタミナの最大値は50ポイントです。 あなたが次にプレイしたいクエストをプレイするにはスタミナが10ポイント必要です。 しかし、現在のあなたのスタミナ値は9ポイントしかなく、5分待たなければ次のクエストをプレイできません。 そこで、あなたはスタミナを購入しようと思いました。スタミナを購入するとスタミナが50ポイント回復します。 しかし、今すぐにスタミナを購入すると、50ポイント回復しても9ポイントは無駄になってしまいます。 そのため、あなたは5分待って1ポイント回復した後でクエストをプレイし、0ポイントにした後で、その次のクエストを遊ぶためにスタミナを購入しました。

これではプレイヤーがゲームを快適にプレイすることはできません。 そこで、最大値を超えてスタミナを回復できる仕様を導入するゲームが生まれました。

スタミナに最大値を超える仕様を加え、ユーザー体験がどのように変化するか見てみましょう。

現在のあなたのスタミナ値は9ポイントしかなく、5分待たなければ次のクエストをプレイできません。 そこで、あなたはスタミナを購入し、スタミナを50ポイント回復し、59ポイントしました。 この状態では時間経過によるスタミナの回復は行われなくなりますが、 あなたはすぐに次のクエストを開始し、スタミナを49ポイントにしました。

GS2-Experience との連携

MaxStaminaTable / RecoverIntervalTable / RecoverValueTable をマスターデータに登録し、GS2-Experience のランク(プレイヤーレベル等)に応じて自動的に最大値・回復周期・回復量を変化させることができます。 プレイヤーレベルアップに合わせて自動的にスタミナの上限が増えるといった、よくある成長要素を簡単に表現できます。

自然回復の同期

Apply API を呼ぶと、最後にアクセスしてから経過した時間分のスタミナ自然回復を即時反映できます。 通常はスタミナ取得 API の内部で自動的に反映されるため、明示的に呼ぶ必要はほとんどありませんが、サーバー側でスタミナ値を厳密に同期させたい場合に使用します。

スクリプトトリガー

ネームスペースに overflowTriggerScript を設定すると、自然回復によってスタミナが最大値を超えた際にカスタムスクリプトを実行できます。これにより、オーバーフロー時にあふれたスタミナを別のリソースに変換するといった応用が可能です。

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

  • overflowTriggerScript: スタミナオーバーフロー時

バフによる補正

GS2-Buff と連携すると、maxValuerecoverIntervalMinutesrecoverValue のほか consumeValuerecoverValue といったアクションパラメーターをバフで補正でき、イベント等でスタミナ上限や回復速度、消費量を柔軟に調整できます。

「期間限定でスタミナ最大値を1.5倍にする」「ログイン直後の1時間だけ回復速度を2倍にする」といった、時限的なゲーム体験の調整に活用できます。

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

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

  • 検証アクション: 現在値の検証、最大値の検証、回復速度の検証、回復量の検証、オーバーフロー量の検証
  • 消費アクション: スタミナの消費、最大値の減算
  • 入手アクション: スタミナの回復(加算)、最大値の加算・設定、回復速度の設定、回復量の設定

「最大値の加算」を入手アクションとして利用することで、ショップでのアイテム購入時やプレイヤーランクが上昇した際などに、自動的にスタミナの最大容量を拡張するといった処理をトランザクション内で安全に実行できます。これにより、プレイヤーの成長を実感させる報酬設計が容易になります。また、「スタミナの回復」を報酬として設定することで、特定のミッション達成時にスタミナを全回復させ、継続プレイを促すといった運用も可能です。

マスターデータ運用

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

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

  • StaminaModel: 回復量・回復間隔・最大値・オーバーフローを許容するかの定義
  • MaxStaminaTable: GS2-Experience のランクから最大値を引くテーブル
  • RecoverIntervalTable: GS2-Experience のランクから回復周期を引くテーブル
  • RecoverValueTable: GS2-Experience のランクから回復量を引くテーブル

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

{
  "version": "2019-04-23",
  "staminaModels": [
    {
      "name": "stamina-0001",
      "metadata": "クエスト用スタミナ",
      "recoverIntervalMinutes": 5,
      "recoverValue": 1,
      "initialCapacity": 50,
      "isOverflow": true,
      "maxCapacity": 999
    }
  ]
}

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

実装例

現在のスタミナ値を取得

取得時刻における自然回復が反映された状態のスタミナ値が返却されます。

    var item = await gs2.Stamina.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Stamina(
        staminaName: "stamina-0001"
    ).ModelAsync();
    const auto Domain = Gs2->Stamina->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Stamina(
        "stamina-0001" // staminaName
    );
    const auto Item = Domain->Model();

スタミナ一覧を取得

ネームスペースに登録された複数の StaminaModel のうち、プレイヤーが利用している全てのスタミナの現在値を一括で取得できます。

    var items = await gs2.Stamina.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).StaminasAsync(
    ).ToListAsync();
    const auto Domain = Gs2->Stamina->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    );
    const auto It = Domain->Staminas(
    );
    TArray<Gs2::UE5::Stamina::Model::FEzStaminaPtr> Result;
    for (auto Item : *It)
    {
        if (Item.IsError())
        {
            return false;
        }
        Result.Add(Item.Current());
    }

スタミナを消費

このAPIで直接スタミナを消費する処理を行うことは推奨していません。

GS2-Quest といったサービスを通してスタミナの消費を行うことで、消費と引き換えにクエスト開始やアイテム入手などの処理を一連のトランザクションとして安全に扱うことができます。

    var result = await gs2.Stamina.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Stamina(
        staminaName: "stamina-0001"
    ).ConsumeAsync(
        consumeValue: 50
    );
    var item = await result.ModelAsync();
    const auto Domain = Gs2->Stamina->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Stamina(
        "stamina-0001" // staminaName
    );
    const auto Future = Domain->Consume(
        50
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

自然回復の反映

最後のアクセス以降に経過した時間分の自然回復を即時反映します。 通常は API 呼び出し時に内部で自動反映されるため、明示的に呼ぶ必要はほとんどありません。

    var result = await gs2.Stamina.Namespace(
        namespaceName: "namespace-0001"
    ).Me(
        gameSession: GameSession
    ).Stamina(
        staminaName: "stamina-0001"
    ).ApplyAsync(
    );
    var item = await result.ModelAsync();
    const auto Domain = Gs2->Stamina->Namespace(
        "namespace-0001" // namespaceName
    )->Me(
        AccessToken
    )->Stamina(
        "stamina-0001" // staminaName
    );
    const auto Future = Domain->Apply(
    );
    Future->StartSynchronousTask();
    if (Future->GetTask().IsError()) return false;

応用例: 街づくりゲームの採集機能

スタミナの仕組みを応用すると、街づくりゲームの採集機能も実装できます。 スタミナモデルに建築物を設定し、スタミナ値を建築物の生産量として表現することで、時間経過によって資源が蓄積していく採集システムを実現できます。

たとえば、「鉄鉱石を生産する鉱山」を1つのスタミナモデルとして扱い、回復周期を採掘間隔、回復量を1回あたりの採掘量、最大値を倉庫の上限として表現できます。 プレイヤーが回収アクションを行ったときに Consume で全量を消費し、得られたポイント数を GS2-Inventory にアイテムとして格納する、といった構成が可能です。

詳細なリファレンス