メインコンテンツへ移動

Unity3D 遅延呼び出し

無料2015-10-17#Unity3D#u3d延时调用#unity3d几秒后#unity3d return new yield#u3d return new yield#StartCoroutine#WaitForSeconds#Invoke#InvokeRepeating

u3d では Coroutine(コルーチン)を使用して遅延呼び出しを実装できます。Invoke を使用することも、Time.time で自分で制御することもできます

一.コルーチン

コルーチンとは何か?

A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.

コルーチンは新しいスレッドではありません。u3d が提供するイテレータ管理メカニズムの一種です。例えば一般的な例:

void Start () {
    print (0);
    StartCoroutine (wait (3));
    print (1);
}

IEnumerator wait(float s) {
    print (2);
    yield return new WaitForSeconds (s);
    print (3);
}
// => 0 2 1 [3s later] 3

StartCoroutinewaitが返すイテレータを渡すと、コルーチン内部でイテレータ内部の実行を制御できます。以下のように:

IEnumerator iter;

void Start () {
    print (0);
    iter = wait ();
    print (1);
}

IEnumerator wait() {
    print (2);
    yield return 1;
    print (3);
}
// => 0 1

なぜ 2 と 3 が出力されないのか?

wait はイテレータを作成しただけだからです。イテレータはnext() メソッド(C#ではMoveNext、js ではnext)を呼び出さないと実行されないことは我们知道っています。では、続けて試してみましょう:

IEnumerator iter;

void Start () {
    print (0);
    iter = wait ();
    print (1);
    iter.MoveNext ();
    iter.MoveNext ();
}

IEnumerator wait() {
    print (2);
    yield return 1;
    print (3);
}
// => 0 1 2 3

イテレータのMoveNext() メソッドを手動で呼び出すと、予想通りの結果になります。これがStartCoroutine のパラメータです。StartCoroutine は内部でイテレータの参照を取得し、いつMoveNext を実行するか、いつ停止するかを制御できます。因此我们看到的效果是:コードが分割されて実行されている(yield return 文によって分割される)という表象です

まだよくわからない?大丈夫、続けて見ていきましょう:

IEnumerator iter;

void Start () {
    print (0);
    iter = wait (3);
    print (1);
    iter.MoveNext ();
    iter.MoveNext ();
}

IEnumerator wait(float s) {
    print (2);
    yield return new WaitForSeconds (s);
    print (3);
}
// => 0 1 2 3

ちょっと、なぜ 2 を出力した後 3 秒待機しないのか?

現在イテレータを手動で管理しているため、最初のMoveNext() の後、yield return が返すWaitForSeconds タイプのオブジェクトを適切に処理していないため、10 秒待機しません。

StartCoroutine は内部でこのような処理を行っている可能性があります:

  1. パラメータ(イテレータ)を取得した後、あるタイミングで(即時ではない、いつなのかは後で議論します)イテレータのMoveNext() を呼び出す

  2. MoveNext() の戻り値(つまりyield return の後のもの)がWaitForSeconds オブジェクトであれば、s 秒後にMoveNext() を再実行する

最終的に私たちが直接見る結果は、s 秒遅延してyield return の下のものを実行するということです。好了、ここまででほぼ理解できました。最後の例:

IEnumerator iter;

void Start () {
    print (0);
    StartCoroutine (iter = wait (3));
    print (1);
    iter.MoveNext ();
}

IEnumerator wait(float s) {
    print (2);
    yield return new WaitForSeconds (s);
    print (3);
}
// => 0 2 1 3

2 を出力した後 3 秒待機するかどうか推測してみてください?

いいえ、手動でMoveNext () したため、コルーチンが最初にMoveNext () を呼び出すときにイテレータはすでに一歩進んでおり、コルーチンはWaitForSeconds を見ていません。因此コルーチンはイテレータを取得した後即時実行するのではなく、コルーチンに属するある時間枠を待機します(噂ではUpdate の後とのこと、興味があれば u3d 関数実行順序図を探してみてください。もちろん、これは重要ではありません)

コルーチンとは何か?答えは冒頭で述べた通りです:

コルーチンは u3d が提供するイテレータ管理メカニズムの一種です

P.S.先輩のブログ によると、「コルーチンは実は IEnumerator(イテレータ)そのもの」とのことですが、どちらがより正しいか、議論する必要はありませんね

二.コルーチンによる遅延呼び出しの実装

非常に柔軟な遅延呼び出しを見かけました。以下の通りです:

using UnityEngine;
using System.Collections;
using System;

public class Delay {
    public static IEnumerator run(Action action, float delaySeconds) {
        yield return new WaitForSeconds(delaySeconds);
        action();
    }
}

コードは Unity 遅延実行の実装で比較的良好な方式 から修正しました

静的メソッドなので呼び出しが便利で、ツール関数として使用でき、どのオブジェクトにもバインドする必要がありません。呼び出し方法はやや見栄えが悪いです:

void Start() {
    print (1);
    StartCoroutine(Delay.run (() => {
        print (2);
    }, 3));
}
// => 1 [3s later] 3

特に注意:遅延呼び出しが失敗する 2 つの状況があります

  • StartCoroutine の後にシーン切り替え(Application.LoadLevel)がある

    シーン切り替え後、コルーチンの実行が停止するため、遅延呼び出しが失敗します

  • StartCoroutine の後に現在のオブジェクトまたは現在のオブジェクトの祖先オブジェクトをDestroy する

    オブジェクトが破棄されると、そのオブジェクトにアタッチされたすべてのスクリプトのStartCoroutine で追加されたコルーチンタスクの実行が停止するため、遅延呼び出しが失敗します

三.Invoke による遅延呼び出し

Invoke は js のsetTimeout に似ており、setInterval に似たInvokeRepeating もありますが、js ほど強力ではありません。Invoke シリーズは文字列形式のメソッド名のみを受け付け、名前で遅延呼び出しを実行します。例えば:

int arg;

void Start() {
    arg = 1;
    Invoke ("doSth", 3);
}

void doSth() {
    print (arg);
}
// => [3s later] 1

文字列形式ではパラメータを渡せないため、グローバル変数を使用してパラメータを渡しました。構文は非常に簡潔で、混乱を招く箇所もありません

特に注意:上記で述べた 2 つの遅延呼び出し失敗状況は依然として存在します

四.Time.time による遅延呼び出しの実装

やや愚かな方法ですが、シーン切り替えやオブジェクト破棄による遅延呼び出し無効化の問題を回避できます。コードは以下の通りです:

using UnityEngine;
using System.Collections;
using System;

public class Wait : MonoBehaviour {
    static Action _action;
    static float time;
    static float delayTime;

    // Use this for initialization
    void Start () {
        // シーン切り替え時にこのオブジェクトを破棄しない
        DontDestroyOnLoad (gameObject);

        reset ();
    }
    
    // Update is called once per frame
    void Update () {
        if (Time.time > time + delayTime) {
            _action();
            reset();
        }
    }

    void reset() {
        time = 0;
        delayTime = int.MaxValue;
    }

    public static void runAfterSec(Action action, float s) {
        _action = action;
        time = Time.time;
        delayTime = s;
    }
}

使い方にはコツがあります:

  1. 上記スクリプトを作成

  2. 空のオブジェクトを作成し、上記スクリプトをバインド

その後、任意の場所から呼び出せます。例えば:

void Start() {
    int arg = 1;
    Wait.runAfterSec (() => {
        print (arg);
    }, 3);
    Application.LoadLevel("scene1");
}
// => シーンジャンプ [3s later] 1

機能が弱いと感じる場合は、キューを使用してaction を保存するように変更するなど、自行で拡張してください

五.まとめ

最初の方式、つまりコルーチンによる遅延呼び出しの実装を推奨します。制御性への要求が高い場合は第三の方式を採用できます。シナリオが比較的単純な場合は第二の方式を使用できます。パラメータ渡しや関数定義が必要なため、あまり便利ではありません

参考資料

コメント

コメントはまだありません

コメントを書く