はじめに
今回は、Unityでの非同期処理を爆速にするライブラリ、UniTaskについて解説します。非同期処理マスター、目指します!
https://github.com/Cysharp/UniTask.git
UniTask って何? なんでそんなにすごいの?
Unityで非同期処理を行う方法はいくつかありますが、標準のTaskには、パフォーマンス面でちょっと気になる点があったりするんですよね。そこで、救世主として現れたのがUniTask です!
UniTaskは、Unityに特化した非同期処理ライブラリで、Taskよりも格段に高速で、メモリの無駄遣い(アロケーション)を極限まで抑えられるのが自慢です。C#のasync/await構文をそのまま使える手軽さも魅力!
「非同期処理?なにそれ?ジュース?」って方も大丈夫!簡単に言うと、時間のかかる処理を裏でこっそり終わらせてくれる、忍者みたいな存在です。例えば、ネットからデータをダウンロードしたり、大量のファイルを読み込んだりする処理って、ゲームが一時停止したみたいにカクつく原因になりますよね。それを非同期処理で実行することで、ゲームはスムーズに動き続けるってわけです!
UniTask を華麗に導入してみよう!
UniTaskの導入は拍子抜けするほど簡単!UnityのPackageManagerからサクッとインストールできます。
Window>PackageManagerを開く+ボタンをクリック >Add package from git URL...を選択- 以下のURLを入力して
Addをクリック
https://github.com/Cysharp/UniTask.git
たったこれだけで、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では、おなじみの 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、使ってみるとマジで手放せなくなるから!導入しないと損するレベルですよ!

