(:3[kanのメモ帳]

個人ゲーム開発者kan.kikuchiのメモ的技術ブログ。月木更新でUnity関連がメイン。

変更と3Dモデルの子を減らして、Transformを最適化 【Unity】【Unite 2017 Tokyo】【最適化】


このエントリーをはてなブックマークに追加

この記事でのバージョン
Unity 2017.1.0f3


はじめに

今回はTransformの最適化を行い、負荷を軽減しようというお話です。


なお、『Unite 2017 Tokyo』

「Unity最適化講座 ~スペシャリストが教えるメモリとCPU使用率の負担最小化テクニック~」

の前半部分を参考にさせてもらってます。



また、記事中では以下のアセットを使っております。





Transformとは

そもそもTransformとはなんぞやという話からですが、

全てのGameObjectに必ず一つ付いているコンポーネントで、

座標、回転、スケールを決めるためのモノです。


f:id:kan_kikuchi:20170914175908j:plain


実はこのTransform、

座標等の数値を変えるとUnity内部でOnTransformChangedというメッセージを送信します。

(MonoBehaviourでは受け取れない。)


しかも、子がいる場合には全ての子にもメッセージが送られます。(親が動くと子も動く事になるので)

例えば、100万の空オブジェクトが子にあると、Updateで移動させるだけでも相当な負荷になります。

private void Start () {
  //100万の子を作成
  for (int i = 0; i < 1000000; i++) {
    GameObject child = new GameObject("Child");
    child.transform.SetParent(transform);
  }	
}	

private void Update () {
  //毎フレーム移動させる(OnTransformChangedが発生する)
  transform.position = transform.position + new Vector3(1, 0, 0);

 //localPositionでもOnTransformChangedは発生する
 //transform.localPosition = transform.localPosition + new Vector3(1, 0, 0);
}

f:id:kan_kikuchi:20170915062903j:plain


このようにメッセージが増大すると処理時間が増える事になり、Transformの最適化が必要になります。


ちなみに親子関係が変わると、そのTransformと全ての子は

OnBeforeTransformParentChanged, OnTransformParentChanged

という二つのメッセージ(MonoBehaviourで受け取れる。)が送られるため、

親を頻繁に変更したり、子が大量にいるのオブジェクトの親を

変更したりすることも負荷の増加に繋がります。


Transformの変更を減らす

Transformを最適化する一つ目の方法は、Transformの変更を減らす事です。

なので、以下のように1フレーム内で何度も変更せずに、

変更する値を貯めておき、Transformの変更は一度だけにしましょう。

/*改善前(分かり易いようかなりシンプルな例です)*/
private void Update () {
  //移動(OnTransformChanged発生)
  transform.position = transform.position + new Vector3(1, 0, 0);

  //移動(OnTransformChanged発生)
  transform.position = transform.position + new Vector3(1, 0, 0);

  //移動(OnTransformChanged発生)
  transform.position = transform.position + new Vector3(1, 0, 0);
}
/*改善後*/
private void Update () {
  //変更を貯めておき、transformの変更は一度だけにする
  Vector3 newPosition = new Vector3(1, 0, 0);
  newPosition.x += 1;
  newPosition.x += 1;
  transform.position = newPosition;

  //もちろん以下でも良い
  //transform.position = transform.position + new Vector3(3, 0, 0);
}


また、Unity5.6で追加されたSetPositionAndRotationを使うと座標と角度を同時に設定できるので、

OnTransformChangedの発生回数を減らす事が出来ます。

/*改善前*/

//移動(OnTransformChanged発生)
transform.position = new Vector3(1, 0, 0);

//回転(OnTransformChanged発生)
transform.rotation = Quaternion.Euler(90, 0, 0);
/*改善後*/

//移動と回転を同時に行う(OnTransformChangedは1回だけ発生)
transform.SetPositionAndRotation(new Vector3(1, 0, 0), Quaternion.Euler(90, 0, 0));



Transformを減らす

Transformを最適化する二つ目の方法は、Transform自体を減らす事です。

そもそもTransformが大量に生成されるのはどこかと言えば、Rigの付いた3Dモデルです。


例えば、以下のモデルの場合は157個ものTransformで構成されています。

これは決して特別な例ではなく、Rigの付いたモデルをインポートするとこれぐらいにはなります。


f:id:kan_kikuchi:20170915070352j:plain


このキャラが移動やアニメーションをすれば、子の分だけメッセージが発生しますし、

さらにキャラの数が増えれば、それだけメッセージも増えます。


実はこれを簡単に解決する方法があります。

それはモデルをProjectで選択し、InspectorからOptimize GameObjectsにチェックを入れるだけ。


f:id:kan_kikuchi:20170915071721j:plain


こうする事で、インポート時に追加される余分なTransformを削除し、

かつ、マルチスレッドで使いやすようにアニメーションデータを並び替える事が出来ます。


先ほどのモデルだと、Transformはたったの一個になりました。

もちろんアニメーションも問題なく実行できます。


f:id:kan_kikuchi:20170915071908j:plain


ただし、これを行うと頭や手などの特定の部位の位置が分からなくなります。

そういう場合は、残したいパーツのTransformをExtra Transforms to Exposeに設定しましょう。


f:id:kan_kikuchi:20170915072301j:plain
f:id:kan_kikuchi:20170915072309j:plain


なお、これらの設定を変更すると、Prefabを作り直す必要があるので注意が必要です。