Help us understand the problem. What is going on with this article?

.NET 4.6時代のUnityでUniRx

More than 1 year has passed since last update.

はじめに

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する例

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のタイミングで1フレームズレるので4フレーム待機になる)
        await Observable.TimerFrame(3);

    }
}

OnErrorの扱い

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しちゃえばかなり表現の幅が広がります。

何かにぶつかったら相手の名前を表示する
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と組み合わせるとかなり良さげにかけそう!

ログイン→部屋に参加
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できるようになります。

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;
    }
}
コルーチンの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できるようになることがわかりました。
ぱっと触ってみた感じで書いた記事なので、指摘項目や「こういう機能もあるよ」というのがあれば教えてください。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away