Unity 非同期処理プチテクニック

概要

こんにちは、亀山です。コベリンでは Unity で VR アプリケーションの開発を行っています。 UniRx を使うと async/await と楽しく過ごすことができます。本記事では、書いてみてちょっと良かったコードを共有します。

ボタンが押されるのを await で待つ

public IObservable<Unit> ObserveKeyDown(KeyCode keyCode)
{
    return Observable.EveryUpdate()
        .Select((_) => Input.GetKeyDown(keyCode))
        .Where(t => t)
        .Select((_) => Unit.Default);
}

このままだと Observable ですが、First() で await できるようになるので、以下のように待つことができます。

await ObserveKeyDown(KeyCode.Return).First()

Input.GetKeyDown の部分を変えれば VR などデバイスが変わっても同じように実装できます。

もっとも近づいた時の距離を取得する Observable

var minimumDistance = Observable.EveryFixedUpdate()
    .Select(_ => (foo.transform.position - bar.transform.position).magnitude)
    .Scan(Mathf.Min);

Mathf.MinMathf.Max に変えるともっとも離れた時の距離になります。

await でタイムアウト

async な Task を実行している間、一定時間が経過したら await を終了するコードです。ゲームの時間切れのように、エラーではなく正常系としてタイムアウトを実装したいときを想定しています。 例としてタイムアウト時には false を、そうでなければ true を返すようにしています。enum やクラスで結果を表現すると良いでしょう。

Task.WhenAny 版

Task<bool> fooTask;

var resultTask = await Task.WhenAny(
    fooTask,
    Task.Run(async () => {
        await Task.Delay(TimeSpan.FromSeconds(0.5));
        return false;
    })
);
var result = await resultTask;

Task.Run のあたりがやや冗長なので、こういうメソッドを作ってもいいかもしれませんね。

static async Task<T> Delayed<T>(T value, TimeSpan timeSpan)
{
    await Task.Delay(timeSpan);
    return value;
}
Task<bool> fooTask;

var resultTask = await Task.WhenAny(
    fooTask,
    Delayed(false, TimeSpan.FromSeconds(0.5))
);
var result = await resultTask;

Observable.Merge 版

IObservable<bool> fooObservable;

var result = await Observable.Merge(
  fooObservable.Select(_ => true),
  Observable.Timer(TimeSpan.FromSeconds(0.5)).Select(_ => false),
).First();

Observable だと WithLatestFrom が使えて便利です。

以上!