今回はUnityの最適化に関する話題で「C#スクリプトの処理を軽くするための基本テクニック」をまとめてみるという内容です。
Unityで作ったゲームが重くなる原因はザックリ分けて
- 描画関係の処理が重い(=GPUに負荷がかかる)
- スクリプトの処理が重い(=CPUに負荷がかかる)
という2種類に大別できると思うのですが、今回は2番目の「スクリプトの処理」を軽くするためのテクニックをご紹介していきます。C#スクリプトの書き方が悪いとどんどんゲームが重くなってしまうので、心当たりのある方はぜひこれからご紹介する内容を参考にしてスクリプトを見直していただければと思います。
C#スクリプトの処理を軽くするためのテクニック8個
では早速ですがスクリプトの処理を軽くするための基本テクニックを見ていきましょう。
- GameObject.FindやFindObjectOfTypeは頻繁に使わないようにする
- GetComponentも頻繁に使わないようにする
- 距離の比較ではVector3.sqrMagnitudeを使う
- transformはキャッシュして使ったほうが良い
- Camera.mainもキャッシュして使ったほうが良い
- コルーチンの停止処理は毎回newしないでキャッシュしておく
- Tagの判定にはCompareTagを使う
- Animatorのステート指定にはStringToHashしたものを使う
GameObject.FindやFindObjectOfTypeは頻繁に使わないようにする
まず、Unity初心者の方がやってしまいがちなミスとして「GameObject.Find等をUpdate関数内に書く」といったことがあると思いますが、
- GameObject.Find
- GameObject.FindGameObjectWithTag
- FindObjectOfType
などは重い処理なので毎フレーム呼び出したりするのはやめたほうが良いでしょう(※)。
ではどうすれば良いのかというと代替策は2つあります。
- そもそもスクリプトからFindしないで、予めインスペクターに登録できるようにする
- Start関数などのタイミングで1回だけFindしてキャッシュしておく
そもそもスクリプトからFindしないで予めインスペクターに登録しておく
一つ目はゲーム実行中にスクリプトからゲームオブジェクトを検索しないで、予めインスペクターに登録しておく方法です。
対象のゲームオブジェクトが初めから分かっている場合はこの方法を使いましょう。
Start関数などのタイミングで1回だけFindしてキャッシュしておく
二つ目はStart関数などのタイミングで1回だけ検索し、それを変数にキャッシュしておく方法です。対象のゲームオブジェクトがゲーム実行までシーンに存在しない場合(=ゲーム実行時にInstantiateする等)はこちらを採用すると良いでしょう。
※余談ですが、Find系関数3種類の処理の重さを比較するとFindGameObjectWithTagが一番軽くてFindObjectOfTypeが一番重いです。
GetComponentも頻繁に使わないようにする
次にFind系の関数と同様にGetComponentも比較的重い処理なのでこちらも毎フレーム呼び出すといったことは避けるのが無難です。こちらも予めインスペクターから登録するか、またはキャッシュして使いまわすようにしましょう。
距離の比較ではVector3.sqrMagnitudeを使う
さて、ゲームを作っているとゲームオブジェクト同士の距離を比較したい場合がよくあると思います。このときはVector3.magnitudeやVector3.Distanceを使って距離を取得するのではなく、代わりにVector3.sqrMagnitudeを使うと高速です。
sqrMagnitudeのほうが高速な理由は、ベクトルの長さを算出するときの平方根の計算を行っていないからです。平方根の計算は重いのでそれがない分高速というわけですね。
なおsqrMagnitudeで取得できる値はベクトルの長さの2乗になっているので、当然ながら比較対象も2乗された値でないと間違った結果になる点には注意してください。
transformはキャッシュして使ったほうが良い
Unityでゲームオブジェクトのトランスフォームを取得するときには一般的にtransformを呼ぶと思いますが、これは内部的にはGetComponentを使っているので少し遅いです。予めキャッシュして使いまわすと良いでしょう。
Camera.mainもキャッシュして使ったほうが良い
それからtransformと似たような話で、メインカメラを取得するときにはCamera.mainをよく使うと思います。しかしこれは内部的にはメインカメラを検索するためにFindGameObjectWithTagを使っているので毎フレーム呼び出す等はやめたほうが良いです。Start関数などのタイミングでキャッシュしておくと良いでしょう。
コルーチンの停止処理は毎回newしないでキャッシュしておく
次にコルーチンの処理を一時停止するとき、例えばコルーチン内に
yield return new WaitForSeconds(1f)
などと書く場合があると思います。しかしこのコルーチンを頻繁に呼び出す場合、毎回WaitForSecondsをnewするとメモリにゴミが発生してしまい、そのゴミを片付けようと重いガベージコレクションの処理が走ってゲームのフレームレート(FPS)が下がってしまうことがあります。
ではどうすれば良いのかというと、実はWaitForSeconds等はキャッシュしておけるので、Start関数などのタイミングでキャッシュしてから使うとゴミを出さずに済みます。地味ですが覚えておくと便利です。
Tagの判定にはCompareTagを使う
Unityでゲームを作っていると、ゲームオブジェクトの「タグ」を判定して処理を分岐させることが非常によくあると思います。このとき条件式を「gameObject.tag == “Player”」などと書いてしまいがちですが、実はこれよりも「gameObject.CompareTag(“Player”)」と書いた方が高速です。
まあ等号で書いてもそこまで重くなるわけではないと思いますが、チリも積もれば山となるのでこういった細かい部分にも気を配ると良いでしょう。
Animatorのパラメータ指定にはStringToHashしたものを使う
最後にAnimatorのパラメータを変更するときは一般的に
animator.SetTrigger("Attack");
と書くと思いますが、こう書くよりも予め
int attackParamHash = Animator.StringToHash("Attack"); animator.SetTrigger(attackParamHash);
とStringToHashしておいたものを使ったほうが高速です。Animatorの場合は頻繁にパラメータを変更することが多いので役に立つと思います。
おわりに
以上、UnityのC#スクリプトの処理を軽くするための基本テクニックを8個ご紹介しました。
最適化はとても奥が深いのでまだまだ色々なテクニックがあるのですが、まずは基本的な部分を理解していただければ十分ゲームが軽くなると思います。C#スクリプトを書くときはぜひ上記のようなことを意識してサクサク動くゲームを作ってください。
この記事がゲーム制作のお役に立てば幸いです。