Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
72
Help us understand the problem. What are the problem?

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

この記事について

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

やりたいこと

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

標準機能のみで書く場合

外部ライブラリを用いず、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

C#のasync/awaitという機能をつかってコルーチンに似た処理が書けます。
ただしそのままでは扱いにくいのでasync/awaitを使う場合は後述するUniTaskとセットでつかったほうがオススメです。
(とくにキャンセル周りがすごい面倒くさい)

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

public class Sample : MonoBehaviour
{
    private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

    private void Start()
    {
        var ct = _cancellationTokenSource.Token;

        // 非同期メソッド実行
        _ = DelayAsync(ct);
    }

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

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

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

    private void OnDestroy()
    {
        _cancellationTokenSource.Cancel();
        _cancellationTokenSource.Dispose();
    }
}

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

コルーチン(おすすめ)

Nフレーム後に実行したいという場合はコルーチンを使うのがもっとも簡単です。

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();
    }
}

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

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!");
        }
    }
}

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

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

コルーチンを使うことで実行タイミングを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!");
    }
}

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

コルーチンとWaitForSecondsRealtimeを組み合わせることで実現できます。

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;
    }
}

UniRxを使う

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

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

Observable.Timerを使う

using System;
using UniRx;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 3秒後に呼び出す
        Observable.Timer(TimeSpan.FromSeconds(3))
            .Subscribe(_ => DelayMethod());

        // 3秒後に呼び出す
        // Time.timeScaleを無視して実時間で計測する
        Observable.Timer(TimeSpan.FromSeconds(3), Scheduler.MainThreadIgnoreTimeScale)
            .Subscribe(_ => DelayMethod());
    }

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

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

Observable.TimerFrameを使う

using UniRx;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 2F後に呼び出す
        Observable.TimerFrame(2)
            .Subscribe(_ => DelayMethod())
            .AddTo(this);

        // 2F後のFixedUpdateで実行する
        Observable.TimerFrame(2, FrameCountType.FixedUpdate)
            .Subscribe(_ => DelayMethod())
            .AddTo(this);
    }

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

「次のフレーム」と指定したい場合はNextFrameでもOKです。

using UniRx;
using UnityEngine;

public class Sample : MonoBehaviour
{
    private void Start()
    {
        // 次のフレームで呼び出す
        Observable.NextFrame()
            .Subscribe(_ => DelayMethod())
            .AddTo(this);

        // 次のFixedUpdateで実行する
        Observable.NextFrame( FrameCountType.FixedUpdate)
            .Subscribe(_ => DelayMethod())
            .AddTo(this);
    }

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

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

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

using System;
using UniRx;
using UnityEngine;

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

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

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

UniTaskを使う

UniTaskasync/awaitを使うパターン。
基本的にはコルーチンと同じです。

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

標準Taskを使う場合とあまり変わりません。
違いはCancellationTokenの生成が楽になったくらいです。

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

public class Sample : MonoBehaviour
{
    private void Start()
    {
        var ct = this.GetCancellationTokenOnDestroy();

        // 非同期メソッド実行
        DelayAsync(ct).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()
    {
        var ct = this.GetCancellationTokenOnDestroy();

        // 非同期メソッド実行
        DelayAsync(ct).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()
    {
        var ct = this.GetCancellationTokenOnDestroy();

        // 非同期メソッド実行
        DelayAsync(ct).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()
    {
        var ct = this.GetCancellationTokenOnDestroy();

        // 非同期メソッド実行
        DelayAsync(ct).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!");
    }
}

まとめ

標準機能のみで扱えてバランスがよいのは「コルーチン」。
コルーチンよりも高機能だけどちょっと難しいのが「async/await + UniTask」。
今日においてはUniRxを率先して使う必要はないです。
Invoke()は実際のコードで使われてるのを見かけたことないです)

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
72
Help us understand the problem. What are the problem?