【UniRx】 PhotonCloudのログイン処理をUniRxで同期処理っぽく書く

先に[【UniRx】UniRxとコルーチン](http://qiita.com/toRisouP/items/dc369ff4232c5c127437)を読んで頂くとわかりやすいかと思います。

#はじめに

PhotonCloudは基本的にコールバックで通知される仕組みになっています。
その為、処理があちこちに散らばってしまい挙動が読めないコードになりがちです。

今回はUniRxのStartAsCoroutineを使ってPhotonのログイン処理の部分を同期処理っぽく書いてみます


#PhotonのコールバックをObservableに変換する

まずはPhotonのコールバックをObservableとして提供するシングルトンを作成します。
シングルトンは[テラシュールブログ](http://tsubakit1.hateblo.jp/entry/20140709/1404915381)さんのSingletonMonoBehaviourを用いて作成します。

```csharp:PhotonCallbacks.cs
/// <summary>
/// PhotonのコールバックをObservableとして提供するシングルトン
/// </summary>
public class PhotonCallbacks : SingletonMonoBehaviour<PhotonCallbacks>
{
    #region Server
    /// <summary>
   /// サーバ接続の結果を通知するSubject
   /// </summary>
    private Subject<ConnectSeverResult> _connectToSeverResutlSubject;

    /// <summary>
    /// サーバ接続の結果を通知する
    /// </summary>
    public IObservable<ConnectSeverResult> ConnectToSeverObservable
    {
        get
        {
            if(_connectToSeverResutlSubject==null) _connectToSeverResutlSubject = new Subject<ConnectSeverResult>();
            return _connectToSeverResutlSubject.AsObservable().First(); //OnCompletedを発行させるためのFirst
        }
    } 

    /// <summary>
    /// サーバーに接続成功した
    /// </summary>
    private void OnConnectedToPhoton()
    {
        if(_connectToSeverResutlSubject!=null) _connectToSeverResutlSubject.OnNext(new ConnectSeverSuccess());
    }


    /// <summary>
    /// Photonサーバーに繋げなかった
    /// </summary>
    /// <param name='cause'>原因</param>
    private void OnFailedToConnectToPhoton(DisconnectCause cause)
    {
        if (_connectToSeverResutlSubject != null) _connectToSeverResutlSubject.OnNext(new ConnectSeverFail(cause));
    }

    #endregion
    #region Lobby

    /// <summary>
    /// ロビーに接続したことを通知するSubject
    /// </summary>
    Subject<Unit> _onJoinedLobby;

    /// <summary>
    /// ロビー入室成功を通知する
    /// </summary>
    public IObservable<Unit> OnJoinedLobbyAsObservable
    {
        get
        {
            if (_onJoinedLobby == null) _onJoinedLobby = new Subject<Unit>();
            return _onJoinedLobby.First();
        }
    }

    /// <summary>
    /// ロビーに入室した
    /// </summary>
    private void OnJoinedLobby()
    {
        if (_onJoinedLobby != null)
            _onJoinedLobby.OnNext(Unit.Default);
    }

    #endregion
}
```
```csharp:ConnectSeverResult.cs
/// <summary>
/// サーバ接続の成否判定クラス
/// </summary>
public abstract class ConnectSeverResult { }

/// <summary>
/// 成功
/// </summary>
public class ConnectSeverSuccess : ConnectSeverResult{}

/// <summary>
/// 失敗
/// </summary>
public class ConnectSeverFailuer : ConnectSeverResult
{
    private DisconnectCause disconnectCause;
    /// <summary>
    /// 失敗原因
    /// </summary>
    public DisconnectCause Cause { get { return disconnectCause; } }
    public ConnectSeverFailuer(DisconnectCause cause)
    {
        disconnectCause = cause;
    }
}
```

`ConnectToSeverObservable`をSubscribeすることで、サーバ接続できたかどうかの成否が流れてくるようにしてあります。
(成否判定に`ConnectSeverResult`というクラスをわざわざ作ってますが、ここもうちょっと上手く書きたいものですね…。)


#コルーチンとStartAsObservalbeを使ってログイン処理を書く

PhotonCallbacksを用意できたら、後はコルーチン内に処理を書くだけです。
以下はButtonが押された時にログインを試行するコードです。

```csharp:ConnectToPhotonServerSample.cs
public class ConnectToPhotonServerSample : MonoBehaviour
{
    private PhotonCallbacks callbacks;

    private void Start()
    {
        callbacks = PhotonCallbacks.Instance;

        //ボタンが押されたらログイン処理開始
        GetComponent<Button>()
            .OnClickAsObservable()
            .Subscribe(_ => StartCoroutine(LoginTask()));
    }

    /// <summary>
    /// Photonのログイン処理を行う
    /// </summary>
    private IEnumerator LoginTask()
    {
        Debug.Log("サーバに接続開始");

        //接続開始
        PhotonNetwork.ConnectUsingSettings("0.1");

        //成否判定格納用
        ConnectSeverResult severResult = null;

        //ログイン処理の終了を待機
        yield return callbacks.ConnectToSeverObservable
            .StartAsCoroutine(x => severResult = x);

        //if文で成否判定できる！
        if (severResult is ConnectSeverFailuer)
        {
            Debug.LogError("ログイン失敗");
            Debug.LogError(((ConnectSeverFailuer)severResult).Cause);
            //結果が失敗判定なら終了
            yield break;
        }

        //ログイン成功していたら続行
        Debug.Log("ログイン成功");

        //ロビー入室を待機
        yield return callbacks.OnJoinedLobbyAsObservable.StartAsCoroutine();

        Debug.Log("ロビー入室完了");

        //以下ログイン後の処理を書く
    }
}
```

#まとめ

StrartAsCoroutineを使うことでコルーチン中でTaskのawait処理が再現できるため、このような非同期処理を同期処理のように書くことができました。

ログインのメイン処理の部分は同期処理っぽくなって非常に読みやすいのですが、代わりにごちゃごちゃした部分がPhotonCallbacksの中に押し込まれた感じになっています。
まだこの方法で実装を始めてみたばかりなので本当にこの方法で今後上手く行くかわかりませんが、上手く行った場合はまた記事にまとめたいと思います。