「待つ」処理を超簡単にかけるUniTask導入
はじめに
こんにちは、まつさこ です。
本投稿は、Iwaken Lab.の開発合宿ブログリレー16日目の記事です。
普段のUnity開発において、処理を「待つ」という動作を簡潔に実現できる『UniTask』 というライブラリを重宝しています。
今回は、そのUniTaskの便利さを共有し、多くの人がより効率よくUnity開発できるようになる手助けが出来たらと思います。
開発環境
筆者の開発環境は以下です。
- Unity 2021.3.5f1
- Rider 2022.2.3
- UniTask 2.3.1
UniTaskの導入
UniTaskのReleaseページから最新のunitypackageをダウンロードし、プロジェクトにインポートします。
Assets/Plugins ディレクトリ内に UniTask ディレクトリが出来ていたら成功です。
やったね
(パッケージ導入の手法としてOpenUPMってのもあるらしい。いいかも。)
UniTaskを扱う前に...
UniTaskを使うことでいかに簡潔に「待つ」処理が出来るようになるかということを実感するために、まずはあえてUniTaskを使わない実装を考えてみます。
UniTaskをまだ知らないみなさん、以下のような動作をさせたい場合どのような実装をしますか?
ゲームを再生して3秒経過した後、"Hello universe" というデバッグログが出現する
「3秒経過した後」というのはつまり「3秒待った後」ということですので、主に以下2通りが思い浮かぶと思います。
- Updateメソッド 内で3秒時間計測をして、3秒を超えた時にデバッグログを一度だけ出す。
- コルーチンを使用して3秒待った後にデバッグログを出す。
前者の「Updateメソッドで完結させる」実装をやってみましょう。
using UnityEngine;
public class UpdateTimer : MonoBehaviour
{
//経過時間
float _time;
//待つ時間を変数として定義
float _waitTime = 3f;
//デバッグログを一度だけ出すためのフラグ
bool _flag;
void Update()
{
//経過時間が指定時間を超えるかつフラグが立っていないときに、デバッグログを出す
if (_time >= _waitTime && !_flag)
{
Debug.Log("Hello universe");
_flag = true;
}
//1フレームあたりの秒数を加算する
_time += Time.deltaTime;
}
}
このような感じで書けると思います。経過時間を保持しておくための変数 _time
や、デバッグログを一度だけ出すためのフラグ変数 _flag
を用意しなければならず、ちょっと冗長です。
UniTaskで実装してみる
前項でUpdateメソッド実装したものと同じ動作をUniTaskで記述すると、以下のようになります。
using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
public class UniTaskTimer : MonoBehaviour
{
//待つ時間を変数として定義
float _waitTime = 3f;
async void Start()
{
//_waitTime秒待つ
await UniTask.Delay(TimeSpan.FromSeconds(_waitTime));
Debug.Log("Hello universe");
}
}
驚きの短さ。これで、Updateメソッドで実装したときと全く同じ動作をします。
ここでの筆者的UniTask推しポイントは3点あります。
- Startメソッドに記述している。つまりゲーム実行直後一度だけ呼ばれている。
- 時間経過を保持する変数や、時間経過後の処理を一度だけ起こすためのフラグ変数が不要。
- 時系列に沿って上から順番に記述できるので、動作が直感的。
複雑な「待ち」処理で真価を発揮するUniTask
前項で紹介したUniTaskの推しポイントによる恩恵をより実感するために、さらに発展して、
Spaceキーを押すたびに、順番に「こんにちは」「お元気ですか」「さようなら」とデバッグログ表示する
という実装をする場合を考えてみましょう。
これをUpdateメソッド内で書こうとすると...
サンプルのコードすら書きたくないですね😇
if文による条件分岐と、フラグ変数の乱立が予想され、もう帰りたい気分になります。
UniTask様を使うと以下のように書けます。
using Cysharp.Threading.Tasks;
using UnityEngine;
public class UniTaskDialogue : MonoBehaviour
{
async void Start()
{
await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space));
Debug.Log("こんにちは");
await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space));
Debug.Log("お元気ですか");
await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space));
Debug.Log("さようなら");
}
}
全米が涙する簡潔さで実現できました。
スペースキー入力を取得する Input.GetKeyDown(KeyCode.Space)
が、謎の使われ方をしていますね。
await UniTask.WaitUntil(() => ほげほげ)
このようにWaitUntilというUniTaskメソッドをつかうことで、「ほげほげ」がtrueになるまで待ち続けるという処理を実現できます。
止まらない絶対神をなだめる
あ、一点だけ。
CancellationTokenは必ず使いましょう。
ギリシャ神話における絶対神ゼウスのごとく、UniTaskはその威力をとどめることを知りません。シーンの切り替えやオブジェクトの破棄があってもUniTaskは止まりません。
コルーチンはGameObjectの破棄と同時に処理も止まるのですが、それと同じノリでUniTaskを実装すると、参照切れやメモリリークなどの問題を引き起こす実装をしてしまうことがあります。
前項の実装も、GameObjectが破棄された時に自動的にキャンセルを飛ばすtokenを引数に渡してあげることで、安心してUniTaskを利用できます。
using Cysharp.Threading.Tasks;
using UnityEngine;
public class UniTaskDialogue : MonoBehaviour
{
async void Start()
{
//GameObjectが破棄された時にキャンセルを飛ばすトークンを作成
var token = this.GetCancellationTokenOnDestroy();
//UniTaskメソッドの引数にCancellationTokenを入れる
await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space), cancellationToken: token);
Debug.Log("こんにちは");
await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space), cancellationToken: token);
Debug.Log("お元気ですか");
await UniTask.WaitUntil(() => Input.GetKeyDown(KeyCode.Space), cancellationToken: token);
Debug.Log("さようなら");
}
}
まとめ
UniTaskというライブラリの神っぷりが伝わりましたでしょうか?
尚、UniTaskライブラリの詳細な仕様や使い方は本記事では割愛しました。
UniTaskの様々な機能や使い方については、toRisouPさんの一連の記事がとても分かりやすいです。いつもありがとうございます。
Discussion