WebGL ビルドで JavaScript と連携する際、非同期処理はパフォーマンスとユーザー体験の向上に不可欠です。しかし、従来の UnityInstance.SendMessage
を用いた方法では、コールバック処理が煩雑になり、コードの見通しが悪くなるという問題がありました。このパターンでは、async/await と UniTask を活用することで、非同期処理を同期的に記述でき、コードの可読性と保守性を大幅に向上させています。
実装
1. Javascript関数呼び出しクラス
public class MyJsFunc
{
[DllImport("__Internal")]
private static extern void _myJsFunc(string args, string callbackObject, string callbackMethod);
private GameObject _gameObject;
private CallbackAwaiter _awaiter;
public MyJsFunc()
{
// unityInstance.SendMessage() で戻り値を受け取る
// ユニークな名前の空 GameObject を作成する
_gameObject = new GameObject(Guid.NewGuid().ToString());
_awaiter = _gameObject.AddComponent<CallbackAwaiter>();
}
public async UniTask<string> Invoke(string args)
{
try
{
_myJsFunc(args, _gameObject.name, "Callback");
return await _awaiter.Task;
}
finally
{
// 処理が終わったら GameObject を片付ける
Destroy(_gameObject);
}
}
}
2. コールバック待機コンポーネント
public class CallbackAwaiter : MonoBehaviour
{
private UniTaskCompletionSource<string> _utcs = new();
public UniTask<string> Task => _utcs.Task;
void Awake()
{
// シーン遷移後もコールバックを受け取れるようにする
DontDestroyOnLoad(gameObject);
}
// Javascript 側から UnityInstance.SendMessage で呼び出されるメソッド
public void Callback(string str)
{
_utcs.TrySetResult(str);
}
}
3. C# から Javascript 関数を呼び出すためのインターフェース
mergeInto(LibraryManager.library, {
_myJsFunc: function (args, callbackObject, callbackMethod) {
args = UTF8ToString(args)
callbackObject = UTF8ToString(callbackObject)
callbackMethod = UTF8ToString(callbackMethod)
// jslib は薄い Wrapper として利用している
window.unityBridge.doSomething(args, callbackObject, callbackMethod)
}
})
4. 非同期処理の記述
window.unityBridge = window.unityBridge || {}
window.unityBridge.doSomething = async (args: string, callbackObject: string, callbackMethod: string) => {
// ...
// 非同期で行う何らかの処理
const result = await xxx()
// 処理結果をコールバック
window.unityInstance.SendMessage(callbackObject, callbackMethod, result)
}
5. ユーティリティクラス
public static class Javascript
{
public static UniTask<string> DoSomething(string args)
{
return new MyJsFunc().Invoke(args);
}
}
JavaScript関数の呼び出しを簡略化するためのユーティリティクラス。
Javascript の処理の呼び出し
var result = await Javascript.DoSomething(args);
Javascript 側の非同期処理を async/await パターンで呼び出せるようになりました。
補足
実際にはエラーハンドリングをちゃんと行ったり、CallbackAwaiter.cs
にキャンセルトークン持たせたり、コールバックが返ってこない場合のタイムアウト処理を持たせたり、引数や戻り値に複雑なデータ型をやり取りするために JSON を使用したり、MyJsFunc
クラスでは複数の Javascript の関数を呼び出せるように Template Method パターンで実装を分割したりしているが、核心部分はこんな感じ。