.NET 4.6時代のUnityでUniRx

# はじめに

Unity 2018.1でついに .NET 4.6バージョン がstableとなり、今までの.NETバージョンがLegacy扱いとなりました。
そこで、 .NET 4.6バージョン（C#6.0)でUniRxがどうなるのかを解説します。

# Observableのawaitができるようになる

大きな特徴として、Task同様に**Observableのawait**ができるようになります。
今までは`Observable`を同期的に待とうとするとコルーチンを使わざるを得ませんでしが、今後は直接awaitすれOKになります。

なお、awaitを終わる条件は`OnCompleted`が発行されることなので、無限長の`Observable`を待ち受けるには`Take(1)`や`First()`を入れて`OnCompleted`を発行させる必要があります。

## 単にawaitする例

```cs:Observableのawait
class AsynTest2 : MonoBehaviour
{
    void Start()
    {
        Wait();
    }

    async Task Wait()
    {

        // これはいつものTaskのDelay
        await Task.Delay(TimeSpan.FromSeconds(1));

        // Observableもawaitできる
        await Observable.Timer(TimeSpan.FromSeconds(1));

        // 3フレーム待ってみる（実際はawaitのタイミングで１フレームズレるので４フレーム待機になる）
        await Observable.TimerFrame(3);

    }
}
```

## OnErrorの扱い

```cs:OnError
async Task Wait()
{
    try
    {
        await Observable.Throw<int>(new Exception("Failed!"));
    }
    catch (Exception e)
    {
        Debug.LogWarning(e);
    }
}
```

`OnError`はそのまま例外になるのでtryで処理


## MonoBehaviour triggersと組み合わせる

`UpdateAsObservable`とか`OnCollisionEnterAsObservable`とか`OnTriggerEnterAsObservable`とか、そういったUnityのイベントをawaitしちゃえばかなり表現の幅が広がります。

```cs:何かにぶつかったら相手の名前を表示する
public class AsyncTest : MonoBehaviour
{
    CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();

    void Start()
    {
        //衝突待機
        WaitToHit(_cancellationTokenSource.Token);

        //破棄されたら非同期処理をキャンセル
        _cancellationTokenSource.AddTo(this);
    }

    // 何かにぶつかる度に、ぶつかった相手の名前を表示する
    private async Task WaitToHit(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            //何かにぶつかるのを待機する
            var hit = await this.OnCollisionEnterAsObservable().Take(1).GetAwaiter(token);

            //相手の名前を表示する
            Debug.Log(hit.gameObject.name);
        }
    }
}
```


## Photonの非同期処理もかなり簡単にかける

[PhotonRx](https://github.com/TORISOUP/PhotonRx)と組み合わせるとかなり良さげにかけそう！

```cs:ログイン→部屋に参加
public class PhotonTest : MonoBehaviour
{

    async void Start()
    {
        PhotonNetwork.autoJoinLobby = true;

        //サーバに繋いで成功したら部屋にも入る
        var result = await ConnectPhoton("v0.1");

        Debug.Log(result);
    }

    /// <summary>
    /// サーバに繋いで、成功したら部屋に参加する
    /// </summary>
    async Task<bool> ConnectPhoton(string serverName)
    {
        //指定したサーバにつなぐ
        var connectResult = await ConnectToServerAsync(serverName);

        if (!connectResult)
        {
            Debug.LogWarning("サーバに接続できませんでした");
            return false;
        }

        // 適当な部屋に参加
        return await ConnectToRandomRoom();
    }

    /// <summary>
    /// サーバに接続する
    /// </summary>
    /// <param name="severName">サーバ名</param>
    /// <returns>成否</returns>
    async Task<bool> ConnectToServerAsync(string severName)
    {
        PhotonNetwork.ConnectUsingSettings(severName);

        //接続結果を返す
        return await Observable.Merge(
                this.OnJoinedLobbyAsObservable().Select(_ => true), //成功
                this.OnFailedToConnectToPhotonAsObservable().Select(_ => false) //失敗
            ).Take(1);
    }


    /// <summary>
    /// ランダムな部屋に接続する（無いなら新しく作る）
    /// </summary>
    /// <returns>成否</returns>
    async Task<bool> ConnectToRandomRoom()
    {
        //結果を返すObservable
        var roomConnectObservable = Observable.Merge(
            this.OnPhotonRandomJoinFailedAsObservable().Select(_ => false),
            this.OnPhotonJoinRoomFailedAsObservable().Select(_ => false),
            this.OnJoinedRoomAsObservable().Select(_ => true)).Take(1);

        // 適当な部屋に参加
        PhotonNetwork.JoinRandomRoom();

        // 部屋に参加できたら終了
        if (await roomConnectObservable) return true; //成功

        // 参加に失敗したので新しく部屋を作ってリカバリ
        PhotonNetwork.CreateRoom(null);

        // 部屋に参加できたかの結果をそのまま帰す
        return await roomConnectObservable;
    }
}
```

# 他にもawaitできるものがある

コルーチンやAsyncOperationもUniRxを使うとawaitできるようになります。

```cs:AsyncOperationのawait
class LoadTextureTest : MonoBehaviour
{
    async void Start()
    {
        var request = Resources.LoadAsync<Texture>("texture/player");

        await request; //テクスチャの読み込み待機

        GetComponent<Renderer>().material.mainTexture = request.asset as Texture;
    }
}
```

```cs:コルーチンのawait
class CoroutineAwaitTest: MonoBehaviour
{
    async void Start()
    {
        await MoveCoroutine(Vector3.forward, 1.0f, 1.0f);
        await MoveCoroutine(Vector3.right, 1.5f, 2.0f);
        await MoveCoroutine(Vector3.back, 2.0f, 5.0f);
    }

    /// <summary>
    /// 指定した方向に指定秒数移動する
    /// </summary>
    IEnumerator MoveCoroutine(Vector3 direction, float speed, float seconds)
    {
        var start = Time.time;
        while (Time.time - start <= seconds)
        {
            transform.position += direction * speed * Time.deltaTime;
            yield return null;
        }
    }
}
```

# まとめ

UniRxを導入すると非同期処理がだいたいawaitできるようになることがわかりました。
ぱっと触ってみた感じで書いた記事なので、指摘項目や「こういう機能もあるよ」というのがあれば教えてください。