はじめに
こんにちは ![]()
クライアントエンジニアを目指している Code Egg です。
今回は、私が UniTask を学習した際にまとめた内容を記事にしました。
Unityでの非同期処理をシンプルに書くためのヒントになれば嬉しいです!
UniTaskとは
UniTask とは、Cysharp が開発している、Unity 向けに最適化された非同期ライブラリです。
C# の標準 Task を Unity 環境で効率的に動作させるように設計されており、
Unity 特有の制約に対応した非同期コードを書けるのが特徴です。
主な機能としては以下のようなものがあります。
- C# 標準 Task を Unity 向けに最適化した UniTask 型
- Unity の各種 API の async / await 対応
- 非同期処理の稼働状況を確認できる UniTaskTracker
これらを使うことで、await ベースのシンプルな非同期処理が実現できます。
UniTaskの導入方法
UniTask には、主に以下の2つの導入方法があります。
- " GitHub から直接インポートする方法 "
- " Unity Package Manager を使う方法 "
- GitHub から導入する場合 -
- " UniTask の Releases ページ にアクセスします "
- " 最新の unitypackage をダウンロードします "
- " Unity プロジェクトにドラッグ & ドロップしてインポートします "
- Package Manager から導入する場合 -
- " Unity のメニューから Window → Package Manager を開きます "
- " 左上の「+」ボタンをクリックし、Add package from git URL ... を選択します "
- " 以下の URL を入力して「Add」を押します "
https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask
Unityの各種APIを非同期に扱う
あるオブジェクトを await するために必要なオブジェクトを Awaiter と呼びます。
UniTask では、Unity のさまざまなオブジェクトに対して Awaiter が実装されており、
それらを await することで、シンプルに非同期処理を記述することができます。
ここでは、代表的な例として以下の3つを紹介します。
- コルーチン
- uGUI の各種イベント
- MonoBehaviour の各種イベント
それぞれの使い方を順に見ていきましょう ![]()
- コルーチン を await する -
通常、コルーチンを直接 await することはできません。
コルーチンを待機して何らかの処理を実行する場合、次のように記述する必要があります。
using System.Collections;
using UnityEngine;
public class CoroutineSample : MonoBehaviour
{
private void Start()
{
// コルーチンを開始する
StartCoroutine(WaitCoroutineRoutine());
}
// コルーチンを待機して処理を実行するコルーチン
private IEnumerator WaitCoroutineRoutine()
{
// MoveRoutine が完了するまで待機する
yield return MoveRoutine(Vector3.up, 1.0f, 1.0f);
// MoveRoutine の完了後に実行したい処理
Debug.Log("Coroutine Sample");
}
// 方向・速度・秒数を指定して移動させるコルーチン
private IEnumerator MoveRoutine(Vector3 direction, float speed, float seconds)
{
var elapsedTime = 0f;
while (elapsedTime < seconds)
{
elapsedTime += Time.deltaTime;
transform.position += direction * speed * Time.deltaTime;
// 次のフレームまで待機する
yield return null;
}
}
}
この場合、コルーチンを待機するたびに新たなコルーチンが生成されます。
しかし、UniTask を使うことで、コルーチンを直接 await することが可能になります。
using System.Collections;
using Cysharp.Threading.Tasks;
using UnityEngine;
public class CoroutineSample : MonoBehaviour
{
private async void Start()
{
// MoveRoutine の完了を await する
await MoveRoutine(Vector3.up, 1.0f, 1.0f);
Debug.Log("Coroutine Sample");
}
// 方向・速度・秒数を指定して移動させるコルーチン
private IEnumerator MoveRoutine(Vector3 direction, float speed, float seconds)
{
var elapsedTime = 0f;
while (elapsedTime < seconds)
{
elapsedTime += Time.deltaTime;
transform.position += direction * speed * Time.deltaTime;
// 次のフレームまで待機する
yield return null;
}
}
}
このように、非同期処理を 同期的 に記述することができます。
- uGUI の各種イベントを await する -
uGUI の各種イベントを await する方法は、主に2つあります。
- "
On ~ Asyncメソッドを直接awaitする " - "
AsyncHandlerを取得してawaitする "
順番に見ていきましょう ![]()
1. " On ~ Async メソッドを直接 await する場合 "
この方法では、各種 uGUI コンポーネントのメソッドを直接 await します。
イベントを 一度だけ await する場合 は、こちらの方法で問題ありません。
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
public class UGUISample : MonoBehaviour
{
[SerializeField] private Button button;
[SerializeField] private Slider slider;
[SerializeField] private Toggle toggle;
[SerializeField] private InputField inputField;
private async void Start()
{
// Button
await WaitForButtonClick();
// Slider
await WaitForSliderChange();
// Toggle
await WaitForToggleChange();
// InputField
await WaitForInputFieldChange();
}
// Button
private async UniTask WaitForButtonClick()
{
await button.OnClickAsync();
Debug.Log("Button clicked");
}
// Slider
private async UniTask WaitForSliderChange()
{
await slider.OnValueChangedAsync();
Debug.Log("Slider changed");
}
// Toggle
private async UniTask WaitForToggleChange()
{
await toggle.OnValueChangedAsync();
Debug.Log("Toggle changed");
}
// InputField
private async UniTask WaitForInputFieldChange()
{
await inputField.OnValueChangedAsync();
Debug.Log("InputField changed");
}
}
これらの拡張メソッドは、引数に CancellationToken を指定することができます。
- 指定した場合:
CancellationTokenがキャンセルされるとawaitが中断されます - 未指定の場合:対象の uGUI コンポーネントが破棄されるタイミングで中断されます
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
public class UGUISample : MonoBehaviour
{
[SerializeField] private Button button;
[SerializeField] private Slider slider;
[SerializeField] private Toggle toggle;
[SerializeField] private InputField inputField;
private async void Start()
{
// CancellationTokenを取得する
var token = this.GetCancellationTokenOnDestroy();
// Button
await WaitForButtonClick(token);
// Slider
await WaitForSliderChange(token);
// Toggle
await WaitForToggleChange(token);
// InputField
await WaitForInputFieldChange(token);
}
// Button
private async UniTask WaitForButtonClick(CancellationToken token)
{
await button.OnClickAsync(token);
Debug.Log("Button clicked");
}
// Slider
private async UniTask WaitForSliderChange(CancellationToken token)
{
await slider.OnValueChangedAsync(token);
Debug.Log("Slider changed");
}
// Toggle
private async UniTask WaitForToggleChange(CancellationToken token)
{
await toggle.OnValueChangedAsync(token);
Debug.Log("Toggle changed");
}
// InputField
private async UniTask WaitForInputFieldChange(CancellationToken token)
{
await inputField.OnValueChangedAsync(token);
Debug.Log("InputField changed");
}
}
2. " AsyncHandler を取得する場合 "
この方法では、各種 uGUI コンポーネントから AsyncHandler を取得し、
対応するメソッドを await します。
同じイベントを 何度も繰り返し await する場合 は、こちらの方法を使います。
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
public class UGUISample : MonoBehaviour
{
[SerializeField] private Button button;
[SerializeField] private Slider slider;
[SerializeField] private Toggle toggle;
[SerializeField] private InputField inputField;
private async void Start()
{
// Button
await WaitForButtonClick();
// Slider
await WaitForSliderChange();
// Toggle
await WaitForToggleChange();
// InputField
await WaitForInputFieldChange();
}
// Button
private async UniTask WaitForButtonClick()
{
// AsyncHandlerを取得する
using var handler = button.GetAsyncClickEventHandler();
await handler.OnClickAsync();
Debug.Log("Button clicked");
}
// Slider
private async UniTask WaitForSliderChange()
{
// AsyncHandlerを取得する
using var handler = slider.GetAsyncValueChangedEventHandler();
await handler.OnValueChangedAsync();
Debug.Log("Slider changed");
}
// Toggle
private async UniTask WaitForToggleChange()
{
// AsyncHandlerを取得する
using var handler = toggle.GetAsyncValueChangedEventHandler();
await handler.OnValueChangedAsync();
Debug.Log("Toggle changed");
}
// InputField
private async UniTask WaitForInputFieldChange()
{
// AsyncHandlerを取得する
using var handler = inputField.GetAsyncValueChangedEventHandler();
await handler.OnValueChangedAsync();
Debug.Log("InputField changed");
}
}
また、AsyncHandler の取得時に CancellationToken を指定することが可能です。
- 指定した場合:CancellationToken がキャンセルされると
Dispose()が実行されます - 未指定の場合:対象の
uGUIコンポーネントが破棄されるタイミングで実行されます
using System.Threading;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
public class UGUISample : MonoBehaviour
{
[SerializeField] private Button button;
[SerializeField] private Slider slider;
[SerializeField] private Toggle toggle;
[SerializeField] private InputField inputField;
private void Start()
{
// CancellationTokenを取得する
var token = this.GetCancellationTokenOnDestroy();
// Button
WaitForButtonClick(token).Forget();
// Slider
WaitForSliderChange(token).Forget();
// Toggle
WaitForToggleChange(token).Forget();
// InputField
WaitForInputFieldChange(token).Forget();
}
// Button
private async UniTaskVoid WaitForButtonClick(CancellationToken token)
{
// AsyncHandlerを取得する
using var handler = button.GetAsyncClickEventHandler(token);
while (!token.IsCancellationRequested)
{
await handler.OnClickAsync();
Debug.Log("Button clicked");
}
}
// Slider
private async UniTaskVoid WaitForSliderChange(CancellationToken token)
{
// AsyncHandlerを取得する
using var handler = slider.GetAsyncValueChangedEventHandler(token);
while (!token.IsCancellationRequested)
{
await handler.OnValueChangedAsync();
Debug.Log("Slider changed");
}
}
// Toggle
private async UniTaskVoid WaitForToggleChange(CancellationToken token)
{
// AsyncHandlerを取得する
using var handler = toggle.GetAsyncValueChangedEventHandler(token);
while (!token.IsCancellationRequested)
{
await handler.OnValueChangedAsync();
Debug.Log("Toggle changed");
}
}
// InputField
private async UniTaskVoid WaitForInputFieldChange(CancellationToken token)
{
// AsyncHandlerを取得する
using var handler = inputField.GetAsyncValueChangedEventHandler(token);
while (!token.IsCancellationRequested)
{
await handler.OnValueChangedAsync();
Debug.Log("InputField changed");
}
}
}
- MonoBehaviour の各種イベントを await する -
UniTask を導入すると、GameObject や Component に
GetAsync ~ Trigger() という拡張メソッドが追加されます。
これらを利用することで、 MonoBehaviour の各種イベントをawait できます。
Awake()
await this.GetAsyncAwakeTrigger().AwakeAsync();
Start()
await this.GetAsyncStartTrigger().StartAsync();
Update()
await this.GetAsyncUpdateTrigger().UpdateAsync();
FixedUpdate()
await this.GetAsyncFixedUpdateTrigger().FixedUpdateAsync();
LateUpdate()
await this.GetAsyncLateUpdateTrigger().LateUpdateAsync();
OnCollision()
// OnCollisionEnter()
var collision = await this.GetAsyncCollisionEnterTrigger().OnCollisionEnterAsync();
// OnCollisionStay()
var collision = await this.GetAsyncCollisionStayTrigger().OnCollisionStayAsync();
// OnCollisionExit()
var collision = await this.GetAsyncCollisionExitTrigger().OnCollisionExitAsync();
OnTrigger()
// OnTriggerEnter()
var collider = await this.GetAsyncTriggerEnterTrigger().OnTriggerEnterAsync();
// OnTriggerStay()
var collider = await this.GetAsyncTriggerStayTrigger().OnTriggerStayAsync();
// OnTriggerExit()
var collider = await this.GetAsyncTriggerExitTrigger().OnTriggerExitAsync();
Visible
// Visible
await this.GetAsyncBecameVisibleTrigger().OnBecameVisibleAsync();
// Invisible
await this.GetAsyncBecameInvisibleTrigger().OnBecameInvisibleAsync();
OnDestroy()
await this.GetAsyncDestroyTrigger().OnDestroyAsync();
Mouse
// Mouseボタンを押す(Mouseカーソルがオブジェクト上にある状態で)
await this.GetAsyncMouseDownTrigger().OnMouseDownAsync();
// Mouseボタンを離す(Mouseカーソルがオブジェクト上にある状態で)
await this.GetAsyncMouseUpTrigger().OnMouseUpAsync();
// Mouseボタンをドラッグ(Mouseカーソルがオブジェクト上にある状態で)
await this.GetAsyncMouseDragTrigger().OnMouseDragAsync();
// Mouseカーソルがオブジェクトに重なる
await this.GetAsyncMouseEnterTrigger().OnMouseEnterAsync();
// Mouseカーソルがオブジェクトから離れる
await this.GetAsyncMouseExitTrigger().OnMouseExitAsync();
// Mouseカーソルがオブジェクトに重なる
await this.GetAsyncMouseOverTrigger().OnMouseOverAsync();
Pointer
// Pointerを押す(Pointerがオブジェクト上にある状態で)
await this.GetAsyncPointerDownTrigger().OnPointerDownAsync();
// Pointerを離す(Pointerがオブジェクト上にある状態で)
await this.GetAsyncPointerUpTrigger().OnPointerUpAsync();
// Pointerをクリック(Pointerがオブジェクト上にある状態で)
await this.GetAsyncPointerClickTrigger().OnPointerClickAsync();
// Pointerがオブジェクトに重なる
await this.GetAsyncPointerEnterTrigger().OnPointerEnterAsync();
// Pointerがオブジェクトから離れる
await this.GetAsyncPointerExitTrigger().OnPointerExitAsync();
まとめ
ここまで、UniTask を使った Unity の非同期処理について紹介してきました。
Unity の各種 API を await することで、非同期処理をシンプルに記述することができ、
コードの見通しや保守性も大きく向上すると思います。
この記事が、みなさんの「Unity × 非同期処理」の理解の一助になれば幸いです!
もし参考になったら、いいね
や フォロー していただけると嬉しいです!