このセクションではUnityのネットワーキングアーキテクチャでゲーム開発をするうえで理解すべき一般的なネットワーキングの概念についてカバーします。
ネットワーキングは2つ以上のコンピュータ間の通信です。基本的な考え方はクライアント( Client )(情報をリクエストするコンピュータ)とサーバ( Server )(情報のリクエストに応答するコンピュータ)との関係です。サーバは全てのクライアントから使用されるホストマシンか,単にプレイヤーがゲームを実行しているマシン(クライアント)がさらに他のプレイヤーのサーバの役割を果たす場合の両方があります。サーバーを起動しクライアント接続が行われると,二つのコンピュータはゲームプレイに応じてデータのやりとりを行うことが出来ます。
ネットワークゲームを作成するには非常に具体的な詳細事項を気にする必要があります。ネットワークのアクションをUnityで設計し作成することは容易ですが,それでも複雑さはあります。Unityの重要な設計方針としてネットワーキングについて出来るかぎり堅牢かつ柔軟性を持たせる,ということがあります。すなわちゲームクリエイターのあなたが責任をもたないといけないこととして,他のゲームエンジンでは自動化されているものの堅牢性が劣る事項も含まれる,ということです。あなたの判断はゲームデザインに大きな影響があり,設計段階の早い時点で決定することが大事です。ネットワーキングの概念を理解することでゲームデザインの設計が改善され実装で発生する問題を回避できます。
ネットワークゲームの構築にあたり良く知られていて,かつ検証も十分に行われている2つのアプローチがあり,権限サーバ( Authoritative Server )と非権限サーバ( Non-Authoritative Server )方式と呼ばれます。両方のアプローチはサーバのクライアント接続の確立と相互のデータ受け渡しを前提としています。両方でエンドユーザのプライバシーが確保され,クライアント同士が直接接続されることはなく,またIPアドレスも他のクライアントに公開されません。
権限サーバのアプローチはゲームの世界のシミュレーションがサーバで行われることを前提としていて,ゲームルールの適用やプレイヤークライアントからの入力処理も行います。各クライアントは入力の送信(キー入力やアクションのリクエストといった形で)をサーバに対して行い,継続的にゲームの現在の状態をサーバから取得します。クライアントはゲームの様態に直接変更を反映することは行わず,その代わりにサーバにやりたいことのリクエストを行い,サーバがリクエストをハンドリングして暗いなとに結果として何が起こったかを応答します。
基本的に,プレイヤーが何をしたかと,実際に起きることの間でレイヤーの分離があります。これによりサーバが全てのクライアントのリクエストを聞いてからゲームの状態を更新すればよいか判断することが出来ます。
このアプローチのメリットはクライアントがチート(不正)することを相当に難しくすることです。例えば,クライアントがサーバに敵を殺したことを伝えてチートすることは出来ず,その判断を自ら行うことが出来ません。サーバに対して武器を発射したことを伝え,その先で殺せたかをサーバが判断します。
権限サーバのもう一つの例は,物理挙動に依存したマルチプレイヤーのゲームです。もし各クライアントがそれぞれの物理シミュレーションにもとづいて実行されると,クライアント同士でわずかな差異が徐々に同期がとれなくなります。もしシミュレーションが中央サーバでハンドリングされれば整合性が保証されます。
権限サーバの潜在的な欠点はネットワーク上でメッセージが伝達されるのに時間がかかることです。もしプレーヤーがあるコントロールを前後に動かす場合に,ネットワーク上のサーバからの応答が0.1秒かかるとしたら,遅延の発生がプレイヤーに認識されてしまいます。一つの解決策はクライアント側予測(Client-Side Prediction)を行うことです。このテクニックの本質はクライアントがゲームをローカルのバージョンとして実行しつつ,サーバからサーバが認証したバージョンを更新情報として受け取ることです。通常は,シンプルなゲームアクションでのみ使用されるべきであり,ゲームの状態にとって重要な情報の更新では使用してはいけません。例えば,プレイヤーに敵が殺された情報を報告して,その後にサーバーがこの判断を覆すのは賢くありません。
クライアント側予測は高度なトピックなので,このガイドでカバーしませんが,さらに詳細を調べるならば本やウェブ上のリソースがあります。
権限サーバは非権限サーバに比べて処理オーバーヘッドが高くなります。サーバが全ての変更をゲームの状態に対する変更をハンドリングすることが必要な場合,この処理負荷はクライアントで分散させることが出来ます。
非権限サーバはユーザ入力の結果を制御しません。クライアント自身がユーザ入力およびゲームロジックをローカルで処理して決まったアクションをサーバに送信します。サーバは全てのアクションをゲームの世界の状態と同期します。これにより,設計の観点から実装しやすく,サーバは単にクライアント間のメッセージリレーを行うのみであり,クライアントが行う以上の追加の処理がありません。
予測( Prediction )手法の類は必要とされず,クライアントは全ての物理挙動やイベントのハンドリングを行い,発生した内容をサーバにリレーします。オブジェクトに対するオーナーとなり,ネットワーク上でそれらのオブジェクトに対する修正はそのエージェントのみが行うものと限定されます。
ネットワークゲームの基本アーキテクチャがカバーできたところで,下位概念においてクライアントやサーバが互いに通信する方法を見ていきます。
二つの関連した手法があり,リモートプロシージャコール(Remote Procedure Callsと状態同期(State Synchronization)と呼ばれます。どのようなゲームのどのようなタイミングで使用するのも一般的なことです。
リモートプロシージャコール(RPC)はネットワーク上で他のコンピュータ上で関数の実行するために使用され,ここで「ネットワーク」はクライアントとサーバが同じコンピュータ上で実行されている場合にはそれらの間のメッセージチャネルを意味します。クライアントはサーバにRPCを送信することが出来,サーバはRPCをひとつ以上のクライアントに送信出来ます。もっとも一般的な使用方法としては,頻繁でないアクションに使用されます。例えばクライアントがドアをあけるためのスイッチを入れた場合,サーバにRPCを送信してドアが開けられたことを知らせます。サーバは別のRPCを全てのクライアントに送信して,それぞれのローカル関数を実行し同じドアを開けるようにします。これらは個別のイベントの管理と実行に使用します。
状態同期は継続的に変更がかかるデータを共有するために使用します。もっとも良い例としてはアクションゲームにおけるプレイヤー位置です。プレイヤーは継続的に移動,走り回り,ジャンプしている,等の場合です。ネットワーク上の他の全てのプレイヤー,すなわちローカルでプレイヤー自身をコントロールしている場合も含めて,どこに位置しているかおよび何をしているか知る必要があります。このプレイヤー位置のデータを継続的にリレーすることで,ゲームは正確にその位置を他のプレイヤーに提示することが出来ます。
この種のデータはネットワーク上を定期的かつ頻繁に送信されます。このデータは時間依存し,ひとつのマシンから次のマシンに渡るまで時間がかかるため,出来るかぎり送信データの量を削減することは重要です。もっと簡単にいうと,状態同期は自然と多くの帯域幅を必要とするため,できるかぎり最小の帯域幅を使用するようにすべきです。
サーバとクライアントを接続することは複雑なプロセスとなりえます。マシンはプライベート,パブリックIPアドレスを持ち,ローカルや外部ファイアウォールがアクセスをブロックします。Unityネットワーキングはできるかぎり多くのシチュエーションをハンドリングしますが,絶対的な解決策はありません。
プライベートアドレスはインターネットから直接アクセスすることができないIPアドレス(ネットワークアドレス変換(Network Address Translation),あるいは使用する手法からNATアドレスと呼ばれます)です。簡単に説明すると,プライベートアドレスはローカルルータを通過し,そこでアドレスがパブリックアドレスに変換されます。これにより,多くのプライベートアドレスを持ったマシンはひとつのパブリックアドレスを使用してインターネットで通信できます。これは誰かインターネット上で他のプライベートアドレスと通信を始めたい場合でなければ何ら問題はありません。この通信はルータのパブリックアドレスを通して行われる必要があり,次にプライベートアドレスにメッセージを渡す必要があります。NATパンチスルーというテクニックを使って,ファシリテータ( Facilitator )として知られる共有サーバを仲介としてプライベートアドレスをパブリックアドレスから到達できるようにします。プライベートアドレスがまずファシリテータにコンタクトを行い,そこからローカルルータに穴を空けることで実現されます。(NATパンチスルーの詳細は現実にはより複雑なものであることに留意して下さい)
パブリックアドレスはより直感的なものです。ここでの主要な課題は接続が内部,あるいは外部ファイアウォールによりブロックされることです(内部ファイアフォールは守ろうとしているコンピュータでローカル上実行されています)。内部ファイアウォールにおいては,ユーザは特定のポートを開放し,ゲームサーバをアクセス可能にするよう求められます。外部ファイアウォールは,対照的にユーザの管理下にありません。UnityはNATパンチスルーを使用して外部ファイアウォールを通してアクセスを試みることは出来ますが,成功は保証されません。われわれが行ってきたテスト結果によれば,実際に一般的に機能することは示されているものの,正式な研究結果でこの発見が正しいと確証できるものがありません。
すでに述べた接続の問題はサーバとクライアントに対して各々違って影響します。クライアントリクエストは発信のトラフィックのみであるため,比較的直感的です。もしクライアントがパブリックアドレスを持つ場合,ほぼ常に成功し,なぜかというと発信のトラフィックまでブロックするのは一般に企業ネットワークなど極めて厳しいアクセス制限をかけているものに限られるためです。もしクライアントがプライベートアドレスを持つ場合,NATパンチスルーができないプライベートアドレスを持つサーバ以外の全てのサーバと接続することが出来ます。(この詳細については後述します)。サーバ側はもっと複雑であり,なぜかというとサーバは未知の発信元から受信を行う必要があるためです。パブリックアドレスでは,サーバはインターネット向けにゲームポートを開放されている必要があり(すなわちファイアウォールでブロックされない),そうでなければクライアントから通信を受け付けることができず使用することが出来ません。もしサーバがプライベートアドレスをもつ場合,NATパンチスルーを使用して接続を許可し,クライアントもまたNATパンチスルーを許可していないと接続ができない。
Unityはこれらの異なる接続状況をテストするツールを提供しています。接続が確立できることが確認できるとするならば,2つの手法があり,直接接続(クライアントがサーバのDNS名やIPアドレスを知る必要がある)かマスターサーバー(Master Server)を通して通信です。マスターサーバにより,特定のゲームサーバーの情報についてあらかじめ知る必要がないクライアントに対して,サーバが存在を示すことが出来ます。
複数のクライアント間で状態同期を行う場合,必ずしも全ての詳細情報がないと,オブジェクトが同期させられないわけではりません。例えば,キャラクターのアバターを同期させる場合はクライアント間を送信するのは位置と回転の情報で十分です。キャラクター自身がもっと複雑で,深い Transform 階層を持っていたとしても,階層全体に関する情報を共有する必要性はありません。
ゲームにおけるたくさんの情報は十分に静的であり,クライアントはそれを送信も同期もする必要がありません。機能の多くは,頻繁でない,あるいはワンタイムのRPCコールだけで十分実現できます。どのゲームのインストールでも存在するデータについて有効に活用し,クライアントはできるかぎり自前で処理できるようにします。例えば,テクスチャやメッシュといったアセットは全てのインストールで存在することが分かっていて変更も通常はされないため,同期を取る必要がありません。これは簡単な例ですが,クライアント間で共有されるべき,まさにクリティカルなデータが何であるか考えるには十分です。そういうデータのみを共有すべきです。
ネットワークゲームを初めて作成する場合は特に,具体的に何を共有すべきで,何を共有すべきでないかを判断することは難しいかもしれません。レベル名をつけた一度のRPCコールで,全てのクライアントが指定されたレベル全体をロードすることが出来て,自動的にネットワーク要素を付け加えることが出来ることに留意して下さい。ゲームにおいて,クライアントができる限り自己完結できるように構成することで,帯域幅は最小化できます。
サーバ自身の物理的な場所とパフォーマンスは,その上で実行されるゲームのプレイ品質に大きく影響します。サーバと別大陸にいるクライアントは大きな遅延を経験するかもしれません。これはインターネットの物理的制約であり,実際の解決方法としてはサーバと使用するクライアントができるかぎり近くに位置することであり,せめて同じ大陸上にあることです。
ネットワーキングについて,学習できる追加のリソースを集めました: