あばうと
ゲーム大賞アマチュア部門で佳作を受賞したボルカノンで実際に行った最適化を書きます。
紹介動画↓
https://www.youtube.com/watch?v=KWcx2CNQ_ks&ab_channel=awardsJGA
オンボードのGPU(Iris Plus Graphics 655)、Unityエディタ上で60FPS(平均90、最高120程度)出るように限界まで最適化しました。
ボルカノン
2Dドット+ドット化シェーダーのハイスピードアクション
マグマを注入→噴射してジャンプ、壁に張り付いて縦横無尽に駆け回るゲーム。この記事を書いている時点で非公開
チームメンバー8人(うちプログラマ3人)×4か月のプロジェクト
最低1万ブロック(200*60)以上の面積で、オブジェクト数は数千。その状況でFPSを落としてはいけないという条件で最適化をしていった具体例を紹介します。
基本
Statを見る
見るのは主にFPSの部分。細かい見方は検索してください。
ちなみにこの開発中にFPSの目測が出来るようになりました。
プロファイラ
こちらも見方は検索してください。
見るのはPlayerLoopの中(EditorLoopは仕方がないので)
急に重いフレームが出るのはアクションゲームとして致命的なので検証対象。
その中で何が重いのかを調べ、処理の削減や分散をして最適化をしていきます。
モデル
超点数が多いモデルは発注段階で減らしてもらうように
ちなみにUnityで見るとStatsのVertsの項目が3Dソフトより多くなります
### カメラ設定
SkyBoxは描画するので変える。
基本はSolidColorだが、当然だがDontClearの方が軽量なので場合によっては可能
DontClearは背景か何かの1レイヤーだけ使っていた気がする
ポストエフェクト
ドット化するエフェクトとドット画像を組み合わせて全体として2Dドットにしているので、エフェクトの対象範囲を減らすように(主に3Dモデル部分のみに限定)
ブロック数の調査→staticに
仮のステージを作っていくうちに、最低のステージ面積は1万ブロック分は必要ということが判明(かなり広範囲を縦横無尽にハイスピードで動き回るので)
動かないオブジェクトはstaticにする
頂点数を減らす
ステージを構成するブロックは1×1で大量(1万~3万)に敷き詰める前提なので、一つ一つを限界まで最適化。Spriteは扱いづらいので選択肢から除外。
オブジェクト | 頂点数 |
---|---|
Box | 100 |
Quad | 20 |
Plane | 609 |
調査の結果、Quadを選択。裏面を自動的に描画しないなど3Dゲームでは使いづらいが2Dゲームなので逆に加点。
半透明は重い
ゲームの描画計算は
内容 | 処理方法 |
---|---|
不透明 | 書き換える(代入) |
半透明 | 計算して加算 |
透明 | 計算しない |
という処理の差があり、半透明の方が重いので基本的に理由がないなら完全不透明か透明にする
画像設定
・画像はある程度の粒度で一つにまとめる。読み込む「回数」が重くなる原因なので一つにまとめるとよい
・ブロックとの距離が一定かつドット絵なので、MipMapを生成しないように
・FilterはドットなのでNoPoint(画像処理を挟まない)が良かったが、ちらつくのでTrilinearに
・画像サイズは2の倍数に。さらに一つの画像にまとめる画像数は2の倍数に(4*4など)
・画像の中で半透明は使わず、完全透明or完全不透明に
カメラ
・ClearフラグはSolidColorに
・ClippingPlanes(描画する範囲)を出来る限り狭く(ボルカノンでは1~200、背景の範囲ぎりぎり)
パーティクル
・MaxParticleを、durationを限界まで制限(一番効果あり)
・出来る限り半透明はなし(画像から)
・出来る限りモジュールは不使用(VelocityOverLifetime、ColorOverLifetimeなど)
・色は2進数換算で1の数が少ないもの(0.5、0.25の倍数)
飛んでいる時のマグマのエフェクトだけで3つ、それを10個以上並べていたのでこの改善だけで10FPS以上改善
不必要なRigidbodyの削除
これは開発中のミス。
OnCollisionからOnTriggerにしていたのでブロック側のRigidbodyを削除。
細かすぎる最適化
Material
・RenderingModeはCutOutに
・Shaderの優先度はUnlitColor(1色塗りつぶし)→Unlit(光の描画計算しない)→Standard(光の計算する)を使用
・Smoothnessは0
空のStart、Updateは削除
ブロックのStartの呼び出し×数千で数ms使っていたので関数ごと削除
少しずつ生成する
Colliderがあると触れ始めた一番最初のフレームにチェックが走る(ちょい重い)ので、それが数千オブジェクト同士でやると数フレーム分かかっていたのでロードの時に少しずつ生成して解消
Transformのキャッシュ
Transform thisTransform;
public void Awake(){
thisTransform = gameObject.transform;
}
コンポーネント取得関係でtransformはかなり最適化されているが、それでもキャッシュには敵わないらしい。
GetComponentは激重なので同じコンポーネントはキャッシュすること
ちなみに、少人数開発だからできたことだがPlayerやManagerなど一つしかないクラスはシングルトンにし、それのpublicでstaticな変数とすることでGetComponentの数自体を減らしている。
StringBuilder
文字列同士の結合は重いのでStringBuilderを使用(詳しくは検索推奨)
主にステージエディタで使用
タグの比較はCompareTag
タグの比較は文字列比較ではなくCompareTagの方が速い
if内の&&で繋ぐものは最初に満たす確率が小さくなるように
これは条件を満たさない時に有効。
例えばnumが2の倍数かつ3の倍数かつ5の倍数であるかどうかを調べるときに
if(num % 2 == 0 && num % 3 == 0 && num % 5 == 0)
よりも
if(num % 5 == 0 && num % 3 == 0 && num % 2 == 0)
の方が5の倍数でない(80%)時点で検証が終わるので、上よりも条件を満たさない時に速くなる
Phisicsのレイヤー設定を見直す
Layer同士で衝突判定をするか設定できるので、関係ないレイヤー同士は衝突判定をしないように見直すと軽くなったりする。
ドット化シェーダーを掛けるもの(3Dオブジェクト、プレイヤーと敵)とかけないもの(ブロック)はお互いしか接触しないように設定。気休めだったがデフォルトのチェックボックスびっしりよりはましになった気がする。
その他
2023年現在、CyberAgentさんが出しているこちらの記事を見ていただく方が良いかなと
宣伝
C#の話ですが拙記事もどうぞ
https://qiita.com/Nakatomo/items/ed55196f11068689923e
そもそもUnityで最適化しても所詮C#じゃん
Unityはビルド時にIL2CPPというツールでC#をC++に変換しているので、C#ゆえに重いというのはビルド時に解消されるはずです
今はBurstという機能もあるので、モバイル端末だと使ってみると良いかもです
どうしても重いフレームは暗転で
これはどうしようもない時の裏技。画面が完全に暗い状態でならカクついても気が付かない
コライダーは最初の1フレームの処理が重いのでこれを使いました