Unity では、IEnumeratorStartCoroutine に渡すことで、コルーチンを実行できる。

標準で定義されている WWWWaitForSeconds などを使うことが多いが、独自のコルーチンを定義することもできる。 Unity 5.3 から追加された CustomYieldInstruction クラスを使えば、ちょっとしたコルーチンを定義するのが楽になる。

概要

  • 非同期で作成されるファイルを待つコルーチンを定義する

環境

  • Unity 5.3.1p3

基本的なこと

Custom Coroutines – Unity Blog に書いてあること。

  • Unity 5.3 から CustomYieldInstruction が使えるようになった
  • keepWaiting プロパティを override して、コルーチンがもう終了すべきか・まだすべきでないかを定義する
  • WaitForSecondsRealtime という実例がある

非同期で作成されるファイルを待つコルーチンを定義する

ファイルの作成を待ってから、そのファイルに対して何かしたいときがある。例えば、スクリーンショットを撮影して Twitter に投稿したい場合など。

参考: Unity でスクリーンショットを撮る | KAKELOG

そこで、ファイルが作成されることを待つ WaitForFile を定義してみる。

WaitForFile を定義する

WaitForFile.cs

using UnityEngine;
using System.IO;

public class WaitForFile : CustomYieldInstruction
{
    public string FilePath { get; private set; }
    
    public bool IsCompleted { get; private set; }
    
    private float timeoutTime;
    
    private bool existance;
    
    public override bool keepWaiting
    {
        get
        {
            IsCompleted = File.Exists(FilePath) == existance;
            if (IsCompleted)
            {
                return false;
            }
            
            if (Time.realtimeSinceStartup >= timeoutTime)
            {
                return false;
            }
            
            return true;
        }
    }
    
    public WaitForFile(string filePath, bool existance, float timeout)
    {
        this.timeoutTime = Time.realtimeSinceStartup + timeout;
        this.existance = existance;
        
        FilePath = filePath;
    }
}

CustomYieldInstruction を継承した場合、keepWaiting を override するだけでコルーチンが定義できる。 今回の様に、何か特定の条件が成立するまで待つルーチンを書くのに便利。

これを使って、スクリーンショットを撮って何かするコルーチンを書いてみると、以下の様になる。

IEnumerator CaptureScreenshot(string fileName)
{
    var filePath = Path.Combine(Application.persistentDataPath, fileName);
    
    if (File.Exists(filePath))
    {
        File.Delete(filePath);
        
        var deletion = new WaitForFile(filePath, false, 1.0f);
        yield return deletion;
        
        if (!deletion.IsCompleted)
        {
            // Timeout.
            yield break;
        }
    }
    
    Application.CaptureScreenshot(fileName);
    
    var creation = new WaitForFile(filePath, true, 1.0f);
    yield return creation;
    
    if (!creation.IsCompleted)
    {
        // Timeout.
        yield break;
    }
    
    // Do something with the file.
}

WaitWhile / WaitUntil

Custom Coroutines – Unity Blog の文末に補足があるように、 WaitUntilWaitWhile を使うと「何か特定の条件が成立するまで待つルーチン」はもっと簡単に書ける。

yield return new WaitUntil(() => File.Exists(filePath));

でも、今回の様にタイムアウトを指定したい場合など、終了条件が複雑になる場合は、クラス化すると良いと思う。

もうちょっと複雑なコルーチンを定義する

これまで(Unity 5.2 まで)通り、IEnumerator を実装したクラスを定義する必要がある。 コルーチンの内部で別のコルーチンを使用する場合などは、IEnumerator の各メソッドを内部のコルーチンに移譲するような実装が必要になる(たぶん)。

まとめ・感想

  • CustomYieldInstruction を使えば、特定の条件が成立するまで待つルーチンを簡単に定義できる
  • 特定の条件がラムダ式一発で書けるほど単純なら、WaitUntilWaitWhile を使えば、もっと簡単に定義できる
  • もうちょっと複雑なコルーチンを定義しようと思ったら IEnumerator を実装する必要がある

コルーチン楽しい。楽しいけど使いどころは考えた方が良い気がする。特に、キャンセルしたいときがつらいといつも思う。