はじめに
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する例
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の扱い
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できるようになります。
class LoadTextureTest : MonoBehaviour
{
async void Start()
{
var request = Resources.LoadAsync<Texture>("texture/player");
await request; //テクスチャの読み込み待機
GetComponent<Renderer>().material.mainTexture = request.asset as Texture;
}
}
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できるようになることがわかりました。
ぱっと触ってみた感じで書いた記事なので、指摘項目や「こういう機能もあるよ」というのがあれば教えてください。