環境
windows10
Unity 2019.3.15f1
指定時間後に動作する処理を書きたい
⇒高精度を求めるならCoroutine一択!※
理由は下記の記事になります。
※2020/06/30 FPSに依存するようです。高精度ではなく、Updateと同じ精度と考えるのが良さそうです。
InvokeとCoroutine
「Unity 指定時間後」で調べるとこの二つが出てきます。
【Unity】スクリプトの処理の実行タイミングを操作する
↑こちらの記事がわかりやすいです。
精度はどうなの?
「BPM200の16分音符を鳴らしたい=音を出してから75ms後に音を止めたい」
これが私にとっての課題です。InvokeもしくはCoroutineはこの課題を解決してくれるのでしょうか。
測定をする
測定にはStopwatchクラスを使用します。今回の測定を行うには十分に高精度です。
どちらも「スペースキー」が押されるとタイマースタート、
一定時間後にタイマーストップをするプログラムです。
Invokeのテストソース
Invoke(xxx, Time); の第二引数で時間を決めます。
publicclassInvokeTimerTest:MonoBehaviour{[SerializeField,Range(0.01f,1.0f)]floatTime;List<long>results=newList<long>();Stopwatchsw=newStopwatch();privatevoidUpdate(){if(Input.GetKeyDown(KeyCode.Space)){sw.Start();//計測開始Invoke("TestInvoke",Time);}}voidTestInvoke(){sw.Stop();UnityEngine.Debug.Log($"TestInvoke()[{results.Count+1}]:{sw.ElapsedMilliseconds}ms");results.Add(sw.ElapsedMilliseconds);sw.Reset();}}
Coroutineのテストソース
yield return new WaitForSecondsRealtime(Time);の第一引数で時間を決めます。
publicclassCoroutineTimerTest:MonoBehaviour{[SerializeField,Range(0.01f,1.0f)]floatTime;List<long>results=newList<long>();Stopwatchsw=newStopwatch();privatevoidUpdate(){if(Input.GetKeyDown(KeyCode.Space)){sw.Start();//計測開始StartCoroutine(TestCoroutine());}}IEnumeratorTestCoroutine(){yieldreturnnewWaitForSecondsRealtime(Time);sw.Stop();UnityEngine.Debug.Log($"TestCoroutine()[{results.Count+1}]:{sw.ElapsedMilliseconds}ms");results.Add(sw.ElapsedMilliseconds);sw.Reset();}}
結果
Timeの値を変えて、各条件で100回実行した結果になります。
条件1.Time=100msの場合
最大値 | 最小値 | 平均 | 標準偏差 | |
---|---|---|---|---|
Invoke | 104 | 83 | 93.79 | 6.02 |
Coroutine | 108 | 100 | 102.50 | 1.67 |
条件2.Time=50msの場合
最大値 | 最小値 | 平均 | 標準偏差 | |
---|---|---|---|---|
Invoke | 57 | 34 | 44.28 | 7.06 |
Coroutine | 57 | 52 | 55.8 | 0.89 |
条件3.Time=10msの場合
最大値 | 最小値 | 平均 | 標準偏差 | |
---|---|---|---|---|
Invoke | 18 | 0 | 4.03 | 5.13 |
Coroutine | 25 | 10 | 17.54 | 2.24 |
条件4.Time=500msの場合
最大値 | 最小値 | 平均 | 標準偏差 | |
---|---|---|---|---|
Invoke | 506 | 480 | 494.23 | 6.28 |
Coroutine | 509 | 500 | 503.58 | 2.97 |
考察
1. 誤差
Invokeの誤差は、+に8ms, -に20msが最大です。
Coroutineの誤差は、+に15ms, -に0msが最大です。
偏差を比べてもCoroutineの方が小さい傾向にあります。
1ms単位とは言いませんが、±10ms程度のタイマーとしては有効ではないでしょうか。
2. 最小値
Coroutineでは最小値が設定値を下回る事がありません。タイマーの時間が過ぎたら実行するプログラムと考えると納得がいきます。しかし、Invokeの最小値は設定値を下回っています。おおよそ60Hzの1フレーム分くらい早めに判定される事があるようです。(理由はよくわからないので、詳しい方補足をお願いします。)
3. Timeの限界値
Time=10msではInvokeの最小値が0になる、Coroutineの誤差も最大+15ms(つまり、目標値の2.5倍)となり、正確な値が取れているとは言い難い状況です。Coroutineの方が精度が良いとはいえ、Time=50msでも約15%の誤差がありますので、過信は禁物です。どちらの誤差も総時間によらない固定値にも思えますので、長時間になればなるほど誤差率は下がりそうです。
まとめ
Invokeでもシビアじゃない環境では十分に使える精度で動作しますが、Coroutineの方が精度よく引数も使えると利点があります。私の課題「75ms音を出す」は「誤差+10ms以内」で達成出来そうです。「この精度で十分かどうか」、それはもちろん使う用途によりますが、このデータが判断の手助けになれば幸いです。