UnityのWebGLでasync/awaitが機能しない場合の対処法

悩む人

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つです。(私が見つけた限り)

解決策2つ
  1. UniTaskを使う
  2. Coroutineを使う(async await諦める)

以下でそれぞれ解説していきます。

解決策1: UniTask を使う

WebGLでasync/awaitを使う方法1つ目は、UniTaskを使うことです。

UniTask とは

そもそもUniTaskとは?という方のために簡単に説明しておきます。

UniTaskとは、Unityでの非同期プログラミングを効率的に行うためのライブラリです。

トマトソース

まだまだバリバリメンテナンスされているので安心して使えます。

Githubリンク UniTask

UniTask の導入方法

UniTaskをプロジェクトに導入するには、パッケージマネージャーを利用するか、GitHubからローカルにダウンロードし、カスタムパッケージとしてインポートします。

トマトソース

個人的にはパッケージマネージャーからのインストールが簡単で良いと思います。

UniTaskの導入後は、Taskを使った時と同様にasyncawaitキーワードを使って非同期処理を簡単に記述できます。

解決策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を活用して、どんどん非同期処理を実装していきましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次