Unityに関連する記事です

非同期処理とは


 非同期処理は、プログラムが複数のタスクを並列に処理する手法の一つです。
例えば、重い処理を行うタスクを別のスレッドで実行し、メインスレッドはユーザーインターフェースの更新などに専念するといった使い方があります。
これにより、プログラムの応答性を保ちつつ、効率的に複数のタスクを進行させることができます。


コルーチン


 Unityでは、非同期処理の一つとしてコルーチンがあります。

 コルーチンは、特定の時間だけ処理を中断したり、特定の条件が満たされるまで待機したりすることができます。

 次のサンプルは、コルーチンを用いて1秒待機する処理です。

using System.Collections;
using UnityEngine;

public class CoroutineExample : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(WaitAndPrint());
    }

    private IEnumerator WaitAndPrint()
    {
        yield return new WaitForSeconds(1);
        Debug.Log("Hello after 1 second!");
    }
}


UniTaskとは


 UniTaskは、Unity向けの非同期処理ライブラリで、Unityのコルーチンをより効率的かつ便利に使うことができます。
特に、async/awaitパターンを活用することで、非同期のロジックを直感的に記述することができます。


UniTaskを使ったコルーチン処理の置き換え


 以下は、UniTaskを使った基本的な非同期処理の例です。

 ただし、いきなり UniTask と言われても混乱しますので、まずはコルーチンの処理を作成し、
そちらと同じ処理になる形で UniTask に置き換えるようにします。



<コルーチンの場合>
using System.Collections;
using UnityEngine;

public class CoroutineExample : MonoBehaviour
{
    private void Start()
    {
        StartCoroutine(Process1());
    }

    private IEnumerator Process1()
    {
        yield return new WaitForSeconds(1f);
        Debug.Log("Process1 finished!");
        StartCoroutine(Process2());
    }

    private IEnumerator Process2()
    {
        yield return new WaitUntil(() => Input.GetButtonDown("Fire1"));
        Debug.Log("Process2 finished!");
        StartCoroutine(Process3());
    }

    private IEnumerator Process3()
    {
        yield return new WaitForSeconds(1f);
        Debug.Log("Process3 finished!");
    }
}

 このコードでは、StartメソッドでProcess1コルーチンを開始します。
そのコルーチンが完了したらProcess2コルーチンを開始し、さらにその完了後にProcess3コルーチンを開始します。



<UniTask の場合>


 上記のコルーチンの処理を同じ内容で UniTask で置き換えた場合のサンプルコードです。

using Cysharp.Threading.Tasks;
using UnityEngine;

public class UniTaskExample : MonoBehaviour
{
    private async void Start()
    {
        await Process1();
        await Process2();
        await Process3();
    }

    private async UniTask Process1()
    {
        await UniTask.Delay(1000);
        Debug.Log("Process1 finished!");
    }

    private async UniTask Process2()
    {
        await UniTask.WaitUntil(() => Input.GetButtonDown("Fire1"));
        Debug.Log("Process2 finished!");
    }

    private async UniTask Process3()
    {
        await UniTask.DelayFrame(60);
        Debug.Log("Process3 finished!");
    }
}

 このコードでは、Process1(), Process2(), Process3()の3つの非同期タスクが順番に実行されます。
それぞれのタスクはUniTaskを返し、awaitキーワードを使ってそのタスクが完了するまで待つことができます。

 UniTaskにはUniTask.WaitUntil()メソッドが用意されており、コルーチンで利用している yield return new WaitUntil() と同様の機能を提供します。


UniTaskとCancellationToken


 CancellationToken(キャンセレーション・トークン)は、非同期タスクのキャンセルを行うための仕組みです。
一度キャンセルされたCancellationTokenは再利用できないため、新たな非同期タスクを開始するたびに新しいCancellationTokenを作成する必要があります。

 以下に、CancellationTokenを利用したUniTaskの例を示します:


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

public class UniTaskCancellationTokenExample : MonoBehaviour
{
    public GameObject button;
    public GameObject cancelButton;
    private CancellationTokenSource cancellationTokenSource;

    private async void Start()
    {
        using (cancellationTokenSource = new CancellationTokenSource())
        {
            await WaitUntilButtonPressed();
        }
    }

    private async UniTask WaitUntilButtonPressed()
    {
        try
        {
            await UniTask.WaitUntil(() => button.GetComponent<Button>().IsPressed(), cancellationToken: cancellationTokenSource.Token);
            Debug.Log("Button pressed!");
        }
        catch (OperationCanceledException)
        {
            Debug.Log("Waiting for button press cancelled!");
        }
    }

    public void OnCancelButtonPressed()
    {
        cancellationTokenSource.Cancel();
    }
}

 このコードでは、WaitUntilButtonPressedメソッドがボタンが押されるのを待つ非同期タスクを作成します。
そして、そのタスクが完了するまでawaitキーワードを使って待ちます。ボタンが押されたら、メッセージがログに出力されます。

 また、キャンセルボタンが押されたら、OnCancelButtonPressedメソッドが呼ばれ、CancellationTokenSourceのCancelメソッドが呼び出されます。
これにより、WaitUntilButtonPressedメソッドの非同期タスクがキャンセルされ、キャンセルが発生したことがログに出力されます。



 それでは、先ほど提示したコルーチンの処理を UniTask に置き換えたサンプルコードにも CancellationToken を適用してみましょう。
UniTask で用意されているメソッドには、CancellationToken を指定することが出来るようになっています(デフォルト引数は null です)。


using Cysharp.Threading.Tasks;
using UnityEngine;

public class UniTaskExample : MonoBehaviour
{
    private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

    private async void Start()
    {
        await Process1();
        await Process2();
        await Process3();
    }

    private async UniTask Process1()
    {
        await UniTask.Delay(1000, cancellationToken: cancellationTokenSource.Token);
        Debug.Log("Process1 finished!");
    }

    private async UniTask Process2()
    {
        await UniTask.WaitUntil(() => Input.GetButtonDown("Fire1"), cancellationToken: cancellationTokenSource.Token);
        Debug.Log("Process2 finished!");
    }

    private async UniTask Process3()
    {
        await UniTask.DelayFrame(60, cancellationToken: cancellationTokenSource.Token);
        Debug.Log("Process3 finished!");
    }

    public void CancelAllProcesses()
    {
        cancellationTokenSource.Cancel();
    }
}

 このコードでは、各非同期処理にCancellationTokenを適用しています。
そして、CancelAllProcessesメソッドが呼ばれると、すべての非同期処理がキャンセルされます。


UniTaskを用いた非同期処理のエラーハンドリング


 非同期処理では、例外が発生する可能性が常に存在します
この場合、try/catchブロックを使用して例外を適切にハンドリングすることが重要です。
特に、CancellationTokenがキャンセルされたときはOperationCanceledExceptionがスローされます。

 以下に、非同期処理のエラーハンドリングの例を示します:


using Cysharp.Threading.Tasks;
using UnityEngine;

public class UniTaskErrorHandlingExample : MonoBehaviour
{
    private async void Start()
    {
        try
        {
            await Process();
        }
        catch (System.Exception e)
        {
            Debug.Log("An error occurred: " + e.Message);
        }
    }

    private async UniTask Process()
    {
        // Here, something might throw an exception...
    }
}

 このコードでは、Process()メソッドが何らかの例外をスローする可能性があります。
そのため、Process()メソッドの呼び出しはtryブロック内に置かれています。
もしProcess()メソッドが例外をスローしたら、それはcatchブロックで捕捉され、エラーメッセージがログに出力されます。



 それでは、先ほど提示したコルーチンの処理を UniTask に置き換えたサンプルコードにもエラーハンドリングを適用してみましょう。

using Cysharp.Threading.Tasks;
using UnityEngine;

public class UniTaskExample : MonoBehaviour
{
    private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

    private async void Start()
    {
        try
        {
            await Process1();
            await Process2();
            await Process3();
        }
        catch (OperationCanceledException)
        {
            Debug.Log("Task was cancelled!");
        }
    }

    private async UniTask Process1()
    {
        await UniTask.Delay(1000, cancellationToken: cancellationTokenSource.Token);
        Debug.Log("Process1 finished!");
    }

    private async UniTask Process2()
    {
        await UniTask.WaitUntil(() => Input.GetButtonDown("Fire1"), cancellationToken: cancellationTokenSource.Token);
        Debug.Log("Process2 finished!");
    }

    private async UniTask Process3()
    {
        await UniTask.DelayFrame(60, cancellationToken: cancellationTokenSource.Token);
        Debug.Log("Process3 finished!");
    }

    public void CancelAllProcesses()
    {
        cancellationTokenSource.Cancel();
    }
}
 
 このコードでは、各非同期処理にCancellationTokenを適用しています。
そして、CancelAllProcessesメソッドが呼ばれると、すべての非同期処理がキャンセルされます。

 そこで、Startメソッドで非同期処理をawaitする際は、キャンセルが発生した場合に備えてtry/catchブロックで囲んでいます
これにより、非同期処理がキャンセルされた場合でも適切に処理できます


UniTaskとDOTween


 UniTaskはDOTweenとも連携が可能で、非同期にTweenを待つことができます。
また CancellationToken も指定し、エラーハンドリングすることが出来ます。

 以下にその例を示します。


using Cysharp.Threading.Tasks;
using UnityEngine;
using DG.Tweening;

public class UniTaskDOTweenExample : MonoBehaviour
{
    public GameObject target;

    private async void Start()
    {
        var token = this.GetCancellationTokenOnDestroy();

        try
        {
            await target.transform.DOMove(new Vector3(10, 0, 0), 1).ToUniTask(cancellationToken: token);
            Debug.Log("Move completed!");
        }
        catch (OperationCanceledException)
        {
            Debug.Log("Move cancelled!");
        }
    }
}

 このコードでは、対象のオブジェクトを新しい位置に移動させるTweenを作成し、そのTweenが完了するまで非同期に待つことができます。
GetCancellationTokenOnDestroyメソッドを使って、このゲームオブジェクトが破棄されたときに自動的に非同期タスクがキャンセルされるようにしています。

 DOTween のようにゲームオブジェクトの挙動に紐づいている処理については、
ゲームオブジェクトの破棄のタイミングで非同期処理がキャンセルされる GetCancellationTokenOnDestroyメソッドを CancellationToken に適用すると便利です。


 
 今回提示している DOTween の待機処理ですが

 await target.transform.DOMove(new Vector3(10, 0, 0), 1).ToUniTask(cancellationToken: token); 

 以下の書式でも同じ処理を実装出来ます。


<別パターン 
 await target.DOMove(new Vector3(10, 0, 0), 1).AsyncWaitForCompletion(cancellationToken: token);

<別パターン◆
Tween tween = target.DOMove(new Vector3(10, 0, 0), 1);
        
await tween.ToUniTask(cancellationToken: token);

 複数の書式がある場合、記述に一貫性を持たせることができればどの処理を利用しても問題ありません。


まとめ


 この記事では、UniTaskを用いたUnityでの非同期処理の基礎について解説しました。
UniTaskを使用することで、コルーチンの管理がより容易になり、コードも直感的になります。
CancellationTokenを用いて非同期処理をキャンセルする方法や、DOTweenとの連携方法についても説明しました。

 非同期処理はUnityのパフォーマンスを向上させる強力なツールであり、UniTaskはその実装を助ける重要なライブラリです。
しかし、非同期処理は例外をうまくハンドリングすることが求められるため、try/catchブロックを適切に使用することを忘れないでください。

コメントをかく


「http://」を含む投稿は禁止されています。

利用規約をご確認のうえご記入下さい

Menu



技術/知識(実装例)

2Dおはじきゲーム(発展編)

2D強制横スクロールアクション(発展編)

3Dダイビングアクション(発展編)

2Dタップシューティング(拡張編)

レースゲーム(抜粋)

2D放置ゲーム(発展編)

3D脱出ゲーム(抜粋)

2Dリアルタイムストラテジー

2Dトップビューアドベンチャー(宴アセット使用)

3Dタップアクション(NavMeshAgent 使用)

2Dトップビューアクション(カエルの為に〜、ボコスカウォーズ風)

VideoPlayer イベント連動の実装例

VideoPlayer リスト内からムービー再生の実装例(発展)

AR 画像付きオブジェクト生成の実装例

AR リスト内から生成の実装例(発展)

private



このサイト内の作品はユニティちゃんライセンス条項の元に提供されています。

管理人/副管理人のみ編集できます