ue4 multiplayer online deep dive: 実践編1 (byking様ご講演) #ue4dd
TRANSCRIPT
バイキングとスピーカーについて
• http://byking.jp/
•対戦型のマルチプレイアクションタイプのゲームが得意
•近年はUE4を採用することが多いです
•本日のスピーカー• 株式会社バイキング CTO 鈴木孝司
• レンダリング・ネットワーク・サーバー・ゲームプレイ・I/O・最適化・デバッグ・・・
はじめに
•サンプルプロジェクトの作成及び確認はUE4.16を用いています
• DedicatedServerの運用はUE4.12.5で行っています
•大変申し訳無いのですが、スライド中の情報は不正確だったり場合によっては誤っている事もあると思います• ツッコミがあったら共有お願いします!
•自分が見つけていない、より良い制御方法などもあるかと思いますが、現状鈴木が把握している内容で紹介させて頂きます• 明確なドキュメントが不足している気がします・・・
お題目
• レプリケート• 遅延• RepNotify
• RPC• ReliableとUnreliable• RPCと帯域
• 帯域幅設定
• レプリケートとRPCの使い分け• DedicatedServer
• ListenServerとの違い• Cosmetic• 遷移フロー例• 負荷軽減
Replicate
レプリケート
• サーバー上にあるアクターをクライアント上で再現するための情報伝達
• サーバー上で生まれた、AActor::bReplicatesがtrueのアクターが対象
• そのアクターのうちReplicate対象と指定されているプロパティのみが対象
• UPROPERTY(Replicated) or UPROPERTY(replicatedUsing=xxx)
• 送信されるのは変更された部分のみ
• デフォルトで対象になっているアクター
• GameState、PlayerController、PlayerState、Pawn
• 逆にレプリケートされないアクター
• GameMode、AIController、Hud関連、GameInstanceなど
• 送信間隔は、帯域幅やAActor::NetPriority、Aactor::NetUpdateFrequencyなどの影響を踏まえて制御される
• うまく扱えば与えられたネットワーク帯域を効率よく利用したコードが書けます
• 遅延することはあれど、サーバー上のプロパティはいつか必ず同期される。
レプリケート遅延
ActorA
ActorB
ActorD
ActorE
十分な帯域があれば、すべてのアクターが送信できます
ActorC
ActorA
ActorB
ActorD
ActorE
ActorC
レプリケート遅延
ActorA
ActorB
ActorD
ActorC
ActorA
ActorB
ActorD
ActorE
ActorC
ActorE
ActorF
ActorG
ActorH
帯域が要求に対して不十分な場合プライオリティ付けされたリストの先頭から指定帯域まで送信されます
ActorF優先順位
プライオリティについて補足
•プライオリティ値• おおざっぱにいって「前回送信してからの時間」*「ActorNetPriority」• 前ページのようにそのTickで送信できなかったアクターは時間が伸びてすこしずつプライオリティが上がります。
• つまり、帯域が飽和している状態続いた場合、NetPriorityの設定によって送信間隔がかなり変わる可能性があります
•参考ソースコード• AActor::GetNetPriority
• オーバーライド可能なのでタイトルに合わせて自由に実装することも可能
• FActorPriority::FActorPriority
レプリケート遅延
FatActorA
ActorD
ActorE
FatActorB変化していない
FatActorA
プライオリティが高くても変化してなければノーカン※ただしプロパティ検査コストはかかる
ActorD
ActorE
NetPriorityデモ
•帯域飽和状態
• NetUpdateFrequencyは100固定
•上画面がサーバーで下画面がクライアント
•「すべてのキャラクターがNetPriority=100」と「ステージ左側のキャラクターだけNetPriority=10000」を切り替える
NetPriorityデモ
•同じアクターでもNetPriorityの違いによってレプリケートの挙動に差が出る
•ただし、NetPriorityの制御が行われるのは帯域が飽和している時のみ• 帯域が十分であればすべてのアクターがレプリケートされる
レプリケートまとめ
•レプリケート帯域が十分にあればNetUpdateFrequencyの頻度で、クライアントにパラメータが送信されます
•帯域が飽和するとアクターの更新間隔が悪化していく• プロパティ量や頻度について適切にコントロールする
• NetPriorityは帯域が飽和しているときだけ有効な事に注意する
RepNotify
ReplicatedUsing(RepNotify)
• 「クライアント上で」レプリケーションによってパラメータが変更された時に指定した関数を呼び出す• 変化しなかった時は呼び出されない
• クライアントと同じようにサーバーでも同じ関数が呼び出されたほうが便利• ネイティブコードでプロパティを書き換えただけの場合サーバー上では呼び出されない
• ブループリントは「あるノード」がそれを処理している
0 1
0 1
Notify呼び出し
サーバー上のRepNotifyコールバック
このノードがRepNotifyを呼び出す
(※値が変化しなくても)
呼び出されない
どちらもクライアント側では動作します
NativeコードでサーバーRepNotify
Setterを用意してローカルでRepNotify関数を呼び出す
ようにすると良いです
RepNotifyが呼ばれないことがある・・・?
•値が変化したときに呼び出し
•値はレプリケートによって書き換えられる
•レプリケートの間隔は設定や帯域依存
RepNotifyの仕組み
値を素早く切り替えるとRepNotifyイベントが起きない事がある。
0 1 0 1
0
0
1 0
1 0
連続的に変化するプロパティのRepNotify消失
0 1 2
0 1 2
0 1 2
0 2
正常
消失
連続的に変化するプロパティのRepNotifyによるレアバグ•消失する可能性のあるRepNotifyコールバックに、クリティカルなコード (インスタンスの生成やアセットのプリローディング)などが含まれているとバグを生む
•更新間隔が長いだけであれば、 AActor::ForceNetUpdate()を呼び出して素早くレプリケート情報を送信することで回避・軽減できるかも
•帯域的には無駄になるが、プロパティをレプリケートしつつReliableMulticastRPCでさらに値を送信することも可能
RPC
RPCについて公式ドキュメントより
ServerRPC
クライアントからサーバーになにかをお願いする時に呼び出すサーバーに何かを伝える場合はこれしかありません。
お小遣い頂戴
ServerRPCを呼び出せるアクター
• クライアント側• RoleがAutonomousProxy• AutonomousProxyに設定されるのは、
PlayerControllerとそこにPossessしているPawn• 加えて、上記アクターが保持しているコンポーネントでも呼び出し可能
• サーバー側• すべてのアクターが呼び出し可能でローカルで呼び出しが完結する
ServerRPCが正しく動作しない例
• RoleがSimurateProxyに設定されているアクターが呼び出した場合、すべてドロップ• 例
• クライアント側のGameStateに実装されたServerRPC• サーバープロセス上だけで動作するので意味がないです。
• 自分が操作していないPawnのServerRPC• 特に警告などもないので慣れない内はハマりやすい
ClientRPC
特定のクライアントだけにメッセージを送る際に使う
今月は赤字
何に使うのか
•主な用途はServerRPCに対する返答• クライアント「ABC弾をターゲット(x,y,z)に向かって発射!」
• サーバー「あなたは今動作出来ない状態なので弾が出せません!」• クライアントからの要求を受けたが、ワールドに対して影響を与える必要が無く、リクエスト送信者に対してだけなにかを返答を行いたいときに使います。
ClientRPCを呼び出せるアクター
• RemoteRoleがAutonomousProxyのアクター• PlayerControllerおよび操作対象のPawn
• ServerRPCに対して対になります
MulticastRPC
ごはんよー
ごはんよー
ごはんよー
ごはんよー
サーバーが自分を含む全員に同じメッセージを送信
Reliable と Unreliable
Reliable
•特徴• 呼び出した回数分「必ず」送信される
• パケロス時の再送制御付き• パケットのコピーが送信者のプロセス上に保存される
ReliableRPCを呼びすぎるとどうなるのか•受信確認が届かずに再送用のバッファが溢れた場合
ReliableBufferOverflowという警告と共に、即座に接続が切断される
Unreliable
• 特徴• マルチキャストとそれ以外で挙動がちょっと違う• ClientとServer
• 呼び出し回数分リクエストが送信される• 再送制御が無いので、パケットロストした場合は失われる
• Multicast• これだけ挙動が違っていて、RPCリクエストは一旦バッファに蓄積されて、アクターのレプリケート時に一緒に送信される
• つまり送信が遅延する
• バッファに蓄積された「同じRPC」が一定数を超えた場合、送信すらされずに破棄される
• 送信回数はパケロスが無かったとしても、保証されません
RPCと帯域
RPCは優先的に帯域を使う
ActorB
ActorC
RPC先輩が使った帯域の余りをレプリケートに割り当てます
RPC1
RPC2
RPC1RPC3
ActorA
RPC4
RPC1
RPC2
RPC1RPC3
ActorA
RPC4
帯域を超えるRPC呼び出し
帯域設定を超えて送信される
RPC1
RPC2
RPC3
RPC1
RPC2
RPC3
大きな構造体や配列を引数に使うとかんたんに飽和するので注意※10K / 60fpsだと1tickあたり
166byte程度しか無い
さらにもっと呼び出すと
帯域設定を超えて送信される!
エンジンは設定された帯域に合わせようと頑張るので・・・
こうなります・・・
大量のRPCレプリケートがしばらく沈黙
レプリケーションに多大な悪影響!
RPCでしょ
レプリケートが動作しない!
レプリケートはなんか遅いしね
RPC
レプリケートは信用できない!
何時くるの?!
RPCにしていくよ
全部Reliableでいいね!
通信帯域が飽和すると・・
RPC
といっても、タイトルによっては
デフォルトの帯域制限値ではどうしても不足することもある
枠を増やすしかない!
帯域幅設定
***Engine.ini のConfiguredInternetSpeed
各クライアントに対して12345 byte/secの制限
12K * 4 client= 49KByte/sec
現在の帯域幅値表示Displayall Player CurrentNetSpeed
ConfiguredInternetSpeed以外の帯域幅設定方法
• void APlayerController::SetNetSpeed(int32 NewSpeed);• プレイヤーコントローラー毎に個別設定が可能
• ネイティブコードからアクセスする• Max(Internet)ClinetRateで制約される• お手軽で思い通りに操作できるのでオススメ
• AGameNetworkManagerを使う• Ini設定などがあるため動きそうな気がする• 試しに設定してもデフォルトではゲームに影響を与えない模様。
• これもネイティブコードを触る必要がありそう• 未検証。すいません
• 帯域設定はサーバーからクライアント向けの設定のみ• クライアントからはRPC送信になるので帯域制限はない
帯域制御デモ
• SetNetSpeedを呼び出して、リアルタイムに割当帯域を変える
•ネイティブコードからしかアクセスできないので、BP版を作成した。
技術要件やハードの制約などと相談して適切な値を設定して下さい
RPCでアクタのステートを変更する時の注意点
サーバークライアントA
クライアントB
RPC(アクタAの顔色を青にして)
レプリケート
レプリケート
あとから接続して来たクライアントに情報が行き渡らない
RPCについてまとめ
• 即座送信や再送制御は便利
とても頼りがいがある
• 呼び出し回数については注意が必要
UnreliableMulticastRPCは間引かれたり遅延したりする
Reliableは回数保証
• 引数のデータサイズに気を付ける
FHitResultとかを引数にいれてはいけません
• 大量に呼び出すと
通信帯域を食いつぶしてレプリケーションが断続的に動作しなくなる事がある
せっかくNetPriorityやFrequencyを設定してもすべて無意味に
• ターゲットに合わせて通信帯域を設定
MulticastRPCとレプリケートの使い分け
プロパティの変化を伝えるだけならRPCでもレプリケートでも同じことが出来る
それぞれの特徴
• ReliableMulticastRPC• とても重要でサーバー側が呼び出した回数分処理される必要があるもの• ロストしない• 即時送信• 大量に呼び出されるとレプリケート帯域が悪化するので注意する• 信頼性が高い
• UnreliableMulticastRPC• 最悪メッセージがロストする• 遅延する• 連続送信してしまったときに間引かれれる
• 大量送信に対する保険
• 帯域は消費する
• UnreliableClientRPC• 即時送信&ロストを許容
• Replicate+ClientRPCで値の変化を約束しつつ、即時性を与えるという組み合わせも考えられる
• Replicate• プロパティを書き換える事が必要• いつか必ず同期する
• 遅延しても良い情報はReliableMulticastRPCではなくReplicateを使うべき
• 送信間隔による変化のロストや遅延が考えられる
わかりにくいのでフローチャート化
NO!
YES!
YES!
NO!
プロパティを使えないイベント同報処理
ロスト不可
ReliableMulticastRPC
UnreliableMulticastRPC
Replicate
NO!
YES!
YES!
NO!
YES!
NO!
どんな時でも遅延は許せない
ロスト不可
ロスト不可
ReliableMulticastRPC
UnreliableMulticastRPCor
Replicate
UnreliableClientRPCor
ReliableMulticastRPC
•やはりReliableMulticastRPCは便利で使いたくなる
•帯域が逼迫していなければレプリケートはほとんど遅延しない• 帯域に余裕をもたせることがとても重要!
SpawnActorを最速でレプリケートするトリック
SpawnActorの遅延
• SpawnActorで生成されたアクターはレプリケーションでクライアントに伝達される
•空き帯域が十分でない状態でアクタを生成すると、リモートクライアント上でのアクターの生成が遅延する。利用可能帯域によっては生成されないことすらある。• クライアント上の生成位置のズレなどが生まれる
• 移動の軌跡を描きたいといった場合に問題が生ずる可能性がある• 初期座標などをレプリケートしてクライアント側で対処する
• アクター生成をできるだけ早く処理するトリックを紹介
最速アクター生成デモ
•ボタンに反応してプレイヤーキャラクターが2つの弾を同時に発射する• 茶色の玉はアクターを生成しただけ
• 白い玉はアクターを生成し、そのアクターに対してRPCを呼ぶ
•動画前半は帯域制限なし、動画後半は帯域制限有り
•左上がListenServerで下側の2つがClientです
•帯域制限があるときに生成される玉はクライアントごとに違う
Actor生成の後RPCを呼び出す
RPC無し茶玉
RPC有り白玉
DedicatedServer
DedicatedServerについて
• Listenとの違い
•コスト
• Cosmetic
• Nativeコードでの注意
• DedicatedServerの遷移フロー
•パフォーマンス制御
ListenServerとの違い
• 低コスト
• RPCやReplicateの仕組みを使っていればネットコード自体は基本同じ
• DedicatedServerはviewportが無い• ローカルのカメラも無い
• AController::IsLocalController() == true のPlayerControllerが無い
• グローバルIPを割り当てることができる• NAT越え不要• 大半のプレイヤーがトラブルなく接続できる• データセンターは基幹回線網に接続されているのでpingも良い
• 自律的に動く• コンソールコマンドなどで直接操作はできない
• クライアントからRPCを送信して操作する• 外部のゲームサーバー管理サーバーと通信して操作する
ハードウェア資源コスト
Client DedicatedServer
CPU資源 あるだけ使う(250%以上)大量のスレッド
1コアを100%としてロビー 5%バトル中 60%MaxTickRate : 60※Xeon E5-2698 v3 @ 2.30GHz
メモリ 4GB+ 仮想メモリ(VIRT) 1.5GB 物理メモリ(RES) 0.8GB
GPU 必要 不要
安価なクラウドサーバーで動作可能軽量なゲームであれば、1コアに2プロセス以上も可能
Cosmetic
• DedicatedServer上で動くべきではないブループリントノードはCosmetic指定が適用されている
• UIやサウンドなど
• これらのノードはDedicatedServerでは処理がスキップされます
NativeコードでCosmetic関数
• NativeコードにはCosmeticの様な自動的にスキップ処理をおこなうような機構は無い• それぞれ IsNetMode(NM_DedicatedServer)などを用いて適切に処理する
• BPコードをNativeコードに移した時にnullアクセスバグが起こりやすい
• BPはnullアクセスがあっても警告のみで継続して動作する
デディケートサーバーの遷移フロー例
LEVEL:バトルレベル
LEVEL:ロビー
LEVEL:ロビー
LEVEL:バトルレベル
すべてのプレイヤーが揃って、全員の必要なアセットの読み込みが終わった
ServerTravel(ロビー)
プレイヤーを待つ
ServerTravel(バトルレベル)
バトルが終わったら全員いなくなるのを待つ
マッチング
DedicatedServerサーバーに接続
サーバーから退出Open(マッチング)
バトル
ServerTravel時の注意
•移動前に必要なアセットを予め読み込んでおく• トラベル時にブロック読み込みが長時間発生するとタイムアウトが発生する可能性があるため• ちなみに最初にクライアントがサーバーに接続する際も最低でもタイムアウトしない程度にアセットをプリロードする必要があります。
•接続エラーや切断• 問題が発生した場合はGameDefaultMapへ移動する
• GameDefaultMapはUGameMapsSettings::SetGameDefaultMap(FString&);でオーバーライドできる
デプロイとテスト
バージョン違いによって、接続できるが正しく実行できない現象が起こる。
ソースコントロールのリビジョン番号を埋め込むなどで早い段階でバージョンの相違が検出できるようにしておいたほうが良い。
サーバーのパフォーマンス制御
アクタやコンポーネントのTick省略
FTickFunction
BlueprintはActor単位で
パケット数制御
•通信負荷軽減の為にPacketの数を制御したい• Packetの送信数はサーバーのTick回数×クライアント数
• IniのNetServerMaxTickRateで最大Tick回数を制限する
• NetClientTicksPerSecond• 1秒間に処理するクライアント数の制限値
• ListenServerならデフォルトで有効
• Dedicatedserverで適用したい場合は、コマンドラインオプション limitclientticksをつける• MagiciansDeadでは使っていません
• あまりに低い値を設定しても効果が無い• ACKパケットだけが入ったパケットが送信されるためパケット数が余り減らない。謎
レプリケーション負荷制御
• サーバーはTick毎に、レプリケート対象のアクターのプロパティを検査して変更を検出する
• アクターやレプリケート対象のプロパティが多いと、この検査コストが処理負荷として大きな比重を占めることがある
• アクター毎に適切な NetUpdateFrequencyを設定する
• 重要性が低いものは更新間隔を低めにしておく
• 必要あればランタイム中にNetUpdateFrequencyを手動で変更してもよい
• 更新したいタイミングが明示的であれば、更新間隔自体を長めに設定しておきつつ、値のセットと同時にAActor::ForceNetUpdate()をつかってすぐさま送信しても良い
• Dormancyという機能もあるらしい
• 更新間隔の自動制御
• net.UseAdaptiveNetUpdateFrequency(UE4.16ではデフォルトで有効)
• 更新間隔の実績値を基準として次の更新予定を設定する
• 値の範囲
• AActor::MinNetUpdateFrequency(下限) ~ AActor::NetUpdateFrequency(上限)
まとめ
まとめ
• RPCの使いすぎは危ない• メリットとデメリットを把握してレプリケートで十分な所はレプリケートを用いる
• 帯域が飽和しなければレプリケートは迅速に行われることを胸に刻む
•帯域を適切に設定して効率よく使う• NetUpdateFrequencyとNetPriorityを適切にコントロールする
謝辞&参考資料
•イラスト屋様
•バイキングスタッフ
• CedricさんのUE Network解説ドキュメントhttp://cedric-neukirchen.net/Downloads/Compendium/UE4_Network_Compendium_by_Cedric_eXi_Neukirchen.pdf
•公式ドキュメント
• UE4 Auswerhub
勤務地東京(東新宿)
勤務時間10:00~18:30 (昼休み13:00~14:00)
給与固定給制月給 ※経験・能力を考慮の上、決定。例 プログラマー 業界歴11年 月給45万円
プログラマー 業界未経験 月給22万円
休日・休暇完全週休2日制(土・日)祝日年末年始有給休暇結婚記念日家族の誕生日
※選考希望の方は弊社HPまでご連絡ください。http://byking.jp/recruit/
開発者募集中! 世界にどデカイ爪痕を残さないか!?
ご清聴ありがとうございました!
おまけP2Pキー同期の通信モデル
P2Pキー同期モデル
Client
ListenServer
物理コントローラー
PlayerController1 (Authority)
PlayerController2 (Automonious)
2Pキャラ
物理コントローラー1Pキャラ
2Pキャラ
1Pキャラキーバッファ1P
キーバッファ2P
キーバッファ1P
キーバッファ2P
UnreliableClientRPC
UnreliableServerRPC
キャラクターはレプリケートOFF
• P2Pであれば1HOPで通信できてお手軽
•キーデータは複数フレーム分を束ねて送る
• Reliableだと再送が起きた時にブロックするのでUnreliableが良い
3人以上のルームを作りたい場合は
•サーバー経由(2HOP)のメッセージのやり取りが必要になる• メッシュ型ネットワークが構成できると良いがむずかしい
• やっぱりUE4を頼らずに独自実装にならざるを得ない