Unityで毎フレーム実行している処理の回数を減らす

今回はUnityで毎フレーム実行している処理の実行回数を減らすということをしてみたいと思います。

毎フレーム実行すると言えばUpdateメソッドですが、Updateメソッドで何らかの処理をする他のメソッドを呼び出すようにすると1秒間に何十回もそのメソッドが実行されます。

呼び出したメソッドの処理が重いものであれば毎フレーム処理を実行するとゲームの処理速度が落ちてしまう事があります。

そんなわけで今回はその実行回数を減らすということをしていきます。

スポンサーリンク

Updateメソッドで何らかのメソッドを呼んでFPSが落ちるのを確認する

FPSを確認する為にシーン上にテキストUIを設置しTextにAssets/StandardAssets/Utility/FPSCounterを取り付けて確認出来るようにしておきます。

まずはUpdateメソッドで処理が重い別のメソッドを毎回呼び出すようにし、FPSを確認してみます。

上のようなスクリプトを作成し、UpdateメソッドでDoSomethingメソッドを毎回呼び出すようにしました。

DoSomethingメソッドでは空の文字列に0~4999までの数値を文字列に変換し、concatenatedStringという変数に足す処理をしています。

012345・・・・

という文字列が作られるという何ら意味のない重い処理です。

パソコンによっては、この5000の数値だとUnityまたはパソコンがフリーズする可能性もあるので低い値を設定してください。

Updateメソッドが呼ばれる度にDoSomethingメソッドが実行される為FPS(1秒間に実行されるフレーム数)が非常に落ちます。

こういった重い処理を毎フレーム実行するとゲームの処理落ちが発生するので、マイフレーム実行する必要がない時はこの実行回数を少し減らしたいところです。

実行してみると以下のようにFPSが極端に落ちてしまいます。

重い処理をUpdateで行うと処理が重くなってしまう

一定時間毎にメソッドを実行する

Updateメソッドで毎回処理を実行すると処理速度が落ちてしまうので、実行する回数を減らしてみます。

一定時間毎にメソッドを実行するには、自前で時間を計測してその時間を越えたらメソッドを実行するようにするか、コルーチンで時間を指定してメソッド実行するようにします。

コルーチンに関しては

Unityでコルーチンを使って定期的に処理をする
Unityでコルーチンを使うと定期的に何らかの処理を行える事が出来るようになります。一見解り辛いけど使い方がわかれば便利かも!?

を参照してみてください。

自前で時間を計測してメソッドを実行

まずは自前で時間を計測して一定時間後にメソッドを実行してみます。

上のようにマイフレームTime.deltaTimeをelapsedTimeに足していき、timeToDoSomethingの時間以上になったらDoSomethingメソッドを実行するようにします。

elapsedTimeは0に初期化するので、またtimeToDoSomethingの時間以上になるまでDoSomethingは実行しません。

コルーチンを使って一定時間後にメソッドを実行

次にコルーチンを使って一定時間後にメソッドを実行するやり方です。

上のようにtomeToDoSomethingで指定した秒数が経過したらStartCoroutineを使ってDoSomethingメソッドを実行するようにします。

DoSomethingメソッド内ではwhileを使って無限ループで処理を実行した後にyield return new WaitForSecondsを使ってtimeToDoSomething秒待機した後再び処理を実行しています。

これらを実行してみます。

timeToDoSomethingを1にして実行すると1秒毎にDoSomethingメソッドが実行され、その時だけFPSが極端に下がります。

DoSomethingを実行した時だけFPSが下がる

敵をサーチする機能を別のやり方で作成する

以前、敵が主人公を検知したら追いかけるようにする機能を作成しました。

Unityで主人公キャラが近づいてきたら追いかける敵キャラを作る
Unityで主人公キャラクターが敵キャラクターに近づいたら敵が主人公に近づいてくるようにしたいと思います。

上の記事では敵の子要素にSearchAreaという空のゲームオブジェクトを作成し、それにSphereColliderを取り付けて主人公を検知するようにしています。

主人公を検知する時にOnTriggerStayを使っていたとしたら毎フレーム何らかのコライダが範囲内にいるかどうかを判定します。

毎フレーム確認するので処理が重くなりますね。

また、別の記事でRigidbodyを持たないコライダで敵を検知する方法だと処理が重いというコメントを頂きましたので、それについて書いていきます。

静的コライダだと処理が重くなる?

敵をサーチする機能をあらかじめ取り付けたコライダだけで判断すると処理が遅くなる可能性があります。

これはRigidbodyを持たないコライダ(静的コライダ)は本来動く事を想定せず最適化されている為で、動かすとその分余計な処理が必要になる為です。

UnityメニューのWindow→Analysis→Profilerで動作を確認してみます。

敵を100体ほど登場させると

UnityのStatic Colliderの処理の重さを確認

上のように敵の数のSearchArea分のStatic Colliderが判定され、その分余計な処理が増えています。

ゲームオブジェクトを動かす事を前提としている場合は

Rigidbody+IsKinematicをfalse(物理的な作用をする)
Rigidbody+IsKinematicをtrue(物理的な作用をしない)

にしておくと良いようです。

なのでSearchAreaを敵の検知範囲として使用する場合はRigidbodyコンポーネントを取り付けた上でIsKinematicにチェックを入れるといいかも?
(ただRigidbody自体が重い処理のようで、逆に付けない方が早いこともあるかもしれません)。

サーチエリアのコライダにキネマティックなRigidbodyを取り付ける

ここら辺はUnityマニュアルを参照してください。

コライダー - Unity マニュアル
Collider コンポーネントは、物理衝突のためのオブジェクト形状を定義します。コライダーは目に見えないので、オブジェクトのメッシュと正確に同じ形状にする必要はありません。実際、大ざっぱにあわせた方がゲームではより効率が良く、違いもわかりません。

サーチエリアにコライダを使うよりも一定時間ごとにサーチする相手が指定した範囲内にいるかどうかを判定した方が処理が早くなる可能性があります。

なので別の方法でキャラクターをサーチする方法を作成してみます。

自前で時間を計測しキャラクターをサーチする方法

まずは自前で時間を計測する方法です。

敵のゲームオブジェクトに取り付けます。

CheckEnemyZombieは敵の行動処理をするスクリプトとして作っていて、SearchCharacter2スクリプトと同じ敵のルートのゲームオブジェクトに取り付けられているとします。

zombieCheckIntervalは敵が主人公を検知する間隔時間を設定します。

elapsedTimeは経過時間を入れます。

searchRadiusは検知する球の半径を設定します。

UpdateメソッドでelapsedTimeがzombieCheckIntervalを越えた時にCheckCharacterメソッドを呼び出します。

CheckCharacterメソッドではPhysics.OverlapSphereを使って敵の位置から半径searchRadiusの球の範囲内にいるPlayerレイヤーが設定された全てのコライダを取得します。

取得したコライダがひとつでもあれば敵の状態とコライダの配列の0番目の敵のTransformを引数として渡し、敵のSetStateメソッドを呼び出して状態を追いかける状態にします。

範囲内のPlayerレイヤーを持つゲームオブジェクトのコライダを全て取得する事が出来ますが、追いかけるのは一体だけなので配列の0番目のTransformを渡してそのゲームオブジェクトを追いかけるようにしています。

検知した全てのPlayerレイヤーを持つゲームオブジェクトに何らかの処理をさせる場合もforeachを使って実行出来そうですね。

コライダがひとつもなければ敵の状態をノーマル状態にします。

敵の移動スクリプトや敵の状態を変更する処理はは他の記事に書いてあるのでブログ右上の検索窓で検索し、参照してみてください。

主人公にしたキャラクターのCharacterController等のコライダを設定したゲームオブジェクトのレイヤーは必ずPlayerにしておきます(でないと検知出来ません)。

Playerレイヤーがない場合は作成し設定します。

操作キャラクターのレイヤーをPlayerにする

コルーチンを使ってキャラクターをサーチする方法

コルーチンを使った方法もやっていることは同じです。

処理速度を確認

キャラクターの子要素にサーチエリアを作り、キネマティックなRigidbodyを取り付けたコライダで判定する場合と、コルーチンなどを使って一定時間ごとに範囲内に敵がいるかどうかを判定する方法でどれだけ処理速度が変わるか確認してみました。

キネマティックなRigidbody+コライダとスクリプトで判定した動作処理の速さの比較

上のようになりました。

比較してみると全然処理速度が違いますね(^^)/

今回の場合は一定時間後にPhysics.OverlapSphereを使ってその範囲内にPlayerレイヤーを持つコライダがいるかどうかで判定していますが、単純に探す相手が決まっている場合はその相手との距離を計算し、一定の距離内にいる場合は追いかけるという方法も出来そうですね。

参考サイト

Unityマニュアルーコルーチンー

コメント

タイトルとURLをコピーしました