GS2 Blog

Game Server Services(https://gs2.io/) の最新情報をお届けします

SimpleInventory / BigInventory に任意の所持数量を設定できるようになりました

はじめに

SimpleInventory はアイテムの所持数量を管理する仕組みで、通常の Inventory が持つスタックサイズの制限や、Inventory の容量制限の管理の仕組みがない代わりに、複数のアイテムを同時に増減することが可能なインベントリです。

BigInventory はアイテムの所持数量を int64 の範囲を超えて保持できるインベントリです。

今回の更新の背景

アイテムの所持数量の操作は、これまで増減APIを通して実行することになっていました。
しかし、任意の数量を持っている状況を作り出したい時に、すでに所有している数量を考慮した上で増減量を調整して処理を実行するのはロジックが関わり不具合の原因となりかねない処理を組み込む必要がありました。

今回の更新の詳細

setSimpleItemsByUserId
GS2-Inventory SDK API リファレンス | Game Server Services | Docs

setBigItemByUserId
GS2-Inventory SDK API リファレンス | Game Server Services | Docs

APIが追加され、それぞれに対応したトランザクション入手アクションとして

Gs2Inventory:SetSimpleItemsByUserId
GS2-Inventory トランザクションアクション | Game Server Services | Docs

Gs2Inventory:SetBigItemByUserId
GS2-Inventory トランザクションアクション | Game Server Services | Docs

が追加されました。

サーバーから取得した時刻を基準とした現在時刻取得機能が追加されました

はじめに

イベントの開始時刻や終了時刻など運営型ゲームでは時間の取り扱いは重要です。

機能追加の背景

一方で、ゲームが動作するデバイスの時刻設定は必ずしも正確とは言えず、正しくUIを表現するのは難しいです。
サーバーから取得した時刻を使用して、現在時間を補正することが一般的に行われており、それによってより正確なUIを表現することができるようになります。

GS2 では元々 GS2-Realtime::Now というAPIで現在時刻を取得できました。
しかし、それはその瞬間の時刻の取得であり、刻々と変化する時刻やスリープ復帰後にも問題なく動作するような仕組みはゲーム開発者が構築する必要がありました。

追加された機能の詳細

    Gs2DateTime datetime = await application.Realtime.Gs2DateTimeAsync(
        GameSession
    );
    DateTimeOffset now = datetime.Current;

Gs2DateTimeAsync を呼び出した時に GS2-Realtime::Now を呼び出して現在時刻を取得し Gs2DateTime オブジェクトを返します。
Gs2DateTime の Current にアクセスすることで、補正が適用された現在時刻を取得できます。

QA目的で GS2-Account や GS2-Auth を使用して現在時刻のオフセットを設定している場合、Gs2DateTime オブジェクトが返す時刻にもオフセットが適用されます。

Nifty Cloud mobile backend をご利用の皆さんへ

本日、Nifty Cloud mobile backend(NCMB) のサービス終了がアナウンスされました。
多くのインディーデベロッパーさんを支えてきたサービスで、移行先を検討される方も多くいらっしゃると思います。

GS2 がその移行先として選んでいただくにあたって、必要となるであろう情報をこちらにまとめます。

料金(無料枠)

まず、重要なのは利用料金です。

API

無料で利用可能な範囲についてですが、NCMB は 100万回 ⁄ 月 が無料で利用できます。
GS2 も月2万円の無料枠があり、APIリクエスト 0.02円 が基本となっているため、同じく100万回/月 が無料で利用可能です。

ストレージ

NCMB ではストレージ機能が提供されており、容量制限があります。
しかし、GS2 が提供するストレージ機能である GS2-Datastore には容量制限はありません。
代わりに 「データの保存容量 1GB 10円」の料金が発生し、無料枠の消費で充当が可能です。

データベース

NCMB ではデータベース機能があり、データベースにインデックスを構築して検索速度を向上するのは無料プランでは利用できません。
GS2 では任意のデータベースを作成できない代わりに、ゲームの用途に最適化された30を超えるサービスが提供されており、常に最適化されたインデックスを使用してデータベースにアクセスします。
そのため、サービス利用者は検索速度などを考える必要がなく、同様の料金プランも存在しません。

スクリプト

NCMB ではスクリプト機能があり、無料プランでは「5万回 ⁄ 月」が利用可能です。
GS2 では、通常のAPIリクエストと同様にカウントされます。
追加で「スクリプトの実行時間 1秒 0.005円」の追加料金が発生しますが、こちらも無料枠の消費で充当が可能です。
NCMBにあるような累計時間の制限はありません。

インディー開発者向けプラン

GS2 には NCMB にない料金プランとしてインディー開発者向けの料金プランがあります。
この料金プランは過去12ヶ月の売り上げが 1000万円未満の個人又は法人が利用可能なプランで、一定の条件を満たす限りGS2のあらゆる機能が無料で利用できます。

一定の条件

月の平均リクエスト回数が秒間10回または、一定期間継続して秒間リクエスト回数が100回を超える場合は Individual プランの対象外となります。

という、通常であれば売り上げ1000万円を超える規模の売り上げになっていてもおかしくない規模のアクセスが発生しているにもかかわらず、そのような申告をいただいていないケース向けの制限です。

起動シーケンス もしくは タイトル画面 に GS2 のロゴを掲載する必要があります。 
ゲームのリリースの目処がたったタイミングで、チャットよりロゴデータと表示に伴うガイドラインを受け取ってください。

に従ってゲーム内にGS2のロゴを掲載する必要があります。
詳細は以下を参照してください。

GS2の利用料金 | Game Server Services | Docs

利用方法

GS2 は https://gs2.io でサインアップすることで、すぐにサービスを利用開始できます。

サンプル

github.com

こちらにサンプルコードを用意しました。
NCMB を利用して実現していた一般的な内容が含まれているはずです。

実行例

GS2 では環境のセットアップに管理画面からポチポチ登録するだけでなく、定義ファイルのアップロードでも対応できます。
今回は CloudSave プロジェクトの実行までの流れを説明します。

環境のセットアップ

サンプルに template.yaml というファイルがあります。

GS2TemplateFormatVersion: "2019-05-01"

Resources:
  AccountNamespace:
    Type: GS2::Account::Namespace
    Properties:
      Name: CloudSave-Account

  DatastoreNamespace:
    Type: GS2::Datastore::Namespace
    Properties:
      Name: CloudSave-Datastore

こちらは、GS2-Account と GS2-Datastore のセットアップをする内容が記述されています。

GS2 のマネージメントコンソール(管理画面)にアクセスします。

サイドメニューより「Deploy -> Stacks」を選択します。

「スタックの新規作成」を選択します。

スタックの名前を「CloudSave」として、template.yaml をアップロードします。

スタックの詳細画面で、実行状態が「作成完了」や、イベントリストに「CREATE_COMPLETE」のような表示が行われればセットアップは完了です。

実行

サンプルリポジトリをチェックアウトし、CloudSave.unity を Unity Editor で開きます。

シーン内に存在する GameObject を選択し、インスペクターを確認します。

ClientID / Client Secret にはあらかじめサンプルの動作確認用の値が設定されています。(この値は定期的に変更されるため、動作確認以外の利用は避けてください)
ここを自分のプロジェクトの ClientID / ClientSecret に書き換えましょう。

マネージメントコンソールの Home にプロジェクトで使用できる ClientID / ClientSecret が表示されますので、そちらを設定してください。

実行すると「ABC」という文字列がコンソールに書き出されます。

実装の確認
/*
 * Copyright 2016 Game Server Services, Inc. or its affiliates. All Rights
 * Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

#if GS2_ENABLE_UNITASK
using System.Collections.Generic;
using System.Text;
using Cysharp.Threading.Tasks.Linq;
using Gs2.Core.Model;
using Gs2.Unity.Core;
using Gs2.Unity.Util;
using UnityEngine;
using Cysharp.Threading.Tasks;
using UnityEditor;

namespace Gs2.Sample.Simple
{
    public class CloudSaveAsync : MonoBehaviour
    {
        public string clientId;
        public string clientSecret;

        public string accountNamespaceName;
        public string datastoreNamespaceName;
        
        private async UniTask ProcessAsync() {
            // Initialize GS2 SDK
            var gs2 = await Gs2Client.CreateAsync(
                new BasicGs2Credential(
                    clientId,
                    clientSecret
                ),
                Region.ApNortheast1
            );
            
            // define GS2-Account namespace
            var gs2Account = gs2.Account.Namespace(
                this.accountNamespaceName
            );
            
            // Create an anonymous account
            var account = await (
                await gs2Account.CreateAsync()
            ).ModelAsync();
            
            // login
            var gameSession = await gs2.LoginAsync(
                new Gs2AccountAuthenticator(
                    accountSetting: new AccountSetting {
                        accountNamespaceName = this.accountNamespaceName,
                    }
                ),
                account.UserId,
                account.Password
            );

            // define GS2-Datastore namespace
            var gs2Datastore = gs2.Datastore.Namespace(
                this.datastoreNamespaceName
            );

            // define save data
            var saveData = new byte[] {
                (byte) 'A', (byte) 'B', (byte) 'C'
            };
            
            // upload save data
            await gs2Datastore.Me(
                gameSession
            ).UploadAsync(
                "public",
                new List<string>(),
                saveData,
                "PublicData"
            );

            // download save data
            var downloadedSaveData = await gs2Datastore.Me(
                gameSession
            ).DataObject(
                "PublicData"
            ).DownloadAsync();

            Debug.Log(Encoding.ASCII.GetString(downloadedSaveData));
        }
        
        public void Start() {
            StartCoroutine(ProcessAsync().ToCoroutine());
        }
    }
}
#endif

実は ABC という文字列は GS2-Datastore に保存し、ダウンロードした内容を表示していました。

データの確認

上記プログラムでは GS2-Account でログインし、GS2-Datastore にデータをアップロードしていました。
マネージメントコンソールでデータを確認してみましょう。

まずは GS2-Account の管理メニューを開くためにサイドメニューから「Account -> Namespaces」を選択します。

CloudSave-Account がこのサンプルでアカウントを保存している領域です。

「bc708ed6-a319-4480-bd64-bd5727afec09」というユーザーIDのプレイヤーが作成されていることがわかります。

次に、GS2-Datastore の管理メニューを開きます「Datastore -> Namespaces」を選択し、「CloudSave-Datastore」を開きます。

ユーザーデータタブの Target User ID に「bc708ed6-a319-4480-bd64-bd5727afec09」を指定すると、このプレイヤーが GS2-Datastore にアップロードしたデータを参照できます。

「PublicData」 という名前のファイルがアップロードされていることがわかります。

3バイトのデータがアップロードされていることがわかります。

最後に

まずはぜひ触ってみてください!

マイクロサービス間の連携の設定が簡素化されました

はじめに

GS2 は多数のマイクロサービスを提供しており、多くのケースはトランザクション処理として、《消費アクション》と《入手アクション》として抽象化されています。

一方で、トランザクションを実行するために必要な GS2-Distributor や GS2-JobQueue
各種暗号処理で利用する GS2-Key の暗号鍵、各種通知処理で使用する GS2-Gateway といったマイクロサービスとの関連付けは各マイクロサービスのネームスペース設定として設定が必要です。

この設定漏れや誤りによる不具合報告を多数頂戴する状況から、利用者にとってもGS2にとってもボトルネックになっていると感じていました。

変更の内容

2023年8月にはプロジェクトの作成時にこれらの設定のややこしいマイクロサービスに「default」という名前でネームスペースを作成するようになりました。
これによって、マネージメントコンソールを初めて操作する際でも、とりあえず選択可能な項目を選択することで設定を完了できるようになりました。
しかし、実際には GS2-SDK を利用する際にもこれらの値を正しく設定しなければ、意図する動作にならないケースがあり、まだこの対応では不十分でした。

今回の更新で、サーバー・SDKを共に大規模な更新を行い、これらの連携用の設定が未設定の場合は「default」ネームスペースを使用して動作するようになります。

変更の適用方法

サーバーサイドは自動的に未設定の場合でもデフォルトネームスペースが利用されます。
もし、プロジェクトの作成が 2023年8月 より前で、デフォルトネームスペースが作成されていない場合は以下の GS2-Deploy テンプレートを適用することで、デフォルトネームスペースを後から作成することができます。

GS2TemplateFormatVersion: "2019-05-01"

Globals:
  Alias:
    ApplicationUserName: default
    KeyNamespaceName: default
    KeyName: default
    DistributorNamespaceName: default
    GatewayNamespaceName: default
    JobQueueNamespaceName: default

Resources:

  IdentifierApplicationUser:
    Type: GS2::Identifier::User
    Properties:
      Name: ${ApplicationUserName}

  IdentifierApplicationUserAttachPolicy:
    Type: GS2::Identifier::AttachSecurityPolicy
    Properties:
      UserName: ${ApplicationUserName}
      SecurityPolicyId: grn:gs2::system:identifier:securityPolicy:ApplicationAccess
    DependsOn:
      - IdentifierApplicationUser

  IdentifierApplicationIdentifier:
    Type: GS2::Identifier::Identifier
    Properties:
      UserName: ${ApplicationUserName}
    DependsOn:
      - IdentifierApplicationUser

  KeyNamespace:
    Type: GS2::Key::Namespace
    Properties:
      Name: ${KeyNamespaceName}

  Key:
    Type: GS2::Key::Key
    Properties:
      NamespaceName: ${KeyNamespaceName}
      Name: ${KeyName}
    DependsOn:
      - KeyNamespace

  GatewayNamespace:
    Type: GS2::Gateway::Namespace
    Properties:
      Name: ${GatewayNamespaceName}

  DistributorNamespace:
    Type: GS2::Distributor::Namespace
    Properties:
      Name: ${DistributorNamespaceName}
      AutoRunStampSheetNotification:
        GatewayNamespaceId: !GetAttr GatewayNamespace.Item.NamespaceId

  JobQueueNamespace:
    Type: GS2::JobQueue::Namespace
    Properties:
      Name: ${JobQueueNamespaceName}
      EnableAutoRun: true

Outputs:
  ApplicationClientId: !GetAttr IdentifierApplicationIdentifier.Item.ClientId
  ApplicationClientSecret: !GetAttr IdentifierApplicationIdentifier.ClientSecret

あとは、GS2-SDK を更新してください。Unity の場合は「v2023.11.7」以降を利用するようにしてください。

トランザクション処理の投機的実行機能が Unreal Engine 5 をサポートしました

gs2.hatenablog.com

先日、通信待ちをすることなく通信後の結果を想定して GS2-SDK がもつローカルキャッシュを投機的に更新することで
SDKから取得できる値を先行して更新後の値を取得できるようにする仕組みのリリースを発表しました。

前回リリースの段階では、Unity 向けの SDK でのみ本機能が実装されていましたが
この度 Unreal Engine 向けの SDK でも同様の対応が行われました。

細かな仕様は Unity と同様ですので、昨日の詳細については前述のブログ記事を参照ください。

トランザクション処理の投機的実行機能が追加されました

はじめに

GS2におけるトランザクション処理というのはユーザーデータの増減処理を指します。
たとえば、「課金通貨を消費してスタミナを回復する」というような操作です。

機能追加の背景

GS2はレスポンスタイムの最適化を日々行なっており、数百msのオーダーで処理が実行されます。
しかし、通信が完了するまでUIを更新しないとこの数百msのラグが違和感を生んでしまいます。

そこで、ユーザー体験を最適化するために投機的実行機能を実装しました。

追加された機能の詳細

GS2-Showcase の Buy や GS2-Exchange の Exchange 関数に第二引数

bool speculativeExecute = true

が追加されました。ここで投機実行機能を有効化するかを指定することができます。
記載の通り、デフォルトで有効化されます。

投機的実行のメカニズム

投機的実行処理を有効にしてトランザクション処理を実行すると、GS2-SDK は通信を開始する前にトランザクション実行後に期待される値でSDKがもつローカルキャッシュを更新します。
これによって、GS2-SDK を経由して取得される値も先行して期待される値に更新されます。

ローカルキャッシュを書き換えますが、このキャッシュの有効期限は10秒に設定され
10秒経ってもトランザクション処理が正常に完了しない場合は改めて最新の値をサーバーから取得します。
10秒以内にトランザクションが完了すれば、サーバーからのトランザクション完了通知を受け取ってキャッシュの有効期限がデフォルト値であれば15分に延長されます。

投機的実行できない処理

GS2-Money のストアプラットフォームのレシート検証や、GS2-Lottery の抽選処理などサーバーが処理しなければ結果がわからない処理については投機的実行は行われません。
投機的実行ができないトランザクション処理が含まれていた場合、以下の警告がコンソールに出力されます。

Speculative execution not supported on this action: XXX:XXX

投機的実行ができないだけで処理自体は今まで通り行われますので、サーバーからの完了通知をもってローカルキャッシュの更新が行われます。
そのため、この警告については深く気にする必要はありません。

投機的実行の入れ子

投機的実行処理はトランザクション処理が入れ子構造になっていても適用されます。
たとえば、GS2-Showcase で GS2-Exchange の交換処理を実行する入手アクションが設定された商品が登録されており、その商品を購入した場合は、GS2-Exchange の交換処理についても投機的実行が行われます。

連続的な処理でのチャタリング

投機的実行処理によって通信処理が完了しなくてもUI上は想定される最新の値を表示するようになったことで
交換処理のAPIを連打した時にUIのチャタリングが生じるようになる可能性があります。

具体的には以下のような順番で処理が発生した場合を想定してください。

アイテムを1個入手(処理A) -> アイテム数量1
アイテムを1個入手(処理B) -> アイテム数量2
サーバーで処理Aが完了 -> アイテム数量1
サーバーで処理Bが完了 -> アイテム数量2

この場合、一瞬アイテムの数量が 2 から 1 に巻き戻って表示されます。

このように高頻度(1秒に何度も)APIを呼び出すのであれば、API呼び出しをバッファリングし GS2-Showcase であれば Quantity、GS2-Exchange であれば Count の値を指定するようにすることで通信回数を削減で、チャタリングの発生を回避できます。

新機能への対応に関する注意事項

GS2 では定期的に新機能が追加されており、トランザクション処理も追加されています。
未知のトランザクション処理が含まれている場合は、投機的実行ができないトランザクションアクションと同様の警告が表示されるだけで動作自体には大きな問題は生じません。
しかし、積極的にSDKを最新に更新することで、最新の投機的実行処理にも対応でき、最適なUXを実現できます。
不具合の修正も頻繁に行われていますので、出来る限り最新のSDKを利用するようにしてください。

GS2-StateMachine で利用する GSL にスクリプトを直値で指定できるようになりました

はじめに

GS2-StateMachine はステートマシンをサーバーサイドで管理し、クライアントから届くメッセージを元に状態遷移することで
チート行為に対する耐性をたかめつつ、自由度の高いスクリプティングを実現する機能を提供しています。

機能追加の背景

GS2-StateMachine が管理するステートマシンは GS2 State Language(GSL) を使用して定義します。
新しいステートに遷移した際にスクリプトを実行して、ステートマシンが持つ状態変数を書き換えることができます。
従来はこのスクリプトの指定には GS2-Script が管理するスクリプトのIDを指定することで定義していました。

しかし、GSLとスクリプトの実体が分離してしまい、見通しが悪くなってしまう状況が発生していました。

機能追加の詳細

StateMachine MainStateMachine {
  Variables {
  }

  EntryPoint Task1;

  Task Task1(int initCounter) {
    Event StartLoop();
    Event Error(string Reason);

    Payload {
      result = {
          event="Pass",
          params={},
          updatedVariables=args.variables
      }
    }
  }

  PassTask Pass;

  ErrorTask Error(string reason);

  Transition Task1 handling Pass -> Pass;
}

こちらの例のように Task 内の Payload セクションで直接 Lua スクリプトを記述できるようになりました。
例では Task1 に遷移した際に以下のスクリプトを実行します。

      result = {
          event="Pass",
          params={},
          updatedVariables=args.variables
      }

スクリプトを実行した結果、Pass というメッセージを自身のステートマシンへ発します。

    Transition Task1 handling Pass -> Pass;

Task1 ステートにいる時に Pass というメッセージを受け取ると、Pass ステートに遷移します。
Pass ステートは PassTask なので、ステートマシンの終端を表すステートに遷移しステートマシンは起動直後に終了します。

(C) Game Server Services, Inc.