55K Views
November 19, 23
スライド概要
UE Tokyo .dev #1 の登壇資料です。
いろいろやってます。
Unreal Engine ネットワークアーキテクチャ 概要から掘り下げていく
自己紹介 すとんりばー ● ● ● ● Unreal Engine / C++ / Rust / Linux などが好き 趣味は読書と創作。 2000年生まれ。高専中退中卒エンジニア。 現在(2023年7月)はAlche株式会社所属。 技術ブログ: strv.dev GitHub: strvert Twitter : @strvert STONRIVER ( Riku Ishikawa )
この資料はなに? 本資料は、Unreal Engine のネットワーキングシステムを30 分で走り抜けることを目指している。 本資料では機能の使い方ではなく、システムの概観から入り、 背後の構造までを広く触れていく。 逆に、普通は話されそうな部分でもカットしている。機能の使 い方的な話は既存の資料をあたったほうが良い。
UE ネットワークシステムの概要
ネットワークシステム概要 UE のネットワークシステムは、 Client - Server モデル。 Client は Server に接続し、他の Client と マルチプレイができる。 マルチプレイ時の通信は、Client ⇔ Server 間でのみ発生する。
よくある誤解 これを聞いて陥りがちな誤解として、以下のようなイメージがある。 Server にだけ World が存在して、そこに各 Client のプレイヤーが参 加。Server 上の自分を操作するというイメージ。
実はひとりぼっち……? すべての Client と Server には個別の World が存在している。 さらに、Client の画面にレンダリングされる World は自分のローカル にある World だけ。 みんな別々の世界でプレイしている。
じゃあ一緒にいるように見えるのはなぜ? 個別の世界にいるのに同じ世界にいるように見えるのは、 ● 各 Client が自分の位置や情報を Server に伝え、Server の世界に反 映する。 ● Server はすべての Client に、Server World の Actor やプレイヤー の情報を複製(Replication) する。Client は Server からの情報を Client World に反映する。 の2段階のステップで、Server World や他の Client World で発生し た出来事が自分の Client World に同期されてくるから。
じゃあ一緒にいるように見えるのはなぜ? つまり、自分発の情報を Server World に送れば、Server がそれを他 のクライアントに同期するし、
じゃあ一緒にいるように見えるのはなぜ? 他の Client 発の情報も Server 経由で複製され、自分の World に同期 する。
じゃあ一緒にいるように見えるのはなぜ? また、Server World 発の出来事も、各 Client に複製される。
じゃあ一緒にいるように見えるのはなぜ? Client の視点では、自分(Client) の世界と Server の世界の間でやりと りし、 Server World の情報を受け取りつつ、自分の情報を必要に応じ て Server に送っているだけ。 重要なのは、これによって現実的に同期できる情報はごく一部だし、 ごく一部であるべきであるという点。 マルチプレイで実現すべきことは、完全な世界の同期ではなく、同じ 世界でプレイしているとプレイヤーに錯覚させること。 State Synchronization として知られる方式
同じ世界だと錯覚させればいい コンテンツよって、同期すべき情報は違う。他のプレイヤーと一緒に 遊んでいる感を、できるだけ少ない情報の同期で実現するのが大切。 各 Client World の中でローカルに実行できる部分は同期が必要ない。 たとえば、攻撃をしたことは同期すべき情報かもしれないが、攻撃エ フェクトの発生やパーティクルの計算結果は同期しなくていいかもし れない。攻撃をした事実から、各 Client が自分で発生から計算までを 行うことが可能で、それを同期する意義は低い。 UE のマルチプレイシステムも、この考えを前提に構築されている。
具体的な機能 Replication と RPC
前提: Actor のはなし UE では World に Spawn 可能なあらゆるものが Actor だが、目に見 えない Actor もある。例えば次のものもすべて Actor 。 ● GameMode / GameState → AGameMode / AGameState ● PlayerController / PlayerState → APlayerController / APlayerState これらは不可視だが、World 初期化時や Player の接続時に World 内に Spawn して存在している。 UE の同期システムは Actor ベースに実装されているので、オブジェクトが Actor であるかどうかは非常に重要。 これらは AInfo という不可視な Actor クラスの派生。
世界の同期を実現する機能 UE において、前章の内容を実現するために利用されている機能は、大 きく分類すると以下の2つ。 ● Actor Replication ○ Actorの情報の同期。 ○ Server → Client の単方向。 ● Actor Remote Procedure Call (RPC) ○ Server と Client の Actor 間でイベントを送受信できる。 ○ 双方向。
Actor Replication について Actorの情報の同期を行う機能で、主に以下の4つの要素から構成される。 ● ● ● ● Actor の作成と破棄の同期 Movement (位置、回転、速度)の値の同期 Replicated を指定したプロパティの値の同期 Actor の Subobjects (Components) に対する、上記と同等の同期 この操作は単方向。UE において Replication と言ったとき、その処理は 常に Server ドリブンで動作している。 Server 世界にあるどの Actor のどの情報をいつにどの Client に送信する かは、Server が制御しているということ。
発展: Replication Driver Server による Replication 制御の実装で、UE のマルチプレイのパフォーマ ンスやスケーラビリティは大きく変動する。高度な要件が必要なコンテンツ では、制御をコンテンツに合った形に最適化できると嬉しい。 そこで、 Replication Driver という仕組みが存在する。これを利用する と、同期制御のロジックをプロジェクトやプラグインからカスタムできる。 サーバー側処理の随所で、設定がある場合は Replication Driver に処理を委 託するコードが見られる。 UE 公式の効率的な Replication 制御実装である Replication Graph Plugin も、これを利用して実装されている。
単方向だと困らないのか? Actor Replication は単方向なので、変更が Client 側で発生したときに は、たとえReplicate を指定していても Client の世界の中のローカルな変 更に留まる。次の Server からの同期時に、Server の値で一方的に補正さ れ上書きされる。 しかし、Server に情報を伝える手段が何もないとマルチプレイが成立し ない(歩けもしない)ため、Client から Server にイベントや情報を伝える 手段 として RPC が存在する。
Actor RPC について リモートの関数を遠隔で呼び出す仕組み。 リモートとは、 Client にとっての Server で、 Server にとっての Client 。視点によってリモートの意味が反転することに注意が必要。 複数のタイプがあり双方向も可能であるため、適切に利用することで Client → Server のパラメータ付き呼び出しが可能。これにより Client の情報を Server に通知できる。
Actor RPC のタイプと効果 RPC の動作タイプは3種類。 ● Run on server ● Run on owning client ● Multicast これらの効果は、呼び出し元の Actor の所有状況と呼び出し元が Server であるか Client であるかによって変化するので、公式ドキュ メントから表を引用してみる。
Actor RPC - タイプ別の挙動 クライアントの世界から呼ぶ アクタの所有者情報 Multicast Run on server Run on owning client 呼び出しクライアントに所有のアク タ 呼び出し側クライアント上で実行 サーバー上で実行 呼び出し側クライアント上で実行 別のクライアント所有のアクタ 呼び出し側クライアント上で実行 ドロップ 呼び出し側クライアント上で実行 所有されていないアクタ 呼び出し側クライアント上で実行 ドロップ 呼び出し側クライアント上で実行 サーバーの世界から呼ぶ アクタの所有者情報 Multicast Run on server Run on owning client クライアント所有のアクタ サーバーとすべてのクライアント上で実行 サーバー上で実行 アクタが所有するクライアント上で実行 所有されていないアクタ サーバーとすべてのクライアント上で実行 サーバー上で実行 サーバー上で実行
なんかややこしい? ややこしい主な要因は、RPC が呼び出しタイプの期待する効果の実 行権限が足りなくても失敗せずに、実行可能な制約の範囲で実行して くれるため。 その全パターンを表にしているので場合分けが多い。
整理バージョン タイプが期待する効果を完全に発揮できる条件と効果の表に整理しなおす と急に3パターンになる。実用上はこの条件を満たすように実装を行うべ きなので、ほぼこれでよい。 RPC タイプ Run on Server Run on owning client Multicast RPC 呼び出し条件 Client → Server Server → Client Server RPC するアクターの所有者条件 効果 まとめ Server で処理を実行できる Client によって所有された Actor から Server に向かって RPC すると、 Client から Server の処理を呼び出 せる。 任意の Client に所有されていると き 所有 Client で処理を実行する 任意の Client によって所有された Actor から、Client に向かって RPC すると、Server から所有 Client の処 理を呼び出せる。 無条件で Server 自身と全 Client で処理を 実行する Server 上で任意の Actor から RPC すると、Server 自身と全 Client 上の 処理を呼び出せる。 呼び出し元 Client に所有されてい るとき
まって、所有って何よ Actor がクライアントに所有されているとは、言い換えると、Actor が、特定の Client-Server 間の接続に紐付けられているということ。 UE では、Client-Server 間の接続を管理するオブジェクトが、Server 世界と Client世界の PlayerController に保持される。 Client には自分の PlayerController しかない
再考 RPC - Run on owning client を呼ぶ ここで、RPC を考える。図中の 赤い Actor に、Run on owning client な RPC を呼ぶ処理を書き、Server から Client 上の処理を呼び出すとする。 しかし、この Actor は現在、どの接続にも紐づいていない。 Server には複数の Client との接続があるので、どの Client 上で実行され ることを意図しているのか判別できない。Client では処理が走らない。 なお、この場合サーバー上の Actor で処理が実行される。
再考 RPC - Run on owning client を呼ぶ Actor に Owner として PlayerController を設定して、再試行。 すると今度は、Owner が Client との接続に紐づいた PlayerController に なるため、 Actor にとっての Client が一意に定まるようになる。緑の Client 上で RPC 呼び出しが成功する。 これが、クライアントに Actor が所有されている状態。
再考 RPC - Run on server を呼ぶ 今度は、Client 世界の Actor から、Server 世界の Actor に Run on Server で処理を実行させようとしてみる。 まずは、所有者のいない Actor で考える。 接続を辿れないので、RPC 呼び出しに失敗し、何も起こらない。
再考 RPC - Run on server を呼ぶ 接続を辿れるようにするために、 Client 側で SetOwner 操作をしてから 再試行する。 この場合、接続は辿れるので Server に RPC は到達するが、Replication が Client → Server では行われないため、Server 世界ではこの Client は Owner ではない。 処理実行前に Server にリジェクトされる。
再考 RPC - Run on server を呼ぶ Server 側で SetOwner 操作をしてから RPC する。 この場合、Owner 情報が プロパティのReplication を通して全 Client に 同期される。結果として、Client 世界でも Server 世界でも、SetOwner した PlyaerController の Client が、Actor の所有者となる。 RPC に成功し、Client から Server 上で処理を呼び出せる。
Owner / 所有者の設定 このように、RPC を用いた処理の実行をうまく制御するには Owner の設 定を適切に行い、Actor を所有する Client を管理する必要がある。 それ自身が接続を保持する Actor である PlayerController や、 PlayerController が操作する Character 、PlayerState は自動で所有者 が設定されるため気にしなくてよい。 しかし、それ以外の World に存在する一般的な Actor では、自分で所有 者の設定を行うことが必要となる。
「接続」の裏側 NetDriver / NetConnection
縁の下の力持ち ここまで、機能に軸足をおいて同期システムの挙動を見てきたが、背後の 仕組みにはまだ触れていない。 ここからは、接続や通信として扱ってきた部分の裏側を掘り下げる。
NetDriver / NetConnection UEはクロスプラットフォームをサポートするエンジンであるため、ネット ワークシステムにも高度な抽象化が施されている。 NetDriver / NetConnection は利用するネットワークのバックエンドを 抽象化する抽象クラス。これらに対して具象クラスを実装することで、 様々なプロトコル・ネットワークスタックの上で UE のマルチプレイを実 行できるように設計されている。 ● ● ● ● IpNetDriver / IpNetConnection WebSocketNetDriver / WebSocketNetConnection SteamSocketsDriver / SteamSocketsNetConnection etc…
UE は UDP ? このような実装であるため、よく言われる「UE は UDP で通信をする」は 常に正しいわけではない。 Ip系のネットワーク実装を利用しているときは正しいが、WebSocketのよ うなTCPの上のプロトコルで動くこともできるし、独自のプロトコルで動 くように拡張することもできる。
NetDriver の役割 NetDriver は、Client でも Server でも1つだけ存在し、すべての Connection のハンドリングを行う。ネットワークインターフェースに Listen し、通信を待ち受けるのも NetDriver。
NetConnection の役割 NetConnection は特定の Client との接続を表すもので、マルチプレイ セッション中の通信経路となる。 Server と Client の両方に対で作成される。結果的に、Client には1本、 Server には接続中の Client の数の Connection が存在することになる。
Actor の所有のはなし RPC について触れたとき、PlayerController が保持する接続を辿って RPC が行われると説明した。 PlayerController が保持している接続とはまさに NetConnection のこと であり、所有者を通して Connection にアクセスすることで、通信相手を 限定することができていたということ。
まだインターフェイスが低レベル NetDriver / NetConnection で、低レベルなネットワーク通信は可能。 しかし、これらはまだ生のバイトデータを送受信するようなインターフェ イスを提供している層であり、エンジンやアプリケーションの実装が直接 利用するには低レベルなものである。 よりアプリケーション実装が利用しやすい方法で通信を可能にするため に、Connection 上での通信を目的に合わせて抽象する Channel が存在 する。
メッセージング機構 Channel
Channel Channel は双方向の通信用オブジェクトで、データを Channel に入れて 送信を行ったり、受信イベントを待ち受けてデータを受け取ったりdケイ ル。 NetDriver / NetConnection の具象実装と異なり、Channel は既に背後 のプロトコルには非依存な実装で、アプリケーションの機能に対して特殊 化されている。
Channels on Connection Channel の通信はすべて NetConnection の経路 の上に乗る。 NetDriver / NetConnection にServer-Client 間 のチャネルの対を管理したり、新たに生成したり する機構があり、通信を正しく対応するチャネル 間で送受信できるようにルーティングしてくれ る。 また、Channel を通るデータのことを Bunch と いう。 NetDriver や間のネットワーク プロトコルを省略した概念図
Channel の種類 Channel は、その派生クラスが複数定義されており、それぞれ異なるアプ リケーション機能のために実装されている。以下は重要な派生クラス。 ● ActorChannel ○ Actor の Replication、 RPC など。 ● ControlChannel ○ 接続のハンドシェイクや通信管理用メッセージなど。
Channel の本数 UE が利用している Channel の種類はそう多くないが、実際のマルチプレ イセッション中に利用される Channel の本数はかなり多い。 ControlChannel のようにセッションに対して1本だけ存在するものもあ るが、ActorChannel などは Replication 対象の Actor と同じ数だけ動的 に生成される。
ActorChannel
ActorChannel ActorChannel は、Actor の各種 Replication や RPC を通す Channel。 ActorChannel はプロパティの変更を即座に送信するわけではなく、変更 の量か、経過時間が一定以上になったらなどの条件を持って同期を行って おり、変更から送信までには少々ラグがある。 そして、実は RPC の送信は、UE の関数オブジェクトである UFunction を一時的にレプリケートするものに追加するという形で実現されている。
発展: Channel の定義と独自の Channel Channel の名前。動的に生成される否か、Channel を実装するクラスは何 かなど、Channel の定義は以下のように設定ファイルで行われている。 [/Script/Engine.NetDriver] +ChannelDefinitions=(ChannelName=Control, ClassName=/Script/Engine.ControlChannel, StaticChannelIndex=0, bTickOnCreate=true, bServerOpen=false, bClientOpen=true, bInitialServer=false, bInitialClient=true) +ChannelDefinitions=(ChannelName=Voice, ClassName=/Script/Engine.VoiceChannel, StaticChannelIndex=1, bTickOnCreate=true, bServerOpen=true, bClientOpen=true, bInitialServer=true, bInitialClient=true) +ChannelDefinitions=(ChannelName=Actor, ClassName=/Script/Engine.ActorChannel, StaticChannelIndex=-1, bTickOnCreate=false, bServerOpen=true, bClientOpen=false, bInitialServer=false, bInitialClient=false) これはプロジェクトやプラグインから上書きしたり、新規に追加したりす ることができる。 RPC や Replication などの ActorChannel 機能に依存しない通信を実装し たい場合などに、独自の Channel を実装するkとが有用であるケースがあ る。
おまけ - Actor 同期のサーバー処理フロー 1. UNetDriver::ServerReplicateActors() a. サーバーで毎Tick 呼ばれる。 b. 同期すべき Actor のリストの作成、優先度付け、実際の同期までを担当する。 c. Actor::NetUpdateFrequency に指定した1秒あたりの更新頻度はリスト作成の段階で考慮され、頻度 に満たない場合はリストに入らずその後の処理から除外される。 2. UActorChannel::ReplicateActor() a. 同期が必要と判断された Actor の所有する ActorChannel で呼ばれる。 b. ActorReplicator::CanSkipUpdate() によって、変更されたプロパティがあるか、その他同期が必要な 条件を満たしているかを確認する。 c. 条件を満たすと、FObjectReplicator::ReplicateProperties() で Bunch に同期プロパティの書き込み を行う。この書き込みに際して、プロパティのチェンジリスト(後述)と現在の状態を細かく比較し、変 更があったプロパティのみを対象とする。 d. Subobjects に対しても類似の処理を行う。 3. UChannel::SendBunch() a. 上記の処理を経て送信すべき情報があった場合のみ、Bunch をクライアントに送信する。
おまけ - Actor 同期のクライアント処理フロー 1. UActorChannel::ReceivedBunch() a. 受け取った Bunch の前処理とチェック。 2. UActorChannel::ProcessBunch() a. Bunch からの同期データの読み取りをはじめる。 3. FObjectReplicator::ReceivedBunch() a. 実際のプロパティの読み取りとActorへの反映処理を実行する。 変更をチェックしたり、負荷削減のためにチェック対象自体もフィルタしたり、同期を休眠状態に入れたり と忙しい Server に対して、Client はシンプルな受け取り処理から構成されている。
ControlChannel
ActorChannel ControlChannel は、通信を制御するためのメッセージをやりとりするた めの Channel。 ControlChannel では、事前に定義されたメッセージタイプに基づいた データを送受信している。メッセージタイプは内部的には FNetControlMessage<uint8 MessageType> というテンプレート型で定 義されますが、わかりやすいように定義用のマクロが用意されている。
メッセージタイプ定義 エンジン内部でのメッセージタイプ定義をいくつか引用する と、以下のようなもの。 メッセージタイプの名前と、メッセージに対応させる整数の コード、メッセージに乗せるペイロードを指定することで定 義している。 DEFINE_CONTROL_CHANNEL_MESSAGE(Hello, 0, uint8, uint32, FString); DEFINE_CONTROL_CHANNEL_MESSAGE(Welcome, 1, FString, FString, FString); DEFINE_CONTROL_CHANNEL_MESSAGE(Upgrade, 2, uint32);
エンジン定義のメッセージタイプ一覧(1) MessageType Description Hello クライアント接続時の初期メッセージ。 Welcome サーバが、クライアントにサーバのレベルを読み込むことを許可することを伝える。 Upgrade サーバが、クライアントにバージョンに互換性がないことを伝える。 Challenge サーバが、クライアントにチャレンジ文字列を送信し、整合性を確認する。 Netspeed クライアントが、要求された転送速度を送信する。 Login クライアントが、ゲームへの参加を要求する。 Failure 接続に失敗したことを表す。 Join 参加要求の最終メッセージ。このメッセージをもってPlayerController を生成する。 JoinSplit 子プレーヤー(分割画面)の参加要求 Skip オプションパッケージをスキップするクライアントリクエスト。 Abort クライアントが、UNLOAD要求によってまだ検証されていないパッケージを中止したことを通知する。 PCSwap クライアントからサーバーに、Connection->Actorのスワップが完了したことを伝える。
エンジン定義のメッセージタイプ一覧(2) MessageType Description ActorChannelFailure サーバーから送られた ActorChannel のオープンに失敗したことをクライアントからサーバーに伝える。 DebugText すべてのクライアントまたはサーバーにデバッグテキストを送信する。 NetGUIDAssign 明示的な NetworkGUID 割り当て。これは稀なケースで NetGUID が client->server でシリアライズされる場合のみ発生する。 SecurityViolation サーバが、クライアントにセキュリティ違反があり、切断されたことを知らせる。 GameSpecific ゲーム固有のカスタムメッセージをUGameInstanceにルーティングし、処理させる。 EncryptionAck サーバーが、暗号化チェックに成功したことをクライアントに通知する。 DestructionInfo 特定のアクターの破棄要求を通知する。 CloseReason クライアント接続クローズの理由を通知する。分析/ロギング用。 BeaconWelcome サーバはクライアントに参加してもよいことを伝える (クライアントはネットスピード/ビーコンタイプを送信する)。 BeaconJoin クライアントが、サーバにビーコンタイプの作成を要求する。 BeaconAssignGUID サーバーが、ビーコンアクターにNetGUIDを割り当てクライアントに送信する。 BeaconNetGUIDAck サーバが、クライアントから NetGUIDAck を受け取り、接続が正常に確立された。
意外と少ない UEネットワークのコネクションを全部制御していると考える と意外と少ない。 実際にすべての中身を把握するのは大変だが、存在と意味を 把握するのはそれほど難しくなさそう。 理解を深めるため、代表的な ControlChannel におけるメッ セージのやりとりである、クライアントがサーバーに接続す るまでの流れを解析してみた。
Client → Server 接続ハンドシェイク 右に示したシーケンス図が、UEクラ イアントがUEサーバーに接続するま でのControlChannel によるハンド シェイクのフロー。 互換性チェック、通信速度調整、 ロードすべきレベルの取得など、そ の後のConnection に必要なデータを 着実に交換していることがわかる。 また、暗号化やリダイレクト、プ ラットフォーム名など、指定できる オプションの存在にも気づく。 シーケンス図画像データ
注目すべき MessageType - GameSpecific 前述の通り、ControlChannel で送受信するメッセージは事 前定義されている必要がある。 では、エンジンに定義されていない通信を ControlChannel で行いたくなったときはどうすればよいのか?。 定義用マクロを使ってメッセージタイプを追加することも可 能だが、コンテンツ特有のメッセージを乗せることを想定さ れた GameSpecific というメッセージタイプも存在してい る。
注目すべき MessageType - GameSpecific GameSpecific は、以下のように定義されている。 ペイロードは2つで、コンテンツ独自のメッセージコードを載せ られる uint8 と、メッセージのデータ本体となる FString からなる。 これを利用することで、ControlChannel のメッセージタイプと は別に、コンテンツ独自のコード系列を利用した制御通信が可 能となる。 DEFINE_CONTROL_CHANNEL_MESSAGE(GameSpecific, 20, uint8, FString);
注目すべき MessageType - GameSpecific GameSpecific として受信したメッセージは、そのペイロードが GameInstance の下記の関数にルーティングされるようになって いる。 この関数は仮想関数として定義されているので、コンテンツの GameInstance で自由に Override することで、GameSpecific で受信したコンテンツ特有の制御メッセージを処理してやるこ とがでる。すばらしい! // GameInstance.h virtual void HandleGameNetControlMessage(UNetConnection* Connection, uint8 MessageByte, const FString& MessageStr);
注目すべき MessageType - Beacon系 また、これはメッセージタイプを意識して使うものではない が、通常の Connection を制御する以外の Beacon 系のメッ セージタイプの存在も目を引く。 これは OnlineBeacons というネットワーク機能の制御のために 存在しているメッセージタイプで、ゲームに接続せずにゲーム サーバーと通信を行うための軽量な方法を提供するもの。
注目すべき MessageType - Beacon系 通常、UEのクライアントがUEのサーバーと通信するためには、 事前に完全な Connection を確立する必要がある。 完全な Conneciton は、サーバーに指定されたレベルのロード や Player の Spawn を伴う。これでは、サーバーに接続する前 にサーバーの現在の接続人数を取得したい!のような簡単な通 信タスクに伴う処理コストが大きすぎる。 このような場合には、通常と異なる形でサーバーに通信用の Beacon と呼ばれる Actor を Spawn し、レベルをロードしない ままに通信を行うことができる。 Beacon 系のメッセージタイプは、この機能の実現に寄与してい る。
まとめ ControlChannel
まとめ - Control Channel ControlChannel は、Connection 自体の管理を行うための制 御メッセージを送受信している。 送受信するメッセージは事前定義されたメッセージタイプ と、それに紐づくペイロードから構成される。 サーバーへの接続処理から接続の維持管理、切断までを担う 重要な Channel。 各メッセージタイプの存在だけでも知っておくと、マルチプ レイ実装でやりたくなった少し複雑なことの実現法を考える のがちょっと楽になりそう。
まとめ - Control Channel また、必要に応じてコンテンツ用の通信を拡張することも想 定されているし、完全な接続をせずとも通信を行う手段も存 在します。 独自の通信手段を載せてしまうまえに、これらの機能の活用 を一度検討することができる。
続き?
今回話せなかったこと ここまでの内容で、Unreal Engine のネットワークシステムの裏 側にあたる部分は概観できた。 しかし、UE のマルチプレイシステムとしては、もっと高次の機能 が沢山ある。 ● Character Movement ● Gameplay Ability 等を用いた堅牢で保守しやすい開発 ● Replication Graph / Iris ● EOS 関連 ● etc… 今後に期待しつつ、また取り上げて喋りたいと思います。