
こんにちは。トイロジックでプログラマをしているKです。本記事では、『FOAMSTARS』(販売元:(株)スクウェア・エニックス)のアワ地形を制作するにあたって行った、CPU最適化の事例についてご紹介させていただきます。
はじめに
『FOAMSTARS』について
『FOAMSTARS』は4vs4で戦うオンライン対戦アクションゲームです。開発には Unreal Engine 4.27.2 を使用しており、PS4・PS5間でのクロスプレイに対応しています。
本作では、射撃やスキルによってフィールド上に立体的なアワを自由に生成でき、プレイヤーが上に乗ったり、攻撃を防ぐための障害物として利用できます。本記事内では、射撃で発射される攻撃判定のある「アワ」と区別するため、フィールド上に生成されるアワを便宜上「アワ地形」と呼称させていただきます。
アワ地形の構成について
UEのランドスケープ機能を使用し、ランタイムにHeightmap等の動的編集が行えるよう、独自に拡張を行いました。
プロジェクト独自のテクスチャや頂点データもいくつか実装しています。
処理負荷のボトルネック
アワ地形の処理では、主にゲームスレッドのCPU処理負荷がボトルネックとなっていました。試合中は、以下のような処理をCPU側で毎フレーム計算します。
- 地形の編集リクエストの受付
- プレイヤーキャラやギミックなどから、どのように地形を変化させるのかのリクエストを受け取る
- 編集情報のネットワーク同期
- そのフレームでどのように地形を編集するのかを、サーバー・クライアント間で同期
- 地形編集処理
- 頂点情報の編集
- 高度の上げ下げ、チーム情報の更新、平滑化、法線計算など
- テクスチャ更新
- GPU側へ渡す情報を更新
- Heightmap、チーム情報、足跡や揺れに関する情報、etc…
- コリジョン更新
- Heightmapをコリジョンデータへ変換し、実際にコリジョン生成
最も負荷が高かったのが頂点情報の編集処理で、PS4/PS5ともにゲームスレッドで大幅な負荷となっていました。特に平滑化などのフィルタ処理は、カーネルのサイズによって負荷が高まりやすい部分です。
仮に、上記をすべてゲームスレッドで実行した場合、一部のスキルでは、1人分のアワ地形の更新処理だけで38msかかかることもありました。試合中は合計8人が任意のタイミングでスキルを発動できるため、大幅な最適化が必要となりました。
非同期タスク実行
今回はFAsyncTask
を使って、頂点編集処理を別スレッドで非同期実行させるようにしました。
UEではマルチスレッドプログラミングの機能が多数用意されています。FAsyncTask
は、メインスレッドをブロックせずにバックグラウンドで大きなタスク処理を実行させたい場合に良く使用されます。
ゲームスレッドとの同期
頂点編集の処理を別スレッドへ移し、次フレームで計算結果をゲームスレッドで受け取ります。
ただし、その間もゲーム側からアワ地形の情報(チームや高さなど)へアクセスする必要があるため、ゲームスレッド用・非同期タスクスレッド用のバッファをそれぞれ用意し、データのロックを最小限になるようにしました。
コリジョン更新
コリジョン更新は、Heightmapデータからコリジョンデータへのコンバート処理のみ非同期で行うようにしました。
コリジョンの実際の生成は、他のゲームオブジェクトと干渉するため、ゲームスレッドで行います。
頂点計算の並列実行
頂点編集の処理を矩形ごとに分割し、各頂点の計算をワーカースレッドへさらに分散し並列実行するようにしました。
編集処理のフェーズ(平滑化/高度編集/エッジ検出/ …etc)ごとに本流の編集スレッドで同期待ちを行い、前のフェーズの計算結果が参照できるようにしています。
同期待ちの間は本流のスレッドは暇になるので、自分でも一部の矩形のタスクを請け負います。
また、矩形を跨ぐような処理(フィルタ、法線計算など)は、並列で動いている別のワーカースレッドが書き込みを行っているデータと競合を起こさないため、参照したい情報を一時計算領域へ事前に受け渡すようにしています。
スレッド
ワーカースレッドはエンジン側で既にバックグラウンド用の汎用スレッドが用意されていますが、プロジェクト側で優先度などを調整しやすくするため、FQueuedThreadPool
を使用してアワ地形計算用のものを専用に用意しました。
キャラクター処理などの高優先度のタスクグラフを邪魔しないよう、それぞれ優先度は低く設定し、CPUの比較的空いているタイミングでタスクが処理されるようにしました。
スレッド | 実行する処理 | 優先度 |
---|---|---|
EditThread | 頂点編集の本流。 編集の全体フローの実行、WorkerThreadへ頂点編集のタスク発行を行う。 |
TPri_BelowNormal |
WorkerThread 0~3 | EditThreadから分散されたタスクを実行 | TPri_Lowest |
CollisionThread | コリジョンデータのコンバート実行 | TPri_Lowest |
最適化の結果
上述の最適化を施した結果、ゲームスレッドのアワ編集の負荷を大幅に削減することに成功しました。先述のスキルの例では、(ゲームの状況によりますが)元々1人分で38msかかっていた負荷を、0.5ms程度まで抑えることができました。
UEでは他にも多くのマルチスレッドプログラミングの機能が用意されています。今回ご紹介した事例はほんの一部分ですが、今後の開発の参考になれば幸いです。お客様にできる限り快適にプレイしていただけるよう、常にパフォーマンスを改善できるよう知見を深めていきたいですね。
 
© SQUARE ENIX