[GS2+Unity]GS2-Realtimeを使ったリアルタイム PvP ゲームの実装
対象読者
- Unity でのゲーム開発経験がある方
- GS2 (Game Server Services) を使ったオンラインゲーム開発に興味がある方
- リアルタイム通信を実装したい方
完成イメージ
- 2 人のプレイヤーによるリアルタイム対戦
- バーを操作しボールを打ち返す。相手の陣地にボールが入れば得点
参考ページ
- https://docs.gs2.io/ja/microservices/realtime/
- https://docs.gs2.io/ja/api_reference/realtime/game_engine/
作業環境
Unity: 6000.0.34f1
GS2 SDK for Unity: 2025.2.5
UniTask: 2.5.10
GS2-SDK のインポート
Unity プロジェクトを作成したら、 GS2のサービスを利用するために GS2-SDKをインポートします。 SDKのセットアップ | Game Server Services | Docs の手順に従います。
- UnityPackege 形式のインストーラー をダウンロードしインポートします
- Unity Editor のメニューから「Window > Game Server Services > SDK Installer」を選択します
- SDK インストーラーのウィンドウが表示されるので「Install」を押下します
- インストールが完了すると、プロジェクトの「Packeges」に SDK がインストールされます。 Package Manager でインストールされていることが確認できます。
UniTask のインポート
非同期処理を簡潔に書くために、UniTaskをインポートします。
- UniTask のリリースページ から最新版をダウンロード
- ダウンロードした
.unitypackage
ファイルをダブルクリックしてインポート - すべての項目にチェックを入れて「Import」をクリック
GS2_ENABLE_UNITASK
の追加
スクリプティング定義シンボル GS2-SDK で UniTask を使用するために、スクリプティング定義シンボルを追加します。
- Edit > Project Settings > Player を開く
- Other Settings > Scripting Define Symbols に
GS2_ENABLE_UNITASK
を追加
GS2 マネジメントコンソールでの設定
GS2 アカウントの作成
ゲームで GS2 の機能を使えるようにするために、開発者が自分のアカウントを作成します。
- GS2 マネジメントコンソール にアクセス
- アカウントを持っていない場合は新規登録
料金について
プラン名称 | 対象 | 価格 |
---|---|---|
Individual | 過去12か月間の売上高が1000万円未満の法人または個人 | 無料 |
Professional | Individual プランに該当しない法人または個人 | 利用料金に準ずる |
Enterprise | Individual プランに該当しない法人または個人 | ASK |
※ 料金について 詳しくはこちら
GS2-Account の設定
ゲームのプレイヤーが匿名アカウントを作成できるようにするために必要な設定です。 GS2-Realtime のルームに接続する際に使用します。
- GS2 マネジメントコンソールで「Account」サービスを選択
- 「ネームスペース作成」をクリック
- 名前を「SamplePingPong」などに設定して作成
GS2-Realtime の設定
リアルタイム通信を行えるようにするための設定を行います。
ネームスペース作成
- GS2 マネジメントコンソールで「Realtime」サービスを選択
- 「ネームスペース作成」をクリック
- 名前を「SamplePingPong」などに設定して作成
サービスごとの追加料金について
GS2-Realtime の料金
スペック | 単位 | 価格 |
---|---|---|
realtime1.nano | 1分 | 0.04円 |
realtime1.micro | 1分 | 0.06円 |
realtime1.small | 1分 | 0.12円 |
realtime1.medium | 1分 | 0.24円 |
realtime1.large | 1分 | 0.5円 |
※ サービスごとの追加料金について 詳しくはこちら
ルーム作成
- 作成したネームスペースを選択し、「ルーム作成」をクリック
- 名前を「SampleRoom」などに設定して作成
Unity エディタでの実装
プロジェクト構造
ゲームの実装に必要なクラスは主に以下の2つです:
-
Gs2Manager: GS2 との通信を担当するクラス
- ユーザー作成、認証、ルーム接続などを処理
- メッセージの送受信を管理
- 接続状態の監視と再接続処理
-
GameManager: ゲームロジックを担当するクラス
- プレイヤーの入力処理
- ボールの物理演算と衝突判定
- スコア管理
- UI 表示の更新
シーンの編集
解像度設定
Project Settings > Player > Resolution and Presentation の内容を下記に変更します。
設定項目 | 値 |
---|---|
Resolution | |
Run In Background | true |
Fullscreen Mode | Windowed |
Default Screen Width | 400 |
Default Screen Height | 800 |
Standalone Player Options | |
Resizable Window | false |
Visible In Background | true |
Allow Fullscreen Switch | false |
Force Single Instance (*1 | false |
*1: 検証のために 2 つのインスタンスを同時に建てる場合があることから false としています。
Gs2Manager
空の GameObject を作成し、「Gs2Manager」と命名します。 Gs2Manager.cs
スクリプトを作成し、これにアタッチします。
Gs2Manager.cs 全文
using System;
using UnityEngine;
using Cysharp.Threading.Tasks;
using RelayRealtimeSession = Gs2.Unity.Gs2Realtime.RelayRealtimeSession;
using Google.Protobuf;
using Gs2Client = Gs2.Unity.Core.Gs2Domain;
using Gs2.Unity.Util;
public class Gs2Manager : MonoBehaviour
{
/// <summary>
/// インスタンス
/// </summary>
public static Gs2Manager Instance { get; private set; }
/// <summary>
/// メッセージ受信イベント
/// </summary>
public event Func<string, UniTask> OnMessageReceived;
/// <summary>
/// セッションが接続中かどうか
/// </summary>
public bool IsConnected { get; private set; }
// GS2の認証情報
private const string ClientId = "****"; // GS2 マネジメントコンソールで Home を選択し少し待つと表示されます
private const string ClientSecret = "****"; // GS2 マネジメントコンソールで Home を選択し少し待つと表示されます
// 名前空間
private const string AccountNamespaceName = "SamplePingPong"; // 先の手順で設定した名前
private const string RealtimeNameSpaceName = "SamplePingPong"; // 先の手順で設定した名前
// ルーム名
private const string RoomName = "SampleRoom"; // 先の手順で設定した名前
// GS2のクライアント
private Gs2Client _gs2;
// ユーザー情報
private string _userId;
private string _password;
private string _accessToken;
// ルーム情報
private string _ipAddress;
private int _port;
private string _encryptionKey;
// GS2Realtime のセッション
private RelayRealtimeSession _session;
/// <summary>
/// メッセージ送信
/// </summary>
/// <param name="message">メッセージ</param>
public async UniTask Send(string message)
{
if (IsConnected == false)
{
return;
}
try
{
await _session.SendAsync(
ByteString.CopyFromUtf8(message)
);
}
catch (Exception e)
{
Debug.LogError($"Failed to send message to GS2 room: {e.Message}");
}
}
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
private async void Start()
{
await Init();
}
private void OnApplicationQuit()
{
Term();
}
// 初期化
private async UniTask Init()
{
if (IsConnected)
{
return;
}
try
{
await CreateGs2();
await CreateUser();
await Login();
await Connect();
}
catch (Exception e)
{
Debug.LogError($"Failed to initialize GS2: {e.Message}");
}
}
// 終了
private void Term()
{
if (IsConnected == false && _session == null)
{
return;
}
try
{
Disconnect();
}
catch (Exception e)
{
Debug.LogError($"Failed to terminate GS2: {e.Message}");
}
}
// GS2 のクライアントを作成
private async UniTask CreateGs2()
{
_gs2 = await Gs2.Unity.Core.Gs2Client.CreateAsync(
new Gs2.Core.Model.BasicGs2Credential(
ClientId,
ClientSecret
)
);
Debug.Log("Created GS2");
}
// GS2 の匿名ユーザーを作成
private async UniTask CreateUser()
{
try
{
var result = await _gs2.Account.Namespace(
namespaceName: AccountNamespaceName
).CreateAsync();
var item = await result.ModelAsync();
_userId = item.UserId;
_password = item.Password;
Debug.Log("Created GS2 user");
}
catch (Exception e)
{
Debug.LogError($"Failed to create GS2 user: {e.Message}");
}
}
// GS2 にログイン
private async UniTask Login()
{
var gameSession = await _gs2.LoginAsync(
new Gs2AccountAuthenticator(
accountSetting: new AccountSetting
{
accountNamespaceName = AccountNamespaceName,
}
),
_userId,
_password
);
// アクセストークンは GS2-Realtime セッションを作成する際に使用する
_accessToken = gameSession.AccessToken.Token;
Debug.Log("Logged in GS2");
}
// GS2-Realtime に接続
private async UniTask Connect()
{
try
{
// ルーム情報の取得
var room = _gs2.Realtime.Namespace(
namespaceName: RealtimeNameSpaceName
).Room(
roomName: RoomName
);
var itemRoom = await room.ModelAsync();
_ipAddress = itemRoom.IpAddress;
_port = itemRoom.Port;
_encryptionKey = itemRoom.EncryptionKey;
// GS2Realtime のセッションを作成
_session = new RelayRealtimeSession(
_accessToken, // アクセストークン
_ipAddress, // ゲームサーバのIPアドレス
_port, // ゲームサーバのポート
_encryptionKey, // 暗号鍵
ByteString.CopyFromUtf8(_userId) // プロフィールの初期値
);
// 接続
await _session.ConnectAsync(this);
// メッセージ受信イベントハンドラを設定
_session.OnRelayMessage += message =>
{
OnMessageReceived?.Invoke(message.Data.ToStringUtf8()).Forget();
};
IsConnected = true;
Debug.Log("Connected to GS2 room");
}
catch (Exception e)
{
Debug.LogError($"Failed to connect to GS2 room: {e.Message}");
}
}
// GS2-Realtime から切断
private void Disconnect()
{
try
{
if (IsConnected || _session != null)
{
// GS2Realtime のセッションを切断
_session.Dispose();
_session = null;
IsConnected = false;
Debug.Log("Disconnected from GS2 room");
}
}
catch (Exception e)
{
Debug.LogError($"Failed to disconnect from GS2 room: {e.Message}");
}
}
}
GameManager.cs
空の GameObject を作成し、「GameManager」と命名します。 GameManager.cs スクリプトを作成し、これにアタッチします。
GameManager.cs 全文
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;
using TMPro;
public class GameManager : MonoBehaviour
{
// Gs2Manager
[SerializeField] private Gs2Manager _gs2Manager;
// ゲームオブジェクト
[SerializeField] private GameObject _playerBar;
[SerializeField] private GameObject _opponentBar;
[SerializeField] private GameObject _ball;
[SerializeField] private TextMeshProUGUI _scoreText;
[SerializeField] private TextMeshProUGUI _statusText;
// ゲーム設定
private const float BallSpeed = 5f;
private const float PlayerBarSpeed = 10f;
private const float BarWidth = 2f;
private const float BarHeight = 0.5f;
private const float BallRadius = 0.25f;
private const float ScreenWidth = 2f;
private const float ScreenHeight = 4f;
// ゲーム状態
private Vector2 _ballPosition;
private Vector2 _ballDirection;
private float _playerBarX;
private float _opponentBarX;
private int _playerScore;
private int _opponentScore;
private bool _isHost;
private bool _isOpponentConnected;
private float _waitingTimer;
private const float PingInterval = 2.0f; // 対戦相手の確認間隔(秒)
// ゲームの状態
private enum GameState
{
WaitingForOpponent,
Playing,
GameOver
}
private GameState _currentState;
// JSON用のクラス
[Serializable]
private class GameMessage
{
public float playerBarX;
public float ballPosX;
public float ballPosY;
public float ballDirX;
public float ballDirY;
public int playerScore;
public int opponentScore;
public bool resetBall;
public string status;
}
private void Awake()
{
// Gs2Managerのイベントにメッセージ処理ハンドラを登録
if (_gs2Manager != null)
{
_gs2Manager.OnMessageReceived += OnMessageReceived;
}
}
private void OnDestroy()
{
// イベント登録解除
if (_gs2Manager != null)
{
_gs2Manager.OnMessageReceived -= OnMessageReceived;
}
}
private void Start()
{
// ゲーム状態の初期化
_currentState = GameState.WaitingForOpponent;
_isOpponentConnected = false;
_waitingTimer = 0f;
// 初期化
InitializeGameObjects();
// 接続状態を表示
UpdateStatusText();
// 定期的に接続確認メッセージを送信
SendPingMessage().Forget();
}
private void Update()
{
switch (_currentState)
{
case GameState.WaitingForOpponent:
// 対戦相手を待っている状態
_waitingTimer += Time.deltaTime;
if (_waitingTimer >= 1.0f)
{
_waitingTimer = 0f;
UpdateStatusText();
}
// 対戦相手が接続したらゲーム開始
if (_isOpponentConnected)
{
StartGame();
}
break;
case GameState.Playing:
if (!_gs2Manager.IsConnected)
{
// 接続が切れた場合
_currentState = GameState.WaitingForOpponent;
_isOpponentConnected = false;
UpdateStatusText();
return;
}
// プレイヤーバーの移動
HandlePlayerInput();
// ホストの場合はボールの動きを計算
if (_isHost)
{
UpdateBallPosition();
CheckCollisions();
SendGameState(false);
}
else
{
// クライアントは自分のバー位置だけ送信
SendPlayerPosition();
}
// ゲームオブジェクトの位置を更新
UpdateGameObjects();
break;
case GameState.GameOver:
// ゲーム終了状態
break;
}
}
private void InitializeGameObjects()
{
// ゲームオブジェクトの初期位置を設定
_playerBarX = 0f;
_opponentBarX = 0f;
_playerBar.transform.position = new Vector3(0f, -ScreenHeight + BarHeight, 0f);
_opponentBar.transform.position = new Vector3(0f, ScreenHeight - BarHeight, 0f);
_ball.transform.position = Vector3.zero;
// ボールを非表示にする(ゲーム開始前)
_ball.SetActive(false);
// スコア表示を初期化
_playerScore = 0;
_opponentScore = 0;
UpdateScoreText();
}
private void StartGame()
{
// ゲーム状態の初期化
_ballPosition = Vector2.zero;
_ballDirection = new Vector2(1f, 0.5f).normalized;
_playerScore = 0;
_opponentScore = 0;
// ホストかどうかを決定(最初の接続者がホスト)
// 実際の実装では接続順などで決定
_isHost = DetermineIfHost();
// ボールを表示
_ball.SetActive(true);
// スコア表示を更新
UpdateScoreText();
// ゲーム状態を更新
_currentState = GameState.Playing;
// ステータス表示を更新
_statusText.text = _isHost ? "You are a host" : "You are a client";
// ホストの場合は初期ゲーム状態を送信
if (_isHost)
{
SendGameState(true);
}
}
// ホストかどうかを決定するロジック
private bool DetermineIfHost()
{
// 実際の実装では、ユーザーIDの比較や接続順などで決定
// ここでは簡易的に実装
return true; // 常にホストとして扱う場合
}
private void UpdateStatusText()
{
if (_currentState == GameState.WaitingForOpponent)
{
// 待機中のアニメーションテキスト
int dots = (int)(_waitingTimer * 2) % 4;
string dotsText = new string('.', dots);
_statusText.text = $"Waiting for opponent{dotsText}";
}
}
// 定期的に接続確認メッセージを送信
private async UniTaskVoid SendPingMessage()
{
while (_currentState == GameState.WaitingForOpponent)
{
GameMessage pingMessage = new GameMessage
{
status = "ping"
};
string message = JsonUtility.ToJson(pingMessage);
await SendMsg(message);
// 一定時間待機
await UniTask.Delay(TimeSpan.FromSeconds(PingInterval));
}
}
private void HandlePlayerInput()
{
// 水平方向の入力を取得
float horizontalInput = Input.GetAxis("Horizontal");
// プレイヤーバーの位置を更新
_playerBarX = Mathf.Clamp(_playerBarX + horizontalInput * PlayerBarSpeed * Time.deltaTime, -ScreenWidth + BarWidth/2, ScreenWidth - BarWidth/2);
}
private void UpdateBallPosition()
{
// ボールの位置を更新
_ballPosition += _ballDirection * (BallSpeed * Time.deltaTime);
}
private void CheckCollisions()
{
// 左右の壁との衝突
if (_ballPosition.x is < -ScreenWidth + BallRadius or > ScreenWidth - BallRadius)
{
_ballDirection.x = -_ballDirection.x;
}
// プレイヤーバーとの衝突
if (_ballPosition.y < -ScreenHeight + BarHeight + BallRadius)
{
if (Mathf.Abs(_ballPosition.x - _playerBarX) < BarWidth/2)
{
// バーに当たった場合は跳ね返る
_ballDirection.y = Mathf.Abs(_ballDirection.y);
// バーのどの位置に当たったかで反射角を変える
float hitFactor = (_ballPosition.x - _playerBarX) / (BarWidth/2);
_ballDirection.x = hitFactor * 0.75f;
_ballDirection = _ballDirection.normalized;
}
else if (_ballPosition.y < -ScreenHeight)
{
// 画面下端を超えた場合は相手の得点
_opponentScore++;
UpdateScoreText();
CheckGameOver();
ResetBall();
}
}
// 相手バーとの衝突
if (_ballPosition.y > ScreenHeight - BarHeight - BallRadius)
{
if (Mathf.Abs(_ballPosition.x - _opponentBarX) < BarWidth/2)
{
// バーに当たった場合は跳ね返る
_ballDirection.y = -Mathf.Abs(_ballDirection.y);
// バーのどの位置に当たったかで反射角を変える
float hitFactor = (_ballPosition.x - _opponentBarX) / (BarWidth/2);
_ballDirection.x = hitFactor * 0.75f;
_ballDirection = _ballDirection.normalized;
}
else if (_ballPosition.y > ScreenHeight)
{
// 画面上端を超えた場合は自分の得点
_playerScore++;
UpdateScoreText();
CheckGameOver();
ResetBall();
}
}
}
private void ResetBall()
{
// ボールの位置をリセット
_ballPosition = Vector2.zero;
// ランダムな方向を設定
float angle = UnityEngine.Random.Range(-45f, 45f);
_ballDirection = new Vector2(
_isHost ? Mathf.Cos(angle * Mathf.Deg2Rad) : -Mathf.Cos(angle * Mathf.Deg2Rad),
_isHost ? Mathf.Sin(angle * Mathf.Deg2Rad) : -Mathf.Sin(angle * Mathf.Deg2Rad)
).normalized;
// ゲーム状態を送信(ボールリセットフラグをtrueに)
SendGameState(true);
}
private void CheckGameOver()
{
if (_playerScore >= 3 || _opponentScore >= 3)
{
bool isWin = _playerScore >= 3;
GameOver(isWin);
}
}
private void GameOver(bool isWin)
{
_currentState = GameState.GameOver;
_statusText.text = isWin ? "Win!" : "Lose...";
// 3秒後に再開
UniTask.Delay(3000).ContinueWith(() => {
InitializeGameObjects();
_currentState = GameState.WaitingForOpponent;
_waitingTimer = 0f;
UpdateStatusText();
}).Forget();
}
private void UpdateGameObjects()
{
// ゲームオブジェクトの位置を更新
_playerBar.transform.position = new Vector3(_playerBarX, -ScreenHeight + BarHeight, 0f);
_opponentBar.transform.position = new Vector3(_opponentBarX, ScreenHeight - BarHeight, 0f);
_ball.transform.position = new Vector3(_ballPosition.x, _ballPosition.y, 0f);
}
private void UpdateScoreText()
{
_scoreText.text = $"Score: {_playerScore} - {_opponentScore}";
}
private void SendPlayerPosition()
{
GameMessage state = new GameMessage
{
playerBarX = _playerBarX,
status = "position"
};
string message = JsonUtility.ToJson(state);
SendMsg(message).Forget();
}
private void SendGameState(bool resetBall)
{
GameMessage state = new GameMessage
{
playerBarX = _playerBarX,
ballPosX = _ballPosition.x,
ballPosY = _ballPosition.y,
ballDirX = _ballDirection.x,
ballDirY = _ballDirection.y,
playerScore = _playerScore,
opponentScore = _opponentScore,
resetBall = resetBall,
status = "gameState"
};
string message = JsonUtility.ToJson(state);
SendMsg(message).Forget();
}
private async UniTask SendMsg(string message)
{
try
{
// Gs2Managerの公開メソッドを呼び出す
await _gs2Manager.Send(message);
}
catch (Exception e)
{
Debug.LogError($"Failed to send message: {e.Message}");
}
}
// Gs2Managerからのメッセージ受信ハンドラ
private async UniTask OnMessageReceived(string message)
{
try
{
// JSONメッセージをパース
GameMessage state = JsonUtility.FromJson<GameMessage>(message);
if (state == null) return;
switch (state.status)
{
case "ping":
// 接続確認メッセージを受信した場合、応答を返す
GameMessage pongMessage = new GameMessage
{
status = "pong"
};
await SendMsg(JsonUtility.ToJson(pongMessage));
// 対戦相手が接続していることを確認
if (!_isOpponentConnected)
{
_isOpponentConnected = true;
// 待機中だった場合はゲーム開始
if (_currentState == GameState.WaitingForOpponent)
{
StartGame();
}
}
break;
case "pong":
// 接続確認応答を受信した場合
if (!_isOpponentConnected)
{
_isOpponentConnected = true;
// 待機中だった場合はゲーム開始
if (_currentState == GameState.WaitingForOpponent)
{
StartGame();
}
}
break;
case "position":
// 相手のバー位置を更新
_opponentBarX = state.playerBarX;
// 対戦相手が接続していることを確認
if (!_isOpponentConnected)
{
_isOpponentConnected = true;
// 待機中だった場合はゲーム開始
if (_currentState == GameState.WaitingForOpponent)
{
StartGame();
}
}
break;
case "gameState":
// 相手のバー位置を更新
_opponentBarX = state.playerBarX;
// 対戦相手が接続していることを確認
if (!_isOpponentConnected)
{
_isOpponentConnected = true;
// 待機中だった場合はゲーム開始
if (_currentState == GameState.WaitingForOpponent)
{
StartGame();
}
}
// ホストでない場合はボールの状態を更新
if (!_isHost && _currentState == GameState.Playing)
{
_ballPosition = new Vector2(state.ballPosX, state.ballPosY);
_ballDirection = new Vector2(state.ballDirX, state.ballDirY);
// スコアの更新(ホストとクライアントで反転)
_playerScore = state.opponentScore;
_opponentScore = state.playerScore;
UpdateScoreText();
// ゲーム終了判定
if (_playerScore >= 3 || _opponentScore >= 3)
{
bool isWin = _playerScore >= 3;
GameOver(isWin);
}
// ボールのリセット
if (state.resetBall)
{
// ボールの位置を更新
_ball.transform.position = new Vector3(_ballPosition.x, _ballPosition.y, 0f);
}
}
break;
case "disconnect":
// 相手が切断した場合
_isOpponentConnected = false;
if (_currentState == GameState.Playing)
{
// ゲーム中だった場合は待機状態に戻る
_currentState = GameState.WaitingForOpponent;
_waitingTimer = 0f;
_ball.SetActive(false);
UpdateStatusText();
// 再度接続確認メッセージの送信を開始
SendPingMessage().Forget();
}
break;
}
}
catch (Exception e)
{
Debug.LogError($"Failed to process message: {e.Message}");
}
}
}
各オブジェクトの作成
プレイヤーバー
- 2Dオブジェクト > Sprite > Square を作成し、「PlayerBar」と命名
- 位置を(0, -4, 0)に設定
- スケールを(2, 0.2, 1)に設定
相手バー
- 2Dオブジェクト > Sprite > Square を作成し、「OpponentBar」と命名
- 位置を(0, 4, 0)に設定
- スケールを(2, 0.2, 1)に設定
- 色を赤色に変更
ボール
- 2Dオブジェクト > Sprite > Circle を作成し、「Ball」と命名
- 位置を(0, 0, 0)に設定
- スケールを(0.2, 0.2, 1)に設定
UI要素
- Canvas を作成
- TextMeshPro - Text を2つ追加し、「ScoreText」と「StatusText」と命名
GameManager へアタッチ
GameManager のインスペクタを開き、オブジェクトをアタッチします。
動作確認
- プロジェクトをビルドします
- ビルドしたアプリを実行し「Waiting for opponent...」と表示されるのを確認します
- さらにもう一つアプリを実行して、ゲームが開始されることを確認します
- それぞれのゲームウインドウでプレイヤーバーを操作し、同期されることを確認します
まとめ
GS2-Realtime を使用して、リアルタイム対戦ピンポンゲームを実装しました。 GS2-Realtime を使うことで、サーバーサイドの複雑な実装なしに、リアルタイム通信機能を持つゲームを簡単に作成できることが分かりました。
実際のゲーム開発では、より複雑な状態同期や、遅延対策、チート対策なども考慮する必要がありますが、 GS2-Realtime ではそれらの基盤となる機能を提供することが可能です。
発展課題
- マッチメイキング機能の追加(GS2-Matchmaking を使用)
- ランキング機能の実装(GS2-Ranking2 を使用)
- 複数のルームを作成して、友達と特定のルームで対戦できるようにする
- リアクションボタンによるユーザー間コミュニケーション機能の追加
GS2-Realtime を使えば、様々なリアルタイム対戦ゲームを簡単に実装できます。ぜひ挑戦してみてください!