Unityでゲームを作っていると周期的に処理したり、ユーザーからの入力を処理するためにUpdateメソッドを使っていると思います。Unityを使い始めのころは「周期的に使うならUpdateメソッドを使う」ぐらいの認識しかありませんでした。
そんなある日、ふと、Updateメソッドってどれぐらいの周期で動いているの?と疑問に思い始めました。フレーム毎に動いているという認識はあったのですが、その「フレーム」についても、「ゲームを処理する周期的なタイミング」ぐらいにしか思っていませんでした。
簡単なゲーム作ったり、ユーザーからの操作がシビアじゃない場合は、それでも大丈夫なのですが、こういう基本を疎かにすると後々苦労するので、ちゃんと押さえておこうということで実際にプログラム作ったりして調べました。
ということでUnityのフレームレートについての説明をします。その上で周期的な処理をするために重要なUpdateとFixedUpdateの違いや使い分けについて説明していこうと思います。
Unityのフレームレートやリフレッシュレートについて知ろう
フレームレートについて理解するためには、まずリフレッシュレートについて理解しておく必要があります。そのためリフレッシュレートについて説明した後、フレームレートについて説明します。
リフレッシュレートとはモニターが表示している情報を更新する周期のこと
今あなたが見ているモニターは一見、何の遅延もなく表示しているように見えると思いますが、実際のところはあなたが認識できていないだけで、かなり短い周期で表示更新されています。この更新周期をリフレッシュレートと言います。
私のモニターのリフレッシュレートは下図のようになります。
約60Hzなので1秒に60回(約17msに1回)モニターに表示されている情報が更新されています。17msの差を認識できる人はいないと思います。Wikipediaでは180ms~200msぐらいが人間が認識できる速度としています。
このリフレッシュレートはデバイスによって違います。つまりあなたが普段使っているPCやスマホのリフレッシュレートは違うということです。
リフレッシュレートについて何となくでも理解できたなら次はフレームレートです。
フレームレートは実は可変な周期である
Updateメソッドは1フレーム毎に呼び出されるメソッドであることは認識していると思います。しかし、これ以上を理解しようとする場合には、Unityのイベント関数の実行順番を理解しなければなりません。(Unityのイベント関数の実行順番の詳細は公式のこちらのぺージを読んでください。)
Updateメソッドはレンダリング(表示更新)の前に呼び出されるメソッドです。
ここで「なるほど、Updateメソッドはモニターに表示される前に呼び出されるものなのか」と理解したのなら、「それは違うよ」と言っておきます。
プログラムが表示更新することと、モニターが画面表示するタイミングは違うということを理解する必要があります。
ザックリとプログラムの処理とモニターの表示の流れを描くと次のようになります。
- プログラムで表示するデータを作る。
- グラフィックボードに表示するデータが書き込まれる
- モニターに表示される。
この1の部分の周期がUpdateメソッドが呼ばれる周期、つまりフレームレートになります。で、3で更新される周期がリフレッシュレートということになります。
ここで言いたいのは、モニター(表示機器)のスペックによって、最適なフレームレートが変わり、Unityは裏で最適になるようにフレームレートを決めているということです。
Unityはフレームレートをリフレッシュレートと同期させる垂直同期という機能があり、この機能をONにしているとフレームレートが使っているデバイスによって変わってくるんですね。じゃあ、OFFにしているとどうなるかというと、「可能な限り早く」という、ほぼ不定の周期で実行されるようになります。
冒頭で話した通り、簡単なゲームだとUpdateの周期が何秒とか気にしないでいいので、Unityが勝手に最適動作してくれるなら何の問題もありません。
ただ、この仕様だと、どうしても安定した周期でプログラムを動かしたいという場合に困ります。そこで登場するのがFixtedUpdateメソッドとなります。
UpdateとFixedUpdateの違いや使い分けを解説!
結論から言うと「Updateメソッドは1フレームに1回呼ばれるメソッドで、FixedUpdateは1フレームに「フレーム間の時間/固定周期」回呼ばれるメソッド」です。
そのため使い分けとしては「より安定した周期で処理したい場合にはFixedUpdateを使い、動作周期がある程度ファジィでもいいならUpdateを使うと良い」です。
この言葉を理解してもらうために以下説明していきます。
FixedUpdateが動くタイミングはUpdateの前である
おそらくFixedUpdateって固定の周期で動いてくれるメソッドなんだな、と理解している人も多いと思います。この理解で大きくは間違いではないんですが、実は本当の動きはおそらくイメージしているものと違うんですよね。
Unityのイベント関数の実行順番のページに記載の図をUpdateとFixedUpdateに関係ある部分だけを抜粋すると次のようになります。
ここで④→①がフレームレートの周期で回るループになります。③→②がFixedUpdateの周期で回るループです。そして、②に行くためには④→①が回らないといけません。
つまりFixtedUpdateが動くタイミングはフレーム毎ということになり、先ほども説明したようにフレームレートは可変なのでFixtedUpdateメソッドも固定周期では動いていないのです。
フレームレートを10でFixtedUpdateの周期を0.05で動作確認する
フレームレートを10にすると、100ms周期で1回Updateが動きます。FixedUpdateの周期を0.05にすると、50ms周期でFixtedUpdateが動きます。
ソースコードは次のような感じで、UpdateメソッドとFixtedUpdateメソッドが動くタイミングで現在の秒+ミリ秒を表示します。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class FixedUpdateTest : MonoBehaviour { private int cnt = 0; private int fixcnt = 0; private void Start() { Application.targetFrameRate = 10; } private void FixedUpdate() { fixcnt++; int millisecond = DateTime.Now.Second * 1000 + DateTime.Now.Millisecond; Debug.Log("FixedUpdate: " + millisecond + " " + fixcnt) ; } // Update is called once per frame void Update() { cnt++; float fps = 1f / Time.deltaTime; int millisecond = DateTime.Now.Second * 1000 + DateTime.Now.Millisecond; Debug.Log("Update: " + millisecond + " fps : " + fps); } }
実行した結果が次のようになります。
赤下線と、緑下線にミリ秒を表示しています。赤下線の所を見ると分かるように、Updateメソッドの前にFixtedUpdateメソッドが2回連続して呼ばれています。
図にすると下のようになります。黒い点の所が実行ヵ所になります。実際の動作もUpdateメソッドが100ms周期で呼ばれていますが、FixtedUpdateメソッドは50ms周期では呼ばれておらず、Updateメソッドの前に2回連続して呼ばれています。
つまり結論で言った「FixedUpdateは1フレームに「フレーム間の時間/固定周期」回呼ばれるメソッド」というのは、1フレームに「100/50(つまり2)」回呼ばれるということです。
注意点としては、例に挙げたように1フレームに2回呼ばれるものではなく、中途半端な数字だった場合はどうなるのか?ということです。例えば、フレームレート10で固定周期が30msみたいな場合です。
1フレームにFixedUpdate3回実行して90msで残りの10msは蓄積されて、1フレームに4回実行されるフレームが現れます。実際に実行すると下図のような感じです。
以上のように、FixedUpdateメソッドは実時間で見た時に、一定の周期で動いているようなことはないんですね。
それじゃあFixedUpdateメソッドの固定周期とはいったい何のか?
このことについて次に説明していきます。
UnityのFixedUpdateの固定周期とは離散的な時間のこと
先ほどの実行結果からはFixedUpdateメソッドが1フレームで連続して呼ばれていること以外に、フレーム間で処理されなかった時間が蓄積されていることから「フレームの周期を見ている軸とは別の時間軸で時間の計算はされている」とことがわかります。
つまりフレームのことや現実世界の時間を取っ払って考えると、FixedUpdateメソッドはちゃんと固定周期毎に1回動いていると見えます。
これだけじゃあわかりにくいので、例を挙げます。
例えば、1cm/msで物体が移動したときに時間経過とともに進んだ距離を移動したかを計算する処理をFixedUpdateメソッドで50ms周期で動かすとしましょう。プログラム的には次のような感じです。
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; public class FixedUpdateTest : MonoBehaviour { private void Start() { Application.targetFrameRate = 10; } private int distance = 0; private int fixcnt = 0; private void FixedUpdate() { distance += 50; Debug.Log(distance); } }
実行結果を見ると次のようになります。
このように実時間を考えず、あくまでFixedUpdateが実行される軸で見た場合、固定周期で実行されているといえます。
つまりFixedUpdateメソッドの周期が連続した時間軸の周期ではなく、離散的な時間軸の周期であるというわけです。この周期の考え方を利用することで、周期が不定のUpdateメソッドではなく、実時間に対して実行する回数を決められるFixedUpdateメソッドの方が、例であげたような時間を扱う物理演算がしやすくなります。
といってもわかりにくいですよね。この辺の考え方は、離散数学を理解している人か物理シミュレーションをするようなプログラムを組んだことがある人だとイメージしやすいのですけどね・・・。
結論:難しいことは置いといてUpdateメソッドを使おう
Unityのフレームレートの説明と、UpdateメソッドとFixedUpdateメソッドの違いについてまとめました。
私のように個人でゲーム制作をしている人で、格闘ゲームとかFPSみたいなのを作っていないようであれば、FixedUpdateメソッドの出番はそんなにない気がします。
というわけで、結論、Updateメソッドを使おう。どうしても時間を意識する場合は、フレームレートを固定資にてUpdateメソッドで処理するか、FixedUpdateメソッドは実時間の定周期で動かないことを認識してプログラムを実装しましょう。