(Unity 5.1 からは UNET の使用を推奨しています。下記の情報は旧ネットワークシステムのものです。)
このセクションでは Unity のネットワーキングアーキテクチャでゲーム開発をするうえで理解すべき一般的なネットワーキングの概念についてカバーします。
ネットワーキングは 2 つ以上のコンピューター間の通信です。基本的な考え方はクライアント( Client )(情報をリクエストするコンピューター)とサーバー( Server )(情報のリクエストに応答するコンピューター)との関係です。サーバーはすべてのクライアントから使用されるホストマシンか、単にプレイヤーがゲームを実行しているマシン(クライアント)がさらに他のプレイヤーのサーバーの役割を果たす場合の両方があります。サーバーを起動しクライアント接続が行われると、二つのコンピューターはゲームプレイに応じてデータのやりとりを行うことができます。
ネットワークゲームを作成するには非常に具体的な詳細事項を気にする必要があります。ネットワークのアクションを Unity で設計し作成することは容易ですが、それでも複雑さはあります。Unity の重要な設計方針としてネットワーキングについてできるかぎり堅牢かつ柔軟性を持たせる、ということがあります。すなわちゲームクリエイターのあなたが責任をもたないといけないこととして、他のゲームエンジンでは自動化されているものの堅牢性が劣る事項も含まれる、ということです。あなたの判断はゲームデザインに大きな影響があり、設計段階の早い時点で決定することが大事です。ネットワーキングの概念を理解することでゲームデザインの設計が改善され実装で発生する問題を回避できます。
ネットワークゲームの構築にあたり良く知られていて、かつ検証も十分に行われている 2 つのアプローチがあり、権限サーバー( Authoritative Server )と非権限サーバー( Non-Authoritative Server )方式と呼ばれます。両方のアプローチはサーバーのクライアント接続の確立と相互のデータ受け渡しを前提としています。両方でエンドユーザーのプライバシーが確保され、クライアント同士が直接接続されることはなく、また IP アドレスも他のクライアントに公開されません。
権限サーバーのアプローチはゲームの世界のシミュレーションがサーバーで行われることを前提としていて、ゲームルールの適用やプレイヤークライアントからの入力処理も行います。各クライアントは入力の送信(キー入力やアクションのリクエストといった形で)をサーバーに対して行い、継続的にゲームの現在の状態をサーバーから取得します。クライアントはゲームの様態に直接変更を反映することは行わず、その代わりにサーバーにやりたいことのリクエストを行い、サーバーがリクエストをハンドリングして暗いなとに結果として何が起こったかを応答します。
基本的に、プレイヤーが何をしたかと、実際に起きることの間でレイヤーの分離があります。これによりサーバーがすべてのクライアントのリクエストを聞いてからゲームの状態を更新すればよいか判断することができます。
このアプローチのメリットはクライアントがチート(不正)することを相当に難しくすることです。例えば、クライアントがサーバーに敵を殺したことを伝えてチートすることはできず、その判断を自ら行うことができません。サーバーに対して武器を発射したことを伝え、その先で殺せたかをサーバーが判断します。
権限サーバーのもう一つの例は、物理挙動に依存したマルチプレイヤーのゲームです。もし各クライアントがそれぞれの物理シミュレーションにもとづいて実行されると、クライアント同士でわずかな差異が徐々に同期がとれなくなります。もしシミュレーションが中央サーバーでハンドリングされれば整合性が保証されます。
権限サーバーの潜在的な欠点はネットワーク上でメッセージが伝達されるのに時間がかかることです。もしプレーヤーがあるコントロールを前後に動かす場合に、ネットワーク上のサーバーからの応答が 0.1 秒かかるとしたら、遅延の発生がプレイヤーに認識されてしまいます。一つの解決策はクライアント側予測( Client-Side Prediction )を行うことです。このテクニックの本質はクライアントがゲームをローカルのバージョンとして実行しつつ、サーバーからサーバーが認証したバージョンを更新情報として受け取ることです。通常は、シンプルなゲームアクションでのみ使用されるべきであり、ゲームの状態にとって重要な情報の更新では使用してはいけません。例えば、プレイヤーに敵が殺された情報を報告して、その後にサーバーがこの判断を覆すのは賢くありません。
クライアント側予測は高度なトピックなので、このガイドでカバーしませんが、さらに詳細を調べるならば本や Web 上のリソースがあります。
権限サーバーは非権限サーバーに比べて処理オーバーヘッドが高くなります。サーバーがすべての変更をゲームの状態に対する変更をハンドリングすることが必要な場合、この処理負荷はクライアントで分散させることができます。
非権限サーバーはユーザー入力の結果を制御しません。クライアント自身がユーザー入力およびゲームロジックをローカルで処理して決まったアクションをサーバーに送信します。サーバーはすべてのアクションをゲームの世界の状態と同期します。これにより、設計の観点から実装しやすく、サーバーは単にクライアント間のメッセージリレーを行うのみであり、クライアントが行う以上の追加の処理がありません。
予測( 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 コールで、すべてのクライアントが指定されたレベル全体をロードすることができて、自動的にネットワーク要素を付け加えることができることに注意してください。ゲームにおいて、クライアントができる限り自己完結できるように構成することで、帯域幅は最小化できます。
サーバー自身の物理的な場所とパフォーマンスは、その上で実行されるゲームのプレイ品質に大きく影響します。サーバーと別大陸にいるクライアントは大きな遅延を経験するかもしれません。これはインターネットの物理的制約であり、実際の解決方法としてはサーバーと使用するクライアントができるかぎり近くに位置することであり、せめて同じ大陸上にあることです。
ネットワーキングについて、学習できる追加のリソースを集めました。