概要
このレポートは、大学でUnreal Engineの挙動を研究した際にまとめたメモから作成しました。このレポートは、UEを使用してゲームを作成する人々を対象というよりは、Unreal Engineの内部挙動を知りたい人向けです。そのためこのレポートはゲーム開発者にとって実用性は皆無です。
そのほかに研究の過程で発見したネットワークゲーム開発者にとって有益な情報は数日以内に別記事にまとめる予定です。
注意事項
当記事は、チートツールの製作を推奨するものではありません。特にネットワークゲームに対するチートは明確に犯罪です。またゲーム会社にとってもプレイヤーにとっても非常に迷惑なのでやめましょう。誰も幸せになりません。UEのコード引用はEULAがあるため、Githubのリンクにて引用します。https://www.unrealengine.com/ja/ue-on-github の手順を踏むことで、該当リンクを確認できます。
検証環境
- Unreal Engine 4.27.2
- Windows 10 and Ubuntu 20.04 LTS
実際にはノートPC二台をつなげ、片方のPCでUnreal Engine製のアプリを二つ動かし、片方のPCでパケットを中継しパケット解析や疑似的に遅延やパケットドロップを与えていました。
経緯
ネットワークゲームにおいてネットワーク品質がどの程度実際のゲームプレイ体験に影響を及ぼすかを研究を行っていました。
WiFiだったり安定してないプロバイダーを使うとパケットロスによって遅延が起き、ゲーム体験が悪くなるというのは周知の事実だと思うのですが、ネットワーク品質とゲームプレイ体験の関係は詳細にはわかっていません。
またゲーム開発者がネットワーク環境の悪化を考慮しゲーム内容を調整するための定量的な手法は一般にはないので、正直なんとなくで調整しているというのが現実です。
そもそも定量的な分析ができるのか?という問いはあると思うのですが、動画ストリーミングの分野では普通に行われています。Netflixのデータセットを使った研究などが有名です。https://ieeexplore.ieee.org/document/9363272
そこで似たような手法をネットワークゲームにも適用する研究の一環として、Unreal Engineのパケット解析を行っていました。
Unreal Engineのネットワーク周りの仕組み
UnrealEngineがレプリケーションやRPCを発行した後、ネットワークに送信する前にPacketizeされます。
パケットフォーマット自体はソースコードに書いてあるためシンプルです。
ネットワークスタック全体は以下の記事がとても詳細で理解に役立ちました。
パケットフォーマットは以下のようになっていました。
Header(77bit)
Bunch Header(26bit)
Content Block Header (N bit)
Content Block Payload (N bit)
普通のプロトコルやネットワークスタックであれば、この規則のままパースすれば内容が解釈できますが、
UEのPacketizerはここにFArchiveというクラスによる圧縮が入るため、そのままパケットキャプチャしたデータを上のフォーマットに照らし合わせても読めません。
FArchiveについて
FArchiveは https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Core/Private/Serialization/Archive.cpp
に実装があります。このコードの中のSerializeIntPacked関数で32bitずつ入力され、データが圧縮されます。
各バイトの最上位7ビットが値を表し、最下位ビットは、さらにバイトがあることを示すために使用されます。
例えば32bitで与えられたデータだとしても、ほとんどのビットが0であるデータが存在します。
その場合0が並んでいるバイトが他のデータと結合するときに無駄なので省いてしまおうという考えで作られた機構のようです。
文章で書くと理解しずらいので以下に例を示します。(more)はさらに続きのバイトを示すmore bitです。
長さ1bitのデータを圧縮する
(残り32bitは0) 1 => 0 0 0 0 0 0 1
長さ7bitのデータ
(残り25bitは0) 1 1 1 1 1 1 1 => 0 1 1 1 1 1 1 1
長さ8bitのデータ
(残り24bitは0) 1 1 1 1 1 1 1 1 => 1 1(more) 1 1 1 1 1 1 1
長さ10bitのデータ
(残り22bitは0) 1 1 0 1 0 1 0 1 0 1 => 1 1 0 1(more) 1 0 1 0 1 0 1
一つのデータであれば割とシンプルで短くなるのでわかりやすいように見えますが、複数のデータが入ることを考えるとかなり混乱します。
長さ5bitのデータ + 長さ5bitのデータ
(残り27bitは0) 1 1 1 1 1 + (残り27bitは0) 1 0 1 0 1 => 1 0 1 1(more) 0 1 1 1 1 1 1
通常はUnreal Engineが処理してしまい人間が読むことがないので構わないのですが、bitの位置が値の内容によって
つまりよくある同じbyte位置を見てパースするようなパケット解析手法が取れません。
解析ツール
しばらくは以下の写真のようにホワイトボードや紙にbitを印刷してアレコレ分析していました。
ある程度法則はわかってからは、Web上で動くViewerを作り解析していました。
カスタムのUnreal Engineで出力したイベントデータとtcpdumpでキャプチャしたダンプデータを混ぜたjsonを作り、それを読み込ませていることで動作させていました。ViewerはReactで作成しました。
これでうまくいればよかったのですが、部分的にしか研究結果と一致しないパケットが多く完全な解析に至りませんでした。
せめてActorごとについているIdがパースできれば、いろいろなことができそうなんですが残念です。
結論
ぜんぜんわからない。俺たちは雰囲気でUnreal Engineをやっている。
蛇足
本記事はクリスマスの余興の記事だと思ってやってください(笑)
研究の過程で得た開発者にとって実用性がある、知名度が低いConfigやネットワーク関連の仕組みは別の記事にてまとめます。
研究自体は、多数のオブジェクトを同期させたときに、UEがどのような振る舞いをするかという方向性に変え、無事終了しました。
今後オンラインゲームやメタバース関連でUEは活用されていくため、ネットワーク関連の挙動が知られることは意義があると考えています。
特にパケット解析ができるようになるとリバースプロキシを作れるようになり、サーバーの冗長化や負荷分散が可能になるのではないかと思います。
この辺のコードは歴史があるようでUE3自体からあるような雰囲気でした。
最近だとOodleのような強力な汎用圧縮があり既にネットワークスタックに対しても適用可能なプラグインがあるため、
FArchiveのような機構は外してもよいのではないかと思いました。
UEの開発を行っているEpic Gamesの開発者ですらもこの部分の仕組みを把握していないのか、無駄なデータを送っていることを確認しました。
発見した箇所についてはPull Requestを送る予定です。
皆さんもお正月にぜひUnreal Engineのコードを読みましょう!!!