UDN
Search public documentation:
NetworkingOverviewJP
English Translation
中国翻译
한국어
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
中国翻译
한국어
Interested in the Unreal Engine?
Visit the Unreal Technology site.
Looking for jobs and company info?
Check out the Epic games site.
Questions about support via UDN?
Contact the UDN Staff
Unreal ネットワーク アーキテクチャ
ドキュメントの概要:Unreal ネットワーク アーキテクチャの概要。 Tim Sweeney により作成。 ドキュメントの変更ログ: Steve Polge によりポート化および更新。 Richard Nalezynski? により更新および管理。概要
マルチプレイヤ ゲームで最も大切なこと。 それはリアリティーの共有です。つまり、各プレイヤが、自分達は同じワールドに共存し、そこで繰り広げられる出来事をそれぞれの視点から見ているというリアリティーを感じられるということです。 マルチプレイヤ ゲームは、Doom のような 2 プレイヤ用の小型モデムに始まり、 Quake 2 、 Unreal 、そして Ultima Online といった、大きくて継続性があり、フリーな対話も実現したものへと変遷し、そのリアリティー共有を支えるテクノロジーは驚くべき進歩を遂げました。ネットワーキングをゼロから実装する
1 つ気づいていただきたい重要な点は、ゲーム内にネットワークされたマルチプレイやをサポートする予定がある場合には、まず最初にビルドインして、ゲームを開発するのと同じようにテストしてください ! 効果的なネットワークの実装のビルドは、ゲームオブジェクトのデザイン決定に重大な影響を与えます。 ソリューションの改良は難しく、手間が大変かかります。 ソリューションを後から組み込むことは難しく、ネットワーキング (多数のオブジェクト上への機能分配など) を考慮に入れる前は理にかなっていた設計上の決定事項が、マルチプレーヤーゲームで深刻な問題を引き起こす可能性があります。ピアツーピア形式
最初に登場したのは、 Doom や Duke Nukem.といったピアツーピア形式のゲームでした。ゲーム中はどの端末も同等であることがこれらのゲームの特徴です。両機で入力内容とそのタイミングが完全にシンクロし、全く同じ入力により全く同じゲームロジックが実行される、というものでした。完全なる確定的ゲームロジック(例えば定率、非ランダムなど)であったため、マシンでプレイヤが体験するリアリティーは同じものでした。 この種のゲームの長所は単純性でした。一方の短所は次の通りです。- 継続性がない プレイヤ全員が同時にゲーム開始しなければならず、追加プレイヤの参加ややめたい人が途中で抜けることは出来ませんでした。
- プレイヤの拡張性がない ネットワークが横並び構造をとっているため、連動負荷やネットワーク誘発エラーがプレイヤ数に比例して増加してしまいます。
- フレームレート拡張性の欠如 プレイヤ全員が同じ内部フレームレートで実行する必要があったため、非常に多様なマシンスピードに対応するのは困難でした。
クライアント サーバー形式
さて、続いて登場したのは単体クライアント サーバー アーキテクチャですが、これは Quake で初開発され、続いて Ultima Online で採用されました。この構造では、一台のマシンを「サーバー」に設定して、ゲームプレーのあらゆる意思決定をここで行っていました。その他のマシンは「クライアント」とし、ここでのキー入力がサーバーに送られ、その結果レンダリングするオブジェクトリストを受信する、という単なるレンダリング端末でした。この技術によって、インターネット上いたる所でゲームサーバーが起動される、大規模なインターネット ゲーム環境が実現されました。後に、使用帯域幅を低く抑えつつ画像詳細度を上げるため、クライアントサーバー アーキテクチャは、シミュレーションと予測ロジックをクライアント側に移行した QuakeWorld と Quake 2 へと拡張されました。この構造では、クライアントはレンダリングするオブジェクトのリストに限らず軌道情報をも受信するため、クライアント側でオブジェクトの動きをある程度予測することが出来ました。さらに、気になるクライアント動作のズレを解消すべく、横並び式の予測プロトコルが開発されました。 しかし、この手法にもいくつかの欠点がありました。- 非制限無期限でない ユーザーやライセンシーが新種のオブジェクト(武器、プレイヤ コントロールなど)を作成した場合、こうした新規オブジェクトのシミュレーションや予測といった点を定義するため、グルーロジックを加えなければなりません。
Unreal ネットワーク アーキテクチャ
そこで、Unreal は、マルチプレイヤ ゲームに汎用クライアントサーバー モデルという新しい手法を採用しました。このモデルでも、ゲームのステートを司る役目はやはりサーバーにあります。しかし実際には、クライアントが自機のゲーム状況の一部を受け持って常に正確に保ち、サーバーとほぼ同じデータに基づいたサーバー同様のコード実行により予測を行います。 これにより、二機間で交換しなければならないデータ量は最小化されます。 サーバーは、複製される関連アクタとその複製されたプロパティにより、クライアントにワールドの情報を送信します。 クライアントとサーバーはまた、関数が呼ばれるアクタを有するサーバーとクライアント間でのみ複製された関数を通して通信しあいます。 さらに、 ゲーム状況 は、拡張オブジェクト指向スクリプト言語によってクライアント独自で記述されており、ゲームロジックはネットワークコードから完全に切り離されます。この言語による記述が可能ならどんなゲームでも統合可能となるため、ネットワークコードは汎用化されます。これにより、拡張性を高めるオブジェクト指向のねらい、つまり、あるオブジェクトの内部実行はハードウェアに組み込まれた他種コードで記述しない、オブジェクトの挙動は完全にそのオブジェクトで記述する、といったコンセプトが達成されます。基本コンセプト
ねらいここでは Unreal のネットワーク構成を非常に厳密に解説します。 複雑な内容がかなりあるため、誤解を招かないために厳密に説明します。 基本用語
以下に基本用語を詳しくご紹介します。
- variable とは、ひとつの固定名と修正可能な値の関連性をいいます。変数に関連付けられる値の例としては、「X=123」のような整数、「Y=3.14」のような浮動小数点数、「Team=”Rangers”」のような文字列、そして「V=(1.5,2.5,-0.5」といったベクトル、などがあります。
- オブジェクト とは、ある固定の変数セットからなる内蔵データ構造です。
- アクタ とは、レベル内を単独で動き回り、レベル内の他のアクタと相互に作用することのできるオブジェクトです。
- レベル とは、ある一連のアクタを含むオブジェクトです。
- Tick(ティック) とは、DeltaTime という不定長の時間が経過に伴い実行される、ゲーム状況全てを更新するオペレーションです。
- ゲーム 状況 とは、ティック実行中でないある時点における、あるレベル内に存在する全アクタ一式、およびその変数の現在の値のことです。
- クライアント とは、ワールドで発生するイベントの大まかなシミュレーションに適した、ゲーム状況の近似サブセットを維持する Unreal.exe の実行中インスタンスです。
- サーバー は、単一レベルのチック処理を統括し、ゲーム 状況を間違いなく全クライアントに伝達する
Unreal.exe
の実行中インスタンスです。
上記コンセプトは、チックとゲーム状況での例外の可能性を除けば、いずれも汎用的です。そこで、これらについてより詳細にご説明します。はじめに、Unreal の更新ループについて簡単にご説明しましょう。
- サーバー ならば、全クライアントとカレント ゲーム状況を交信する。
- クライアント ならば、要求動作をサーバーへ送り、新規ゲーム 状況情報をサーバーから受信し、画面に現在のワールドの概観をレンダリングする。
- 前回の Tick(ティック) から、任意の時間
DeltaTime
(デルタタイム) が経過したら、 Tick(ティック)処理 によりゲーム状況を更新する。
Position += Velocity * DeltaTimeこれにより、フレームレートの高い拡張性が実現されます。 Tick(ティック)処理の間、ゲーム状況は実行コードにより常に修正されます。次の 3 つのケースにおいて、ゲーム状況は変更されます。
- アクタ変数が修正された場合
- アクタが作成された場合
- アクタが破壊された場合
上記から、サーバーのゲーム状況は、あるレベル内の全アクタの変数すべてによって完全かつ簡潔に定義されています。サーバーはゲームプレー フローの権威であり、サーバーのゲーム状況が常に真のゲーム状況とみなされます。クライアントマシンのゲーム状況 バージョンは常に、サーバーのゲーム状況から逸脱した多様な近似ステートであると考えます。クライアントマシンに存在するアクタはプロキシと捉えられますが、これはアクタがオブジェクトそのものではなくオブジェクトの暫定的な近似描写であるためです。 ネットワークされたマルチプレイヤゲームで使用するため、クライアントがレベルをロード時、
TRUE
に設定されている bNoDelete
または TRUE
に設定されている bStatic
を持つアクタを除き、レベルにあるアクタすべてが削除されます。 クライアント(サーバーにより決定される)に関連する他のアクタは、サーバーからクライアントに複製されます。 複数のアクタは( GameInfo アクタなど)は、決してサーバーに複製されません。
帯域幅の制限
ネットワーク帯域幅が無制限だったなら、ネットワークコードはいとも簡単なものとなります。例えば、サーバーは各チック終了時に、各クライアントに向けてゲーム状況のありのまま全てを送信するだけでよいからです。 そうすれば、クライアント側では常にサーバーで進行するゲーム通りのビューがレンダリングされます。しかしながら、実際のインターネットでは、28.8K モデムの場合、正確な更新情報すべての交換に必要とされる容量の 1% 程度でしかありません。ユーザーのインターネット接続環境は今後も益々早くなって行きますが、帯域幅の改良スピードは、ゲームやグラフィックの進歩度を示す「Moore の法則」をはるかに下回っているのです。つまり、ゲーム状況アップデートを完全にカバーできる帯域幅が得られる日が来ることはないでしょう。 さて、ネットワークコードの主たる目的は、サーバーからクライアントへの、適度に近似したゲーム状況の配信です。 これにより、帯域幅の制限を守りつつリアリティーが共有できる程度にインタラクティブなワールドのビューのレンダリングが、クライアント側で実現されます。 複製
Unreal では「サーバー・クライアント間のリアリティー共有に適した近似調整」という一般的問題を「複製」の問題とみなします。つまり、近似リアリティー共有を実現するためにサーバー・クライアント間で行われる、一連のデータおよびコマンドの定義に関する問題です。
アクタ
役割
通常、すべてのはアクタはRole
と RemoteRole
プロパティを持ちます。それらは、サーバーとクライアントで異なる値を持ちます。 サーバーのすべてのアクタは、 ROLE_Authority
に設定する Role
を持ちます。
サーバーのすべてのアクタは、 RemoteRole
を持つ可能性があります。 -
ROLE_AutonomousProxy
(所有するクライアントを複製時、PlayerControllers と Pawn を操作) -
ROLE_SimulatedProxy
(他の複製されたすべてのアクタ) -
ROLE_None
(クライアントに複製されたことがないアクタ)
RemoteRole
は、クライアント上にあるアクタの Role
です。 すべてのアクタは、 ROLE_Authority
に設定される RemoteRole
を持つクライアントに複製されます。
定義
Actor
(アクタ) クラスは ENetRole
エミュレーションと Role
、 RemoteRole
の 2 つの変数を以下の様に定義します。:
// ネット変数 enum ENetRole { ROLE_None, // 役割なし ROLE_SimulatedProxy, // このアクタのプロキシをローカルでシミュレーションする ROLE_AutonomousProxy, // このアクタのプロキシはローカルで自立する ROLE_Authority, // アクタのコントロール権限 }; var ENetRole RemoteRole, Role;
Role
や RemoteRole
といった変数はローカルおよびリモートのマシンをどの程度コントロールするのかが個々にアクタについて記述されています。
-
Role==ROLE_SimulatedProxy
は、アクタが物理とアニメーションをシミュレーションする一時的近似プロキシであることを意味します。クライアント側で、シミュレーションされたプロキシが基本物理(直線や重力関連の動きや衝突)を実行しますが、高度な動きの決定はせずにそのまま進みます。 それらは、 simulated キーワードを持つスクリプト関数のみを実行することができ、 simulated とマークされた状態のみを入力できます。
-
Role==ROLE_AutonomousProxy
はアクタがローカルプレイヤであることを意味します。自立プロキシには、クライアント側で動作を(シミュレーションというよりも)予測するために組み込まれた特別なロジックがあります。 クライアントであらゆるスクリプト関数を実行することができ、あらゆる状態を入力することもできます。
-
Role==ROLE_Authority
は、アクタのコントロールに関し絶対的権限を有するということです。
Role==ROLE_Authority
があり、 RemoteRole
はあるプロキシタイプに設定されます。クライアント側では、 Role
と RemoteRole
はサーバーの設定値と比較して、常にその反対に設定されます。これは Role
と RemoteRole
の意味合いからして当然のことです。
ENetRole
の値のほとんどは、 Actor
や PlayerPawn
といった UnrealScript のクラスにおける複製命令文で定義されます。複製命令文で様々な役割値の意味を定義する例を挙げてみます。
-
Actor
クラスにある複製定義により、Actor.AmbientSound
(アクタ.アンビエントサウンド) 変数は、サーバーからクライアントへ送信されます。:if(Role==ROLE_Authority) AmbientSound;
- アクタクラスの複製定義により、
Actor.AnimSequence
(アクタ.アニメーション シーケンス) 変数は、サーバーからクライアントへ送信されますが、これは、メッシュとしてレンダリングされたアクタのみだけです。:if( DrawType==DT_Mesh && (RemoteRole<=ROLE_SimulatedProxy) ) AnimSequence;
-
PlayerPawn
クラスにある複製定義のため、クライアントは、Fire
とAltFire
関数のサーバーへの呼び出しを複製します。:if( Role<ROLE_Authority ) Fire, AltFire;
-
Actor
クラスにある複製定義により、シュミレーションされたプロキシのVelocity
が初めてスポーンされた時、サーバーはクライアントにそれと移動ブラシを送信します。if( (RemoteRole==ROLE_SimulatedProxy && (bNetInitial || bSimulatedPawn)) || bIsMover ) Velocity;
関連性
定義Unreal のレベルは巨大になることが多く、プレイヤに見えるレベル内アクタは、どんな時もほんの一部だけです。その他のレベル内アクタのほとんどは不可視で、オーディオも無効で、プレイヤに影響する効果も全くありません。サーバーが可視もしくはクライアントへの効果有効と判断した一連のアクタは、そのクライアントにとってのアクタの関連セットとみなされます。Unreal のネットワークコードにおける帯域幅最適化機能では、サーバーがクライアントにクライアントの関連セットにあるアクタのみを通知します。 Unreal では以下のルールを (順番に) 適用して、プレーヤーに関連するアクタのセットを決定します。
- アクタの RemoteRole が ROLE_Noen の場合、関連性はありません。
- アクタが別のアクタの骨組みにアタッチされている場合、そのベースの関連性によって関連性が決定されます。
- アクタの
bAlwaysRelevant
が設定されている場合、関連性があります。 - アクタに
bOnlyRelevantToOwner=true
が設定されている場合 (Inventory に使用)、唯一の可能性としてそのアクタのオーナープレーヤーのクライアントと関連性があります。
- アクタが
static=true
(固定=有効) またはbNoDelete=true
(非削除=有効) の場合、関連している。 - アクタが
ZoneInfo
(ゾーン情報) クラスの場合、関連している。 - アクタがプレイヤ(
Owner==Player
)に所有されいる場合、関連している。 - アクタが
Weapon
(武器) で、可視アクタにより所有されている場合、関連している。 - アクタが、非表示 (
bHidden=true
(非表示=有効))、衝突なし(bBlockPlayers=false
(プレイヤをブロック=無効))、そしてアンビエントサウンドがない(AmbientSound==None
(環境音==なし))の場合、アクタは、関連していない。 - アクタが、アクタの
Location
(場所) とプレイヤのLocation
の間での視線チェックにより可視の場合、関連している。 - アクタが 2~10 秒以内前(正確な秒数は行われるパフォーマンス最適化によって異なる)に可視だった場合、関連アクタである。
AActor::IsNetRelevantFor()
で実装されています。
bStatic
と bNoDelete
のアクタ (クライアントに滞在する) も複製されることに注意してください。
以上のルールは、プレイヤに必ず影響を与えるアクタセットを適度に近似するよう設定されたものです。もちろん、これで完全ではありません。例えば、大きなアクタの視線チェックには時折偽陰性がみられますし(これには経験則で対処されますが)、環境音の閉塞状態も確認しない、といったことです。とはいえ、近似でのエラーは、インターネットというかなりの待ち時間が必要でパケット損失が常である、ネットワーク環境につきもののエラーによって引き起こされているのです。
Repnotify
キーワードでマークされたプロパティが複製されると、UnrealScript の ReplicatedEvent()
イベントが呼び出され、変更されたプロパティの名前をパラメータとして渡します。ネットワーク帯域幅の節約にこれをどのように利用できるかについては、「複製のパターン」セクションを参照してください。
クラスごとに NetPriority
(ネット プライオリティ) のデフォルト設定をうまく調整します。発射物やプレイヤには極めて高いプライオリティを、単なる装飾効果には低いプライオリティをあてます。Unreal で設定されているデフォルトは、当初の見込み適正値ですが、必要に応じて調整して状況改善して下さい。
アクタがクライアントに初めて複製されると、変数は全て各クラスのデフォルト値に初期化されます。続いて、最終値と異なる変数のみが複製されます。つまり、なるべく多くの変数が自動的にクラスのデフォルトに設定されるよう、クラスを自分で設計すると便利です。 例えば、アクタは LightBrightness
値、123 を常に持つようにする場合には 2 つの方法があります。 (1) LightBrightness
のデフォルト値を 123 に設定するか、 (2) アクタの BeginPlay
関数で、initialize LightBrightness
を 123 に初期化します。 LightBrightness
値は複製がまったく必要でないため、 最初の方法のほうが効率的です。2 つ目の方法は、アクタが最初にクライアントに関連づけられるたびに LightBrightness
の複製が必要です。
また、次の状況にも気をつけてください: - アクタ参照をシリアライズできない場合、=bNetInitial= と
bNetDirty
はクリアされません (その参照はクライアントとの関連性がないため)。つまり、サーバーはこのプロパティの複製を続けることになり、CPU サイクルのコストが上がります。この問題の回避例として、Native の複製コード (Base、Controller および Instigator の複製) を参照してください。 - 配列の複製はやめてください。配列メンバの値が 1 つでも変わると配列全体が複製されるからです。
- プロパティ (ベクトルと回転) は、複製のため切り詰められます。
サーバーの CPU 使用の最適化には、Native の複製関数を使用します。
- クラス定義用の
NativeReplication
キーワード -
AActor::GetOptimizedRepList()
のクラスバージョンを実装する。 - 各複製プロパティの
RepIndex
を生成するため、スクリプトReplication{}
定義が必要。 - 場合によって意は、APickupFactory バージョンのような、
GetOptimizedRepList()
を大量に最適化できます。 -
AActor::IsNetRelevantFor()
- Pawn (1 つのクライアントのみの単一パス時、 Pawn の関連性に基づき、Weapon Attachment(武器添付)のようなものが関連しているため) の総関連キャッシング:
bCachedRelevant
flag.
- Pawn (1 つのクライアントのみの単一パス時、 Pawn の関連性に基づき、Weapon Attachment(武器添付)のようなものが関連しているため) の総関連キャッシング:
弊社では、 Unreal Tournament にて次のタイプのネットワーク関連の不正行為に直面しています。:
- Speedhack
- 動作更新にクライアントクロックを使用している事実の利用
- クライアントと過度に異なる率で進まないサーバーを確認することによる、Built-in 検出。
- 大幅なパケット損失を伴った偽陽性
- Aimbots - UnrealScript と社外バージョン
- Wall hack と Radar - UnrealScript と社外バージョン
ネットワーク トラフィックを監視するため、ライセンシーは以下の記述とエンジンをコンパイルすることができます。
サーバーの CPU 使用の最適化には、Native の複製関数を使用します。 これは、
DevNetTraffic
( Debug で自動) を通してネットワークの統計地集計を有効化します。 ネットワークの統計地を非抑制にするには、ゲームエンジン構成で Suppress[#]=DevNetTraffic をコメントアウトします。
DevNetTraffic
がある場合は、ネットワークデータ全体の要約データがログに書かれているマシンで受け取ります。 要するに、サーバーにより送信されたデータを見るには、クライアント側でこれを行ってください。 クライアントにより送信されたデータを見るには、サーバー側でこれを行ってください。ログに書かれたデータは非常に冗長な形式で、全てのパケットにタイムスタンプがあり、全ての複製アクタ、変数そして関数呼出しに要約がついています。
下記のコンソールコマンドは、非抑制的なネットワーク統計地集計に使用されます。
unsuppress DevNetTraffic
ネットワークドライバの実装
プラグイン形式ネットワークドライバUnreal の提供するネットワーク環境は、C++ の
UNetDriver
(Uネットドライバ) クラスに基づく、プラグイン式インターフェースが採用されています。Unreal Engine では UNetDriver
(Uネットドライバ) クラスを通してあらゆるネットワーク問題を処理します。 そして、Unreal.ini ファイルにデフォルトは以下のように定義されている最適なネットワークドライバを、同時に読み込みます。
[Engine.Engine] NetworkDevice=IpDrv.TcpNetDriverUnreal のサポートする最新インターネット環境は、
TcpNetDriver
という名の UDP ネットワークドライバです。しかし、 UNetDriver
の新規サブクラスを作成し、 TcpNetDriver
のソースを、新規関数を実装する際の参考にして、新種のネットワーク環境を作ることも可能です。実際、Maverick Software のチームが、このインターフェースを利用して Unreal の Mac 版用に AppleTalk ドライバを作成した例があります。
ネットワークドライバは、接続開始、接続終了、データ送信、そして不確実なデータの受信を行います。しかし、データのコンテンツは Unreal Wire Protocol (Unreal ワイヤープロトコル) で定義されており、ネットワークドライバ側では全く感知しません。つまり、 UNetDriver
h では交信中の情報内容が全く把握されておらず、ただ送る、受け取る、を行っています。特徴は以下のようになります。
コネクション方式 UNetDriver
は UNetConnection
サブクラスの定義する一連の接続を維持します。 UNetDriver
は、コネクションレス プロトコル(UDP など)階層の初めにあり、接続保守、タイムアウト管理などのための独自の内部ロジックが必ず含まれます。
UNetDriver
は、ストリーム方式ではなく、パケット方式です。全てのデータは 0...MAX_PACKET_SIZE
(最大パケットサイズ) の大きさの個別パケットにある UNetDriver
を通じて送受信されます。
パケットは不確実に送信されます。パケットの確実な送受信は全て、=UNetDriver= には非可視なハイレベルで処理されます。あるマシンから別のマシンへと送られたパケットは到達したりしなかったり、また複数回到達したり、ということもあります。更に、到達したパケットの処理不良や大幅な遅延の可能性もあります。
パケット自体は決して損傷しません。パケットが受信されたら、そのサイズとコンテンツは送り側のそれと同じものが保証されます。
インターネットドライバ
Unreal のインターネットは、不確実なコネクションレスの通信に標準インターネット プロトコルである UDP をベースにしています。Unreal のクライアント サーバー間のゲームプレイは全て、UDP アドレス(クライアントとサーバーがそれぞれ 32 ビットインターネットアドレス、16 ビットポート番号を持つ)の一定かつ不変な組み合わせによって調整されます。 デフォルトのポート番号は
Unreal.ini
構成ファイルにあります。
つまり、Unreal の UDP パケットは非常にプロキシが容易で、プロキシでの内容把握がないまま、ユーザーが意識することなくプロキシ可能です。