この記事は、
プレハブをInstantiateしてゲームオブジェクトを生成する際に、
遅い・画面が止まるといった問題に対して自分が行っているアプローチを書きます。
【箱猫倉庫ver1.3(次回アプデ)】
— snoopopo@個人ゲーム開発中 (@snoopopo) December 5, 2021
古い端末だとunityロゴ~タイトル表示まで遅い問題あってようやく修正。
原因は重いプレハブのインスタンス化に時間かかってた。
知見が増えたのでどっかでまとめます:)
画面汚ないのは勘弁w😖#ゲーム制作 #gamedev #IndieGameDev #indiegame #ゲーム開発 #unity pic.twitter.com/bqBZx1qb1T
- 何が遅いのか原因が分かってない場合
- インスタンス生成時ではなくプレハブのロードが遅いのかも
- プレハブをロードするのが遅い場合
- インスタンス生成が遅い場合
- 画面が止まっている間「Loading・・・」を出して待ってもらう
何が遅いのか原因が分かってない場合
「なんか遅いけど原因がプレハブかどうかわからない」場合は、まずはどこの処理が遅いかを特定しましょう。
特定に一番簡単なのは、遅いときに走る処理に現在日時を表示するデバッグログを仕組むことです。
Debug.Log(DateTime.Now + ":" + DateTime.Now.Millisecond); //処理1 Debug.Log(DateTime.Now + ":" + DateTime.Now.Millisecond); //処理2 Debug.Log(DateTime.Now + ":" + DateTime.Now.Millisecond);
(DateTime.Now
はミリ秒を表示しないのでDateTime.Now.Millisecond
を追加しています)
これでどの処理に時間がかかっているかが特定できます。
「プレハブのインスタンス生成に時間がかかってる!」と特定できた場合はこのまま読み進めてください^^
インスタンス生成時ではなくプレハブのロードが遅いのかも
プレハブをInstantiateしてゲームオブジェクトを生成する、をもう少し細かく書くと、
・プレハブをロード
・プレハブをInstantiateしてゲームオブジェクトを作る
という二つのステップを踏むと思います。
まずはこのどちらかが遅いかを確認しましょう。
プレハブをロードするのが遅い場合
ソースからロードするのではなくあらかじめアタッチしておく
ロードするタイミングを自分でコントロールしなくてもいい場合、
あらかじめプレハブをアタッチしておくことで改善できる場合があります。(エディタにロードをまかせる)
これで解決するならこれが一番簡単かも。
public class TestPrefabCreater : MonoBehaviour { public GameObject prefab; }
非同期でロードする
ここでいう非同期とは、
1フレーム:プレハブをロード(1秒)→表示更新 ⇒1秒画面が固まる
だったものを、
1フレーム:プレハブの一部をロード(0.1秒)→表示更新 ⇒0.1秒しか画面が止まらない これを10フレーム繰り返す
ようなイメージです。非同期にすると複数フレームかけてプレハブ読み込むということ。
プレハブをResources
フォルダにいれている場合、Resources#LoadAsync
で非同期ロードを実現できます。
アセットバンドルを使っている場合はAssetBundle#LoadAssetAsync
があります。
例(複数ある場合)
「Test1~5」という名前の5つのプレハブのロードに時間がかかっているとします。
private void Load() { for (int i = 1; i <= 5; i++) { GameObject obj = Resources.Load("Test" + i) as GameObject; } }
これを非同期でロードするには Resources.Load
をResources#LoadAsync
に置き換えます。
private IEnumerator LoadAsync() { for (int i = 1; i <= 5; i++) { ResourceRequest resourceRequest2 = Resources.LoadAsync<GameObject>("Test" + i); while (!resourceRequest2.isDone) { yield return null; //まだロード終わってない } Debug.Log("Test" + i + "のロード終わった"); } Debug.Log("全部のロード終わった"); }
これで、
Test1のロード開始→Test1のロード完了→Test2のロード開始→Test2のロード完了・・・
という風に処理されます。
while (!resourceRequest2.isDone) { yield return null; //まだロード終わってない }
ロードがまだ終わっていない時にyield return null;
を呼ぶことで、
ロードが終わるまで次のプレハブをロードしないようになります。
これがないと1フレームの内に5つ分のロードのリクエストが走ってしまうため、忘れないように注意!
インスタンス生成が遅い場合
インスタンス化するオブジェクトの数を減らす
たくさんのオブジェクトをインスタンス生成していて遅い場合、
本当にそのオブジェクトは生成する必要があるのかを改めて考えます。
画面に見えてる範囲だけ順次生成する
例えば、画面に表示されないものもインスタンス生成している場合は、
表示されるものの分だけ生成するということで一度に作る量を減らすことができます。
【開発中のゲーム進捗】
— snoopopo@個人ゲーム開発中 (@snoopopo) September 14, 2019
今日はダンジョン画面へ遷移するのが、重かったのを改善できた:)
ダンジョン構成用のGameObject全部一度に生成していたけど、ゲームに見えてる範囲だけ順次生成する作りにしました!#gamedev #unity #indie #IndieGameDev #indiedev #indiegame pic.twitter.com/ujF1wDYPAW
生成したオブジェクトを使いまわす
一つのプレハブを100個複製するようなゲームの場合、
画面に見えていないゲームオブジェクトを使いまわすということも考えられます。
【開発中のゲーム進捗:KBT】
— snoopopo@個人ゲーム開発中 (@snoopopo) September 23, 2019
今日もパフォーマンス改善。
今回は生成したオブジェクトをpoolして使いまわす作りに。
プレイヤーキャラが止まってる間は実機でも30fps出るようになった:)
移動中はPCでも30fps出ないのでまだ改善が必要だなー#gamedev #unity #indie #IndieGameDev #indiegame pic.twitter.com/9Xq2fsIRDq
プレハブ内を分けて非同期で順次インスタンス化する
1つのプレハブのインスタンス生成に時間がかかっている場合、
プレハブを分割してインスタンス生成することが検討できます。
▼worldプレハブ内のインスタンス生成が遅い… →子オブジェクトのworld1~6を非同期で順次生成しよう!
スクリプトで
worldプレハブ全体をインスタンス化すると遅いので、
worldプレハブの直下のゲームオブジェクト(world1~6)を非同期で順次生成します。
1フレ:world1→2フレ:world2→3フレ:world3 ・・・
となります。
修正前はworld1~6すべてを生成し終わるまで画面が止まってしまっていますが、
修正後は、world1が生成し終える時間しか止まらなくなります。
private void Start() { GameObject worldGobj = new GameObject("world"); StartCoroutine(InstanceChildAsync(worldPrefab, worldGobj)); } public IEnumerator InstanceChildAsync(GameObject prefab, GameObject parent) { foreach (Transform prefabChild in prefab.transform) { Instantiate(prefabChild, parent.transform); yield return null; } }
プレハブ自体を分割
上はスクリプトで、子オブジェクトを順次生成していますが、
予めプレハブ自体を分割しておくことでも実現できます。
▼worldプレハブ内のworld1~6をそれぞれ別のプレハブにしておき、順次生成する
画面が止まっている間「Loading・・・」を出して待ってもらう
パフォーマンス改善を直接するのではなく、
画面が止まっている間「Loading…」を出してユーザーに待ってもらうというのも全然あります!