Help us understand the problem. What is going on with this article?

UnityにおけるInvokeとCoroutineの精度比較

環境

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); の第二引数で時間を決めます。

InvokeTest.cs
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);の第一引数で時間を決めます。

Coroutine.cs
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以内」で達成出来そうです。「この精度で十分かどうか」、それはもちろん使う用途によりますが、このデータが判断の手助けになれば幸いです。

T-Okano
ハードもソフトも機構も幅広くやる人。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした