本ページにはプロモーションが含まれます
Unity

【Unity】UniTaskとは

この記事は約10分で読めます。

はじめに

今回は、Unityでの非同期処理を爆速にするライブラリ、UniTaskについて解説します。非同期処理マスター、目指します!

https://github.com/Cysharp/UniTask.git

UniTask って何? なんでそんなにすごいの?

Unityで非同期処理を行う方法はいくつかありますが、標準のTaskには、パフォーマンス面でちょっと気になる点があったりするんですよね。そこで、救世主として現れたのがUniTask です!

UniTaskは、Unityに特化した非同期処理ライブラリで、Taskよりも格段に高速で、メモリの無駄遣い(アロケーション)を極限まで抑えられるのが自慢です。C#のasync/await構文をそのまま使える手軽さも魅力!

「非同期処理?なにそれ?ジュース?」って方も大丈夫!簡単に言うと、時間のかかる処理を裏でこっそり終わらせてくれる、忍者みたいな存在です。例えば、ネットからデータをダウンロードしたり、大量のファイルを読み込んだりする処理って、ゲームが一時停止したみたいにカクつく原因になりますよね。それを非同期処理で実行することで、ゲームはスムーズに動き続けるってわけです!

UniTask を華麗に導入してみよう!

UniTaskの導入は拍子抜けするほど簡単!UnityのPackageManagerからサクッとインストールできます。

たったこれだけで、UniTaskがあなたのプロジェクトで使えるようになります!ね、簡単でしょ?

動作確認バージョン: UniTask v2.3.3, Unity 2022.3.x (Unity 2021 LTSでも動作確認済み)

UniTask を実際に使ってみよう!

実際にUniTaskのパワーを体感してみましょう!簡単な例として、1秒後にコンソールにメッセージを表示する処理を書いてみます。

using UnityEngine;
using Cysharp.Threading.Tasks;

public class UniTaskExample : MonoBehaviour
{
    async UniTaskVoid Start()
    {
        Debug.Log("処理開始!");
        await UniTask.Delay(1000); // 1秒待つ
        Debug.Log("1秒経過!");
    }
}

このコードをUnityのGameObjectにアタッチして実行すると、コンソールに「処理開始!」と表示された後、1秒後に「1秒経過!」と表示されます。

ここで注目してほしいのは、async UniTaskVoid Start()await UniTask.Delay(1000) です。async をつけることで、非同期処理を記述できる関数に変身します。そして、UniTask.Delay(1000) は、1000ミリ秒(1秒)待つ処理を、裏でこっそり、つまり非同期で実行してくれるんです。

簡単すぎて、拍子抜けしちゃいました?これなら、先輩シニアエンジニアに「お、なかなかやるじゃん」って言われるかも!(言われなかったらごめんなさい…)

UniTask の隠された力を解放!便利な機能紹介

UniTaskは、Delayだけじゃありません!他にもあなたのゲーム開発を助ける、便利な機能が盛りだくさんなんです!

  • UniTask.WhenAll: 複数の非同期処理を同時に開始し、全部終わるのを待ちます。「敵キャラ3体同時に出現!→全員動き出すまで待機!」みたいな処理に便利。
  • UniTask.WhenAny: 複数の非同期処理のうち、どれか一つが終わったら次の処理に進みます。「複数のボタンのうち、どれか一つが押されたら反応する」みたいな処理に使えます。
  • UniTask.Run: 別のスレッドで処理を実行します。重たい処理をメインスレッドから分離して、ゲームの動作を軽くするのに役立ちます。
  • CancellationToken: 非同期処理を途中でキャンセルできます。「やっぱり敵キャラ出現させるのやめた!」みたいな時に、処理を中断できます。

これらの機能を組み合わせることで、複雑な非同期処理もスマートに記述できます。さらに詳しい情報は、公式ドキュメントで確認してくださいね!

UniTask GitHubリポジトリ

油断大敵!エラー処理もちゃんとやろう!

非同期処理は便利ですが、エラーが発生する可能性も考慮しておく必要があります。UniTaskでは、おなじみの try-catch を使って例外をキャッチできます。

using UnityEngine;
using Cysharp.Threading.Tasks;

public class UniTaskExample : MonoBehaviour
{
    async UniTaskVoid Start()
    {
        try
        {
            Debug.Log("処理開始!");
            await UniTask.Delay(1000); // 1秒待つ
            Debug.Log("1秒経過!");
            throw new System.Exception("エラー発生!"); // わざとエラーを発生させる
        }
        catch (System.Exception e)
        {
            Debug.LogError("エラーが発生しました: " + e.Message);
        }
    }
}

このように、try-catch で囲んであげることで、もしエラーが発生してもゲームが強制終了するのを防ぎ、エラーメッセージをコンソールに表示できます。

ガーベージコレクションについて

UniTaskは、アロケーションを削減することでガーベージコレクション(GC)の頻度を減らし、パフォーマンス向上に貢献します。しかし、完全にGCをなくせるわけではありません。UniTaskを使用する際も、可能な限りnewを避けたり、オブジェクトの再利用を心がけたりすることで、GCによるパフォーマンスへの影響を最小限に抑えることができます。

実用的な実装例:非同期なUIフェードイン・アウト

ゲームでよく使うUIのフェードイン・アウト処理をUniTaskで実装してみましょう。これにより、UIのアニメーション中に他の処理がブロックされるのを防ぎ、スムーズなゲーム体験を提供できます。

using UnityEngine;
using UnityEngine.UI;
using Cysharp.Threading.Tasks;

public class UIFade : MonoBehaviour
{
    public Image fadeImage;
    public float fadeDuration = 1.0f;

    // フェードイン
    public async UniTaskVoid FadeIn()
    {
        Color color = fadeImage.color;
        color.a = 1;
        fadeImage.color = color;

        while (color.a > 0)
        {
            color.a -= Time.deltaTime / fadeDuration;
            fadeImage.color = color;
            await UniTask.Yield(); // 次のフレームまで待機
        }

        color.a = 0; // 念のため
        fadeImage.color = color;
    }

    // フェードアウト
    public async UniTaskVoid FadeOut()
    {
        Color color = fadeImage.color;
        color.a = 0;
        fadeImage.color = color;

        while (color.a < 1)
        {
            color.a += Time.deltaTime / fadeDuration;
            fadeImage.color = color;
            await UniTask.Yield(); // 次のフレームまで待機
        }

        color.a = 1; // 念のため
        fadeImage.color = color;
    }
}

このスクリプトをImageコンポーネントを持つGameObjectにアタッチし、FadeIn()FadeOut()を呼び出すことで、UIを非同期にフェードイン・アウトさせることができます。UniTask.Yield()を使用することで、毎フレーム処理を中断し、他の処理にCPUリソースを譲るため、パフォーマンスへの影響を最小限に抑えることができます。

キャンセルトークンを使った非同期処理の中断

非同期処理中に、何らかの理由で処理を中断したい場合があります。例えば、ユーザーがボタンを連打した場合、前の処理をキャンセルして新しい処理を開始したい、といったケースです。UniTaskでは、CancellationTokenを使うことで、これを簡単に実現できます。

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

public class UniTaskCancelExample : MonoBehaviour
{
    private CancellationTokenSource cts;

    public async UniTaskVoid StartLongTask()
    {
        // 以前のタスクが実行中であればキャンセル
        if (cts != null)
        {
            cts.Cancel();
            cts.Dispose();
        }

        cts = new CancellationTokenSource();

        try
        {
            Debug.Log("長時間タスク開始");
            await LongRunningTask(cts.Token);
            Debug.Log("長時間タスク完了");
        }
        catch (OperationCanceledException)
        {
            Debug.Log("タスクがキャンセルされました");
        }
        finally
        {
            // CancellationTokenSourceを破棄
            cts.Dispose();
            cts = null;
        }
    }

    private async UniTask LongRunningTask(CancellationToken token)
    {
        for (int i = 0; i < 10; i++)
        {
            // キャンセルが要求されたらOperationCanceledExceptionを投げる
            token.ThrowIfCancellationRequested();

            Debug.Log("処理中: " + i);
            await UniTask.Delay(500, cancellationToken: token); // 0.5秒待機
        }
    }

    // ボタンクリックなどでタスクを開始する例
    public void OnButtonClicked()
    {
        StartLongTask().Forget(); // Fire and forget
    }
}

この例では、StartLongTask()メソッドが長時間タスクを開始します。CancellationTokenSourceを使ってCancellationTokenを作成し、LongRunningTask()メソッドに渡します。LongRunningTask()メソッド内では、ループごとにtoken.ThrowIfCancellationRequested()を呼び出し、キャンセルが要求されたかどうかを確認します。もしキャンセルが要求された場合、OperationCanceledExceptionがthrowされ、catchブロックで処理されます。ボタンがクリックされるたびに、以前のタスクがキャンセルされ、新しいタスクが開始されます。

まとめ:UniTaskでゲーム開発を加速させよう!

というわけで、今回はUnityの非同期処理ライブラリ、UniTaskについて、これでもか!ってくらい詳しく解説しました!UniTaskを使うことで、あなたのゲームはもっと速く、もっとスムーズに動くようになります。ぜひ、UniTaskをあなたの開発に取り入れて、ゲーム開発を爆速化させてください!

UniTask、使ってみるとマジで手放せなくなるから!導入しないと損するレベルですよ!