UnityのWebGLでasync/awaitがうまく機能しないんだけど…
こんな悩みを解決します。
Unityでの非同期処理を実装する際に、async/awaitを用いるかと思います。
しかし、WebGLプラットフォームでのasync/await の使用には特有の課題があり、気をつけないとうまく機能しない場合があるため注意が必要です。
この記事では、Unity WebGL環境でasync/awaitを使った非同期処理ががうまく機能しない場合の対処法を解説します。
Unity WebGLで非同期処理がうまくいかない原因
まず、なぜasync/awaitを使った非同期処理がUnityのWebGLでうまく機能しないのかについてです。
結論としては、WebGLはマルチスレッドに対応していないからだそうです。(Unity forum参照)
特にTask.Delay
メソッドはマルチスレッドのサポートを必要とするようで、この処理を使用しているとasync/awaitではうまく機能しないようです。
実際私は、Task.Delayを使っている処理でこの問題にぶち当たりました。
Unity WebGLでasync/awaitを使うための解決策
Unity WebGLでasync/awaitを使うための主な解決策は以下の2つです。(私が見つけた限り)
- UniTaskを使う
- Coroutineを使う(async await諦める)
以下でそれぞれ解説していきます。
解決策1: UniTask を使う
WebGLでasync/awaitを使う方法1つ目は、UniTaskを使うことです。
UniTask とは
そもそもUniTaskとは?という方のために簡単に説明しておきます。
UniTaskとは、Unityでの非同期プログラミングを効率的に行うためのライブラリです。
まだまだバリバリメンテナンスされているので安心して使えます。
Githubリンク UniTask
UniTask の導入方法
UniTaskをプロジェクトに導入するには、パッケージマネージャーを利用するか、GitHubからローカルにダウンロードし、カスタムパッケージとしてインポートします。
個人的にはパッケージマネージャーからのインストールが簡単で良いと思います。
UniTaskの導入後は、Taskを使った時と同様にasync
とawait
キーワードを使って非同期処理を簡単に記述できます。
解決策2: Coroutine の活用
WebGLでasync/awaitを使う方法2つ目は、Coroutineを使うことです。
まぁ、こちらは正確にはasync/awaitが使えなくなるので、先ほどのUniTaskの導入の方がおすすめです。
Coroutine の基本
Coroutine は、Unityで長年用いられてきた操作を効果的に管理するための方法です。
時間のかかる処理を小さなステップに分割して、フレームごとに処理を実行することができます。
Coroutineとasync/await の併用
基本的には、呼び出し方や返却値の型が異なるためCoroutineとasync/awaitの併用はできません。
ただし、Coroutineを用いればWebGLでも問題なく非同期処理が実行できるようになります。
簡単な実装例
ではここで、それぞれを使った用いたコードの実装例をご紹介します。
まずは、WebGLでうまく動作しないTaskを使ったasync/awaitの処理を見ておきましょう。
以下は、渡された値分カウントダウンを行うシンプルなスクリプトです。
using UnityEngine;
using TMPro;
using System.Threading.Tasks;
public class Countdown : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI _countdownText;
public async Task StartCountdown(int count)
{
await CountdownAsync(count);
}
private async Task CountdownAsync(int count)
{
for (int i = count; i > 0; i--)
{
_countdownText.text = i.ToString();
await Task.Delay(1000);
}
}
}
簡単に解説しておくと、StartCountdownを他のスクリプトから呼び出し、その後countとして渡された秒分のカウントダウンを_countdownTextとして表示します。
この状態だとEditorのプレイモードなどでは問題なく動きますが、WebGLビルドではうまく動作しません。
UniTask を使った実装例
上記コードをUniTaskを用いて実装すると以下のようになります。
using UnityEngine;
using TMPro;
using Cysharp.Threading.Tasks; // インポート元を変更
public class Countdown : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI _countdownText;
public async UniTask StartCountdown(int count)
{
await CountdownAsync(count);
}
private async UniTask CountdownAsync(int count)
{
for (int i = count; i > 0; i--)
{
_countdownText.text = i.ToString();
await UniTask.Delay(1000);
}
}
}
上記では、System.Threading.TasksをCysharp.Threading.Tasksに変更し、TaskをUniTaskに切り替えただけです。
ですが、あら不思議。たったこれだけでWebGLでも上記コードが問題なく動作するようになります。
UniTaskありがたやです。
Coroutine を使った実装例
次は、Coroutineを用いた場合の実装例です。
個人的にはあまりお勧めしませんが、一応のしておきます。基本形ということで。
using UnityEngine;
using TMPro;
public class Countdown : MonoBehaviour
{
[SerializeField] private TextMeshProUGUI _countdownText;
public void StartCountdown(int count)
{
StartCoroutine(CountdownCoroutine(count));
}
private IEnumerator CountdownAsync(int count)
{
for (int i = count; i > 0; i--)
{
_countdownText.text = i.ToString();
yield return new WaitForSeconds(1);
}
}
}
上記では、CountdownCoroutineメソッドの返り値をIEnumerator型にし、StartCountdown内でStartCoroutineで呼び出しています。
この程度の処理ぐらいであれば良いですが、一度Taskで実装しているものをCoroutineに書き換えるのは面倒なので、やはりUniTaskの方が良いかなと思います。
まとめ
今回はUnity WebGLでasync/awaitを使った非同期処理がうまく実行されない場合の解決策について解説しました。
UniTaskを導入するだけで解決するので、UniTask様様という感じです。
デフォルトのTaskではなくUniTaskを活用して、どんどん非同期処理を実装していきましょう。