ARTICLES

リアルタイムリレーサーバーSquirrelayServerの紹介

By wraikny

オンラインゲーム作りたくないですか?

2020年のアドベントカレンダーにて LiteNetLibとMessagePackで行うリアルタイム通信 という記事を書いたのですが、ゲームロジックをサーバーで実行するのは実行コストも実装コストも高いですし、ゲームのたびに実装する必要があって大変ですよね。

今回はそれらを汎用的に扱いやすくした、メッセージリレーサーバーSquirrelayServerの紹介です。

サーバーサイドはビルドしたバイナリを起動するだけで利用できて、クライアントサイドの開発に集中できます。

リポジトリはこちら

wraikny/SquirrelayServer - GitHub

SquirrelayServer実装のポイント

冒頭でリンクした記事の「前提条件とか」を読むとわかると思うのですが、オンラインゲームでは同期を取るのがとても大変で、適当に実装しては、クライアント間で状態が異なってしまう同期ずれという現象が起こります。

SquirrelayServerでは楽に導入できる汎用性を重視したので、サーバーサイドでゲームステートは持ちません。

ではどのように同期ズレを解決するかというと、以下のような方法をとっています。

  • ルームがゲームプレイ状態になると、ルーム内時刻の計測を始める。
    • ゲームメッセージがサーバーに届くと、ルーム内時刻を記録する。
  • クライアントが送信したゲームメッセージは自身に送り返される。
  • つまりゲームメッセージは、ルーム内時刻と共に、その時刻で順序つけられて、全てのクライアントに送られる。
  • すべてのゲームステートへの処理をルーム内時刻に基づいて各クライアントで更新を行うことで、同期ズレが起きにくくなる!!

※「ゲームメッセージ」とは、各プレイヤーが入力する、ゲーム操作を表す型を思い浮かべてください。例えば「右に移動」とか、「銃を打つ」とか。

ただし、サーバーに届いた時間が正義という実装なので、通信環境が悪いクライアントは不利になります。 各クライアントのレイテンシを考慮したい場合は、それもゲームメッセージに含めると良いでしょう。

ここで注意点です。

サーバー内の時刻を利用してゲームの更新を行うということは、各クライアントでの経過時間を利用してはいけないということです。

つまり、ゲームステートは以下のような実装をする必要があります。

class GameState
{
    void Update(ulong clientId, float elapsedSec, Message msg) { }
}

Altseed2ではよくEngine.DeltaSecondsを利用しますが、ゲームステートの更新にはそれを利用せず、こちらのサーバー内時刻を利用してください。

サーバーを介して自身の更新も行うため、60FPSの場合レイテンシが17ms程度でも往復で2~3フレームほど遅延することになります。

クライアント側のより良い実装としては、見た目上はローカルの操作を反映して、サーバーからの応答があった際にそちらで実際のステートを更新する(つまり、見た目と本当の処理を分けて記述する)ことで遅延をごまかすことはできます。

主な機能

通信にRUDP(LiteNetLib)を利用して、以下のような機能を実装してあります。

  • サーバー接続中のクライアント数取得
  • ルーム機能
    • ルームリスト取得
    • ルームリストへの非公開設定
    • ルーム作成時のパスワード設定
    • 一定間隔でのTickメッセージ送信
    • ルームメッセージ(ルームリストで「だれでも歓迎」とか「ガチ勢募集」とか出したい)
    • プレイヤーステータス(ルーム内で名前とか設定したい)
    • メッセージブロードキャスト
  • async/await対応!

設定ファイルの記述や各メソッド等の使い方はGitHubのドキュメントに詳細があります。

SquirrelayServer ドキュメント

使い方

リポジトリを用意したので、参考にしてください。 wraikny/SquirrelayServer-example - GitHub

  1. SquirrelayServerをsubmoduleとして追加します。
mkdir lib
git submodule add https://github.com/wraikny/SquirrelayServer.git lib
git submodule update --init --recursive
  1. 設定ファイルを記述します。サーバーとクライアント共通でOKです。(クライアントではnetConfig部分のみ利用されます。)
cp lib/SquirrelayServer/src/SquirrelayServer/config/config.json .
  1. SquirrelayServerへのプロジェクト参照を追加します。
dotnet add src/ExampleClient reference lib/SquirrelayServer/src/SquirrelayServer
  1. ローカルデバッグ時には、サーバーはSquirrelayServer.Appを実行するだけです。
dotnet run --project lib/SquirrelayServer/src/SquirrelayServer.App config.json
  1. 以下、クライアントサイドの話です。

  2. Types.csのように型定義します。

  3. GameNode.csPlayerNode.csのようにゲームロジックを実装します。ここでは簡単に、マウスに追従する矩形を表示しています。

  4. ClientNode.csのように、Client<,,>クラスの管理や更新などを行います。ここでは例なので簡単に書いていますが、本来はルームUIを実装してそれを操作する想定です。

  5. Program.csのように、ClientNodeGameNodeを受け渡してあげます。

おわりに

わたしもまだ簡単なサンプルプログラムくらいしか組んでないので、ちゃんと遊べるもの作りたいですね~。

あとは、サーバーサイドに興味ある人とか、こういう機能欲しいとか、バグが有るとか、issueからでもcommitお待ちしてます!

SHARE THIS POST