###環境
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); の第二引数で時間を決めます。
public class InvokeTimerTest : MonoBehaviour
{
[SerializeField, Range(0.01f, 1.0f)] float Time;
List<long> results = new List<long>();
Stopwatch sw = new Stopwatch();
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
sw.Start(); //計測開始
Invoke("TestInvoke", Time);
}
}
void TestInvoke()
{
sw.Stop();
UnityEngine.Debug.Log($"TestInvoke()[{results.Count + 1}]:{sw.ElapsedMilliseconds}ms");
results.Add(sw.ElapsedMilliseconds);
sw.Reset();
}
}
##Coroutineのテストソース
yield return new WaitForSecondsRealtime(Time);の第一引数で時間を決めます。
public class CoroutineTimerTest : MonoBehaviour
{
[SerializeField, Range(0.01f, 1.0f)] float Time;
List<long> results = new List<long>();
Stopwatch sw = new Stopwatch();
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
sw.Start(); //計測開始
StartCoroutine(TestCoroutine());
}
}
IEnumerator TestCoroutine()
{
yield return new WaitForSecondsRealtime(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以内」で達成出来そうです。「この精度で十分かどうか」、それはもちろん使う用途によりますが、このデータが判断の手助けになれば幸いです。