138
108

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Unity】一定時間後にスクリプトの処理を呼び出す方法まとめ

Last updated at Posted at 2021-02-28

この記事について

古い記事のPVが未だにそれなりにあるので、2021年現在のやり方で書き直してみました。 2024年版として書き直しました。

やりたいこと

「スクリプトの実行タイミングを操作したい」です。
つまり一定時間後に指定した処理を実行するといった処理をどう書くか紹介します。

先に結論

UniTaskを使いましょう。

Unity標準機能だけでも実現はできますが、柔軟性が無かったりパフォーマンスが出ない場合があります。
UniTaskを使えばだいたい解決します。

標準機能のみで書く場合

外部ライブラリを用いず、UnityやC#の機能のみで記述する場合のやり方。

処理をN秒後に実行したい

Invokeを使う

MonoBehaviour.Invoke()メソッドを使うことで指定した処理を一定秒数後に呼び出すことができます。
引数には対象のメソッド名を指定します(nameofで指定すると便利)

実行をキャンセルする場合はCancelInvoke()を使います。

using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        //DelayMethodを3.5秒後に呼び出す
        Invoke(nameof(DelayMethod), 3.5f);
    }

    void DelayMethod()
    {
        Debug.Log("Delay call");
    }

    private void OnDestroy()
    {
        // Destroy時に登録したInvokeをすべてキャンセル
        CancelInvoke();
    }
}

コルーチン

コルーチンを利用することでも一定時間後の処理を実行するという書き方ができます。
Invoke()よりも細かい制御が効きます。

using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // コルーチンの起動
        StartCoroutine(DelayCoroutine());
    }

    // コルーチン本体
    private IEnumerator DelayCoroutine()
    {
        transform.position = Vector3.one;

        // 3秒間待つ
        yield return new WaitForSeconds(3);

        // 3秒後に原点にワープ
        transform.position = Vector3.zero;
    }
}

また、「デリゲート」という機能を組み合わせることで使い回しが効くようにできます。

using System;
using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        transform.position = Vector3.one;

        // コルーチンの起動
        StartCoroutine(DelayCoroutine(3, () =>
        {
            // 3秒後にここの処理が実行される
            transform.position = Vector3.zero;
        }));
    }

    // 一定時間後に処理を呼び出すコルーチン
    private IEnumerator DelayCoroutine(float seconds, Action action)
    {
        yield return new WaitForSeconds(seconds);
        action?.Invoke();
    }
}

async/await + 標準Task(ValueTask)

C#のasync/awaitという機能をつかってコルーチンに似た処理が書けます。
(ただしキャンセル周りが若干ややこしいので注意 :参考

using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class TaskSample : MonoBehaviour
{
    private void Start()
    {
        // 非同期メソッド実行
        // 「destroyCancellationToken」でこのGameObjectに紐づいた
        // CancellationTokenが取得できる
        _ = DelayAsync(destroyCancellationToken);
    }

    // 非同期メソッド
    private async ValueTask DelayAsync(CancellationToken token)
    {
        transform.position = Vector3.one;

        // 3秒間待つ
        await Task.Delay(TimeSpan.FromSeconds(3), token);

        // 3秒後に原点にワープ
        transform.position = Vector3.zero;
    }
}

async/await + Awaitable(Unity 2023.1~)

Unity 2023.1以降のバージョンであればAwaitableというAPIが利用できるようになっています。
こちらを使うことで標準Taskよりは(おそらく)高パフォーマンスで処理が書けると思います。

using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class AwaitableSample : MonoBehaviour
{
    private void Start()
    {
        // 非同期メソッド実行
        // 「destroyCancellationToken」でこのGameObjectに紐づいた
        // CancellationTokenが取得できる
        _ = DelayAsync(destroyCancellationToken);
    }

    // 非同期メソッド
    // ここの戻り値がTask/ValueTaskなのは一緒
    private async ValueTask DelayAsync(CancellationToken token)
    {
        transform.position = Vector3.one;

        // 3秒間待つ
        // awaitする対象が異なる
        await Awaitable.WaitForSecondsAsync(3, token);

        // 3秒後に原点にワープ
        transform.position = Vector3.zero;
    }
}

処理をNフレーム後に実行したい

コルーチン

コルーチンでNフレーム待つという処理がかけます。
yield return nullを1回実行すると1フレーム待つため、これを繰り返すことで指定フレーム数待機できます。

using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // コルーチンの起動
        StartCoroutine(DelayCoroutine());
    }

    // コルーチン本体
    private IEnumerator DelayCoroutine()
    {
        transform.position = Vector3.one;

        // 10フレーム待つ
        for (var i = 0; i < 10; i++)
        {
            yield return null;
        }

        // 10フレーム後に原点にワープ
        transform.position = Vector3.zero;
    }
}

デリゲートを使うとこう書けます。

using System;
using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        transform.position = Vector3.one;

        // コルーチンの起動
        StartCoroutine(DelayCoroutine(10, () =>
        {
            // 10F後にここの処理が実行される
            transform.position = Vector3.zero;
        }));
    }

    // 一定フレーム後に処理を呼び出すコルーチン
    private IEnumerator DelayCoroutine(int delayFrameCount, Action action)
    {
        for (var i = 0; i < delayFrameCount; i++)
        {
            yield return null;
        }

        action?.Invoke();
    }
}

またWaitForFixedUpdateを使うことでFixedUpdateタイミングで待機することもできます。

using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // コルーチンの起動
        StartCoroutine(DelayCoroutine());
    }

    // コルーチン本体
    private IEnumerator DelayCoroutine()
    {
        transform.position = Vector3.one;

        // FixedUpdateで10フレーム待つ
        for (var i = 0; i < 10; i++)
        {
            yield return new WaitForFixedUpdate();
        }

        // 10フレーム後に原点にワープ
        transform.position = Vector3.zero;
    }
}

async/await + Awaitable

Awaitableを使って指定フレーム数待つことができます。

using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class AwaitableSample : MonoBehaviour
{
    private void Start()
    {
        // 非同期メソッド実行
        _ = DelayAsync(destroyCancellationToken);
    }

    // 非同期メソッド
    private async ValueTask DelayAsync(CancellationToken token)
    {
        transform.position = Vector3.one;

        // 10フレーム待つ
        for (var i = 0; i < 10; i++)
        {
            await Awaitable.NextFrameAsync(token);

            // こうすればFixedUpdate
            // await Awaitable.FixedUpdateAsync(token);
        }

        // 10フレーム後に原点にワープ
        transform.position = Vector3.zero;
    }
}

処理を一定間隔で定期的に実行したい

InvokeRepeating

MonoBehaviour.InvokeRepeatingを使うことで処理を一定間隔で実行することができます。

using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        //DelayMethodを3.5秒後に呼び出し、以降は1秒毎に実行
        InvokeRepeating(nameof(DelayMethod), 3.5f, 1.0f);
    }

    void DelayMethod()
    {
        Debug.Log("Delay call");
    }

    private void OnDestroy()
    {
        // Destroy時に登録したInvokeをすべてキャンセル
        CancelInvoke();
    }
}

コルーチン

コルーチンとwhile/forを組み合わせるパターンです。

using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // コルーチンの起動
        StartCoroutine(DelayCoroutine());
    }

    // コルーチン本体
    private IEnumerator DelayCoroutine()
    {
        while (true) // このGameObjectが有効な間実行し続ける
        {
            yield return new WaitForSeconds(1);

            // 1秒毎に実行する
            Debug.Log("Do!");
        }
    }
}

async/await

async/awaitでも同様のことができます。

using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 非同期メソッド実行
        _ = LoopAsync(destroyCancellationToken);
    }

    // 非同期メソッド
    private async ValueTask LoopAsync(CancellationToken token)
    {
        // キャンセルされるまで繰り返し
        while (!token.IsCancellationRequested)
        {
            // 1秒待つ
            await Task.Delay(TimeSpan.FromSeconds(1), token);

            // Awaitableが使えるならこっちでもOK
            // await Awaitable.WaitForSecondsAsync(1, token);

            // 1秒毎に実行する
            Debug.Log("Do!");
        }
    }
}

細かい時間の制御がしたい

Update()FixedUpdate() を行ったり着たりしたい

コルーチン

using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // コルーチンの起動
        StartCoroutine(DelayCoroutine());
    }

    // コルーチン本体
    private IEnumerator DelayCoroutine()
    {
        // デフォルトは呼び出したタイミング(Start()はUpdate()と同等)
        Debug.Log("OnUpdate!");

        // 次のFixedUpdateタイミングまでまつ
        yield return new WaitForFixedUpdate();
        
        // ここの処理はFixedUpdateのタイミングと同等
        Debug.Log("OnFixedUpdate!");

        // 1フレーム待って次のUpdate()タイミングに移す
        yield return null;
        
        // ここはUpdateタイミング
        Debug.Log("OnUpdate!");
    }
}

async/await + Awaitable

実行タイミングの切り替えはAwaitableで実現できます。

using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 非同期メソッド実行
        _ = SwitchAsync(destroyCancellationToken);
    }

    // 非同期メソッド
    private async ValueTask SwitchAsync(CancellationToken token)
    {
        // デフォルトは呼び出したタイミング(Start()はUpdate()と同等)
        Debug.Log("OnUpdate!");

        // 次のFixedUpdateタイミングまでまつ
        await Awaitable.FixedUpdateAsync(token);

        // ここの処理はFixedUpdateのタイミングと同等
        Debug.Log("OnFixedUpdate!");

        // 1フレーム待って次のUpdate()タイミングに移す
        await Awaitable.NextFrameAsync(token);

        // ここはUpdateタイミング
        Debug.Log("OnUpdate!");
    }
}

Time.timeScaleの影響を受けずに処理を一定時間後に呼び出したい

コルーチン

WaitForSecondsRealtimeを使うことでtimeScaleの影響を無視した実時間での計測ができます。

using System.Collections;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // コルーチンの起動
        StartCoroutine(DelayCoroutine());
    }

    // コルーチン本体
    private IEnumerator DelayCoroutine()
    {
        transform.position = Vector3.one;
        
        // 3秒間待つ
        // Time.timeScale の影響を受けずに実時間で3秒待つ
        yield return new WaitForSecondsRealtime(3);

        // 3秒後に原点にワープ
        transform.position = Vector3.zero;
    }
}

async/await

Task.DelayAwaitable.WaitForSecondsAsyncのそれぞれで挙動が異なります。

Task.Delayは常にtimeScaleの影響を受けません。
Awaitable.WaitForSecondsAsyncは常にtimeScaleの影響を受けます。

using System;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 時間の進みを1/10にする
        Time.timeScale = 0.1f;
        
        // 非同期メソッド実行
        _ = DelayAsync(destroyCancellationToken);
    }

    // 非同期メソッド
    private async ValueTask DelayAsync(CancellationToken token)
    {
        Debug.Log("One!");

        // Task.Delay は timeScaleの影響を受けない
        await Task.Delay(TimeSpan.FromSeconds(1), token);

        Debug.Log("Two!");

        // Awaitable.WaitForSecondsAsync は timeScaleの影響を受ける
        await Awaitable.WaitForSecondsAsync(1, token);

        Debug.Log("Three!");
    }
}

R3を使う

R3 をつかったパターンです。

処理をN秒後に実行したい

Observable.Timerを使う

using System;
using R3;
using UnityEngine;
using Debug = UnityEngine.Debug;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 5秒待つ(Time.timeScaleの影響あり)
        Observable
            .Timer(TimeSpan.FromSeconds(5),
                destroyCancellationToken)
            .Subscribe(_ => { Debug.Log("Done"); });

        // 5秒待つ(Time.timeScaleの影響なし)
        Observable
            .Timer(TimeSpan.FromSeconds(5),
                UnityTimeProvider.UpdateIgnoreTimeScale,
                destroyCancellationToken)
            .Subscribe(_ =>
            {
                Debug.Log("Done"); 
                
            });
    }
}

処理をNフレーム後に実行したい

Observable.TimerFrameを使う

using R3;
using UnityEngine;
using Debug = UnityEngine.Debug;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 2F後に呼び出す
        Observable.TimerFrame(2, destroyCancellationToken)
            .Subscribe(_ =>
            {
                Debug.Log("Done");
            });

        // FixedUpdateで2F後に実行する
        Observable.TimerFrame(2, UnityFrameProvider.FixedUpdate, destroyCancellationToken)
            .Subscribe(_ =>
            {
                Debug.Log("Done");
            });
    }
}

処理を一定間隔で定期的に実行したい

Observable.Timer/Observable.Interval/Observable.TimerFrame/Observable.IntervalFrameでできます。

using System;
using R3;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 3秒後に実行したあと、以降1秒毎に実行
        Observable.Timer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(1), destroyCancellationToken)
            .Subscribe(_ => DelayMethod());

        // 常に3秒間隔で実行
        Observable.Interval(TimeSpan.FromSeconds(3), destroyCancellationToken)
            .Subscribe(_ => DelayMethod());
    }

    private void DelayMethod()
    {
        Debug.Log("Delay call");
    }
}

UniTaskを使う(一番オススメ)

標準機能だけでなく、UniTaskを使うパターンです。
これが一番推奨です。

Task/ValueTask/Awaitableでも時間待機やフレーム待機処理は実現できましたが、UniTaskが一番柔軟に記述できてかつ高パフォーマンスです。

処理をN秒後に実行したい

標準Taskを使う場合とほとんど変わりません。

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 非同期メソッド実行
        DelayAsync(destroyCancellationToken).Forget();
    }

    // 非同期メソッド
    private async UniTask DelayAsync(CancellationToken token)
    {
        transform.position = Vector3.one;

        // 3秒間待つ
        await UniTask.Delay(TimeSpan.FromSeconds(3), cancellationToken: token);

        // 3秒後に原点にワープ
        transform.position = Vector3.zero;
    }
}

処理をNフレーム後に実行したい

UniTask.YieldUniTask.DelayFrameでフレーム単位での待機ができます。

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 非同期メソッド実行
        DelayAsync(destroyCancellationToken).Forget();
    }

    // 非同期メソッド
    private async UniTask DelayAsync(CancellationToken token)
    {
        transform.position = Vector3.one;

        // 5F間待つ
        await UniTask.DelayFrame(5, cancellationToken: token);

        // 5F後に原点にワープ
        transform.position = Vector3.zero;

        // 1F待つ
        await UniTask.Yield(PlayerLoopTiming.Update, token);

        Destroy(gameObject);
    }
}

細かい時間の制御がしたい

UniTask.YieldUniTask.NextFrameの引数でタイミングを指定できます。

UniTask.YieldUniTask.NextFrameの違いは必ず1F待機するかどうかです。
UniTask.Yieldの場合はタイミングの指定によっては1F待たずに実行される場合があります)

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 非同期メソッド実行
        DelayAsync(destroyCancellationToken).Forget();
    }

    // 非同期メソッド
    private async UniTask DelayAsync(CancellationToken token)
    {
        // Updateタイミングを待つ
        await UniTask.Yield(PlayerLoopTiming.Update, token);

        Debug.Log("OnUpdate!");
        
        // 次のFixedUpdateを待つ
        await UniTask.WaitForFixedUpdate(token);
        
        Debug.Log("OnFixedUpdate!");
        
        // 次のPostLateUpdateタイミングを待つ
        await UniTask.NextFrame(PlayerLoopTiming.PostLateUpdate, token);

        Debug.Log("OnPostLateUpdate!");

    }
}

Time.timeScaleの影響を受けずに処理を一定時間後に呼び出したい

UniTask.Delayの引数でDelayType.UnscaledDeltaTimeまたはDelayType.Realtimeを指定してください。
DelayType.UnscaledDeltaTimeはUnitTestでは正しく動作しませんが、DelayType.RealtimeはUnitTestでも利用できます)

using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 非同期メソッド実行
        DelayAsync(destroyCancellationToken).Forget();
    }

    // 非同期メソッド
    private async UniTask DelayAsync(CancellationToken token)
    {
        // Time.timeScaleを無視して3秒後のUpdate()タイミングで実行
        await UniTask.Delay(
            TimeSpan.FromSeconds(3),
            DelayType.UnscaledDeltaTime,
            PlayerLoopTiming.Update, token);

        Debug.Log("OnPostLateUpdate!");
    }
}

まとめ

それぞれを比較すると次になります。

書き方 メリット デメリット 備考
Invoke 標準機能で最初から使える 使い辛い おすすめしない
コルーチン 標準機能で最初から使える
ある程度の柔軟性がある
GameObjectを破棄したら勝手に止まる
async/awaitほど柔軟性がない
非同期処理が連鎖するような処理は苦手
yieldをまたいでtry-catchが使えない
かなりレガシーな機能
async/awaitの記法を覚えてそっちを使うべき
async/await + Task 標準機能で最初から使える パフォーマンスがそこまで高くない
細かい制御はできない
UniTaskを使うべき
async/await + Awaitable Unity2023.1~なら標準で使える
標準Taskより柔軟性あり
UniTaskほど細かい制御はできない UniTaskが導入できない環境ではこれが第一候補になる
async/await + UniTask 高パフォーマンス
一番柔軟に処理が書ける
外部ライブラリの導入が必要 これが一番おすすめ
R3 Observableとして処理が書ける 手続き的な処理が苦手 今回の問題解決のためにわざわざ導入するライブラリではない
もし既に導入していたならば選択肢としてありかも、くらい

UniTask使っておくのが安牌です。次点でAwitable。 async/awaitの記法がわからない初学者ならコルーチンも選択肢に入るかも。

138
108
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
138
108

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?