UniRxのコルーチンについて
こんにちは、Nobollelエンジニアの古屋です。今回はUniRx関連の話です。
UniRxは、.NETのReactive Extensions (Rx) を、neueccさんがUnity向けに移植されたものです。今回はこの中から、Rxに馴染みのない人でも比較的取っ付きやすいであろうコルーチン機能の紹介をしようと思います。
コルーチンの開始
UniRxでコルーチンを開始するには、FromCoroutineでIEnumeratorをIObservable<T>に変換した後、Subscribeします。
void Start() { Observable.FromCoroutine(() => Hoge()).Subscribe(); } IEnumerator Hoge() { yield return new WaitForSeconds(1); Debug.Log("hoge"); }
コルーチン本体 (Hoge) の書き方はUnity標準のコルーチンとまったく同じで、代わりにStartCoroutine(Hoge())のように書いても動きます。
コルーチンの停止
UniRxでコルーチンを止めるには、StopCoroutineの代わりに、Subscribeの戻り値をDisposeします。
IDisposable coroutineDisposable; void Start() { coroutineDisposable = Observable.FromCoroutine(Hoge).Subscribe(); } void AbortCoroutine() { coroutineDisposable.Dispose(); }
IDisposable形式だと、たとえば複数のコルーチンを走らせて一括で止めるような処理が書きやすくなります。
IDisposable coroutinesDisposable; void Start() { IDisposable hogeDisposable = Observable.FromCoroutine(Hoge).Subscribe(); IDisposable fugaDisposable = Observable.FromCoroutine(Fuga).Subscribe(); // 2つのIDisposableをくっつける coroutinesDisposable = StableCompositeDisposable.Create(hogeDisposable, fugaDisposable); } void AbortCoroutines() { // まとめてDispose coroutinesDisposable.Dispose(); }
コルーチンの寿命について
Unity標準のコルーチンは、MonoBehaviourを継承したコンポーネントの中で回ります。コルーチンを回すにはコンポーネント (とゲームオブジェクト) が必須で、Destroy(this)するとコルーチンは止まります。
一方、UniRxのコルーチンは、シーン上に自動生成されるMainThreadDispatcherというシングルトンオブジェクトの中で回ります。なので、MonoBehaviourを継承していないクラスからも利用できるという利点があります。しかし、先ほどのUniRxのコードでは、コンポーネントを破棄してもコルーチンが回り続けてしまうという問題も起こります。もしコルーチンの寿命をStartCoroutineと同じようにしたいなら、OnDestroyでIDisposableを破棄してやれば良いです:
void OnDestroy() {
coroutineDisposable.Dispose();
}
コルーチンの操作
UniRxでは、コルーチンをIObservable<T>として扱えるため、Rxのオペレータを使ってコルーチンを操作することができ便利です。いくつか例を紹介します。
まずはコルーチンの連結です。Hogeが完了したらFugaを開始するコードを、UnityコルーチンとUniRxコルーチンの二通りで書いてみます。
コルーチンの連結 (Unity):
void Start() { StartCoroutine(Piyo()); } IEnumerator Piyo() { yield return Hoge(); yield return Fuga(); Debug.Log("done"); }
コルーチンの連結 (UniRx):
void Start() { Observable.FromCoroutine(Hoge).SelectMany(Fuga).Subscribe(_ => { Debug.Log("done"); }); }
この例だと、Unity標準が割とシンプルに書けるので、むしろUniRxの方が読みづらい気がします。Piyoのような新規メソッドを定義しなくて良い点は便利です。
しかし、もっと複雑な操作になると、UniRxの方が簡単に書くことができます。次はコルーチンの並列実行を書いてみます。
コルーチンの並列実行 (Unity):
bool hogeCompleted = false; bool fugaCompleted = false; void Start() { StartCoroutine(Hoge_()); StartCoroutine(Fuga_()); } void TryComplete() { if (hogeCompleted && fugaCompleted) { Debug.Log("done"); } } IEnumerator Hoge_() { yield return Hoge(); hogeCompleted = true; TryComplete(); } IEnumerator Fuga_() { yield return Fuga(); fugaCompleted = true; TryComplete(); }
コルーチンの並列実行 (UniRx):
void Start() { var hoge = Observable.FromCoroutine(Hoge); var fuga = Observable.FromCoroutine(Fuga); Observable.WhenAll(hoge, fuga).Subscribe(_ => { Debug.Log("done"); }); }
Unityのコルーチンでは呼び出し側から完了を検知できないため、Hoge_やFuga_のようなコルーチンでラップする必要があります (コールバックを使う方法もありますが、同じくコードが煩雑になります)。完了したかどうかを保存しておくbool変数もコルーチンの数だけ用意し、コールバックが来るたびに、すべてのコールバックが呼ばれたかチェックする必要があります。
UniRxの場合、IObservable<T>に変換した時点でコルーチンの完了を検知できる状態になっています。そのため、WhenAllで複数のIObservable<T>の完了をまとめることができます。
Unityの偽装nullの話
こんにちは、Nobollelエンジニアの古屋です。主にUnityでiOS/Androidのクライアントサイドの開発をしています。
今回は、UnityでオブジェクトをDestroyしたときの挙動に関する小ネタを書こうと思います。
Destroyされたオブジェクトは本当はnullではない?
UnityでオブジェクトをDestroyするとnullになります。
void Start() { var obj = new UnityEngine.Object(); Destroy(obj); if (obj == null) { Debug.Log("obj is destroyed"); } }
Unityを使ってるとこれは普通なのですが、C#的に考えると、オブジェクトが勝手にnullになるのはおかしいです。実際のところ、Destroyされたオブジェクトは「nullっぽく振る舞うオブジェクト」であって、参照自体は生きています。
参照が生きていることを確認するために、System.WeakReferenceを使ってオブジェクトを追跡してみると、Destroyしても永久に「alive=True」であることがわかります。
UnityEngine.Object obj; WeakReference weakRef; void Awake() { obj = new UnityEngine.Object(); weakRef = new WeakReference(obj); Destroy(obj); } void Update() { Debug.Log("alive=" + weakRef.IsAlive); }
objが死なないのは、objの参照が「偽装null」として残っているせいでGCが回収できないからです。試しにobjをメンバ変数ではなくローカル変数として定義すると、Awakeを抜けた時点で参照がなくなり、次のGCのタイミングで「alive=False」になります。
また、System.Object.ReferenceEqualsを使っても、objがnullでないことが確認できます。
void Start() { var obj = new UnityEngine.Object(); Destroy(obj); if (!object.ReferenceEquals(obj, null)) { Debug.Log("obj is not null"); } }
何が起きているのか
- CUSTOM == OPERATOR, SHOULD WE KEEP IT?blogs.unity3d.com
この記事にもあるように、Unityのオブジェクトは==演算子をオーバーロードしてnullを偽装しているようです。たぶん、中にDestroyされたかどうかのフラグを持っていて、「本物のnull」でなければそのフラグを見ているのだと思います。
// たぶんこんな感じ public static bool operator ==(UnityEngine.Object lhs, bool rhs) { bool objNull = ((lhs == null) || lhs.isDestroyed); return (!objNull == rhs); }
実際に問題になるケース
この偽装nullはほとんどの場合うまく動いていて問題を起こさないのですが、かなり稀なケースにおいて問題になることがある (あった) ので、それらを紹介したいと思います。
ケース① ??演算子を使ったとき
??演算子は左辺がnullなら右辺を返す演算子です。次のコードなら、普段はaを返し、aがnullのときだけbを返します。
var c = a ?? b;
??を使うとコードが簡潔になって便利なのですが、このときもしaが「偽装null」だと、bではなくaが返されてしまいます。??の内部では「obj == null」ではなく「object.ReferenceEquals(obj, null)」としてnull判定しているのだと思われます。
ケース② ジェネリック内でnull判定をしたとき
次のようなコードに「偽装null」を渡すとfalseが返ってきます。
static bool IsNull<T>(T obj) where T : class { return obj == null; }
T固有の==が実行され問題なく動きそうな気がしますが、すべてのTで共通の (たぶんSystem.Objectの) ==が実行されてしまうようです。
Tに制約をつければ、UnityEngine.Objectに定義された==が呼ばれ、問題なく動きます。
static bool IsNull<T>(T obj) where UnityEngine.Object { return obj == null; }
ただし、これだとTにUnityEngine.Object以外を渡せず不便です。どちらも渡せて、かつ見かけ上のnullを判定したい場合は、次のように条件分岐する必要があります。
static bool IsNull<T>(T obj) where T : class { var unityObj = obj as UnityEngine.Object; if (!object.ReferenceEquals(unityObj, null)) { return unityObj == null; } else { return obj == null; } }
Cocos2d-x用新エディタ「Cocos Creator」
従来、Cocos2d-xを利用するときUIエディタ・アニメーションエディタとして、「Cocos Studio」が利用されていました。しかしこの「Cocos Studio」も2016年1月で開発がストップしています。これにとって代わって急速に開発が進められているのが「Cocos Creator」です。
この「Cocos Creator」では、「Cocos Studio」と同様に
最もポピュラーなゲームエンジンである「Unity」に触れたことがある方なら、このチュートリアルを進めているとその「Unity」によく似ていると感じることでしょう。それもそのはずこの「Cocos Creator」は「Unity」を意識して作られているからです。公式のドキュメントには「Unity User Guide」まで用意されています。こんなドキュメントまで用意されていれば「Unity」ユーザにとっても「ちょっと触ってみようか」という気になるのではないでしょうか。
知っていると特をするUnity AssetStoreアイテム6選
みなさん、はじめまして。
Nobollel株式会社でリードエンジニアをしている石橋と申します。
私は、これまで10タイトル以上のカジュアル、箱庭系、タワーディフェンス系のゲームアプリを2010年頃から、Unityを活用して開発してきました。
Unityは非常に早くゲームを作ることが出来ますが、足りないところが無い訳ではありません。
そこを補う為にAssetStoreの外部ライブラリを使います。
今回は弊社でよく使われているAssetsで、Unityエンジニアであれば是非試してみてほしいAssetsを紹介します。
- NGUI
UIの構築に使います。
Unity4系の後半からuGUIが出て存在感がやや薄れましたが、まだまだ現役で使えるライブラリです。
多くの人が使っているのでネットのドキュメントも充実しています。
弊社でも好んで使っている人はいます。 - iOS Native & Android Native
SNS、課金、アチーブメントなどのネイティブ処理を色々やってくれます。
バージョンアップも頻繁にしていてメールサポートの返信も早く、値段も良心的でオススメです。 - UniRx
イベント処理を簡単にしてくれるフリーのアセットです。
少し高度なコーディングスキルを要求されますが、無くてはならない便利な存在です。
変数の変更を通知してくれるReactivePropertyが便利。 - DoTween
こちらもフリーのアセット(GUI付きの有料もあり)
Tweenは他にもNGUIにも付属していたりGoKitやiTweenなどもありますが、DoTweenは一番高速らしいです。
扱いもしやすいです。 - G2u
GoogleのスプレッドシートからJson、xml、static classなどを自動生成してくれるアセットです。
データベース管理に使います。
弊社では、上記アセット以外にも様々なアセットの研究&実戦での活用を行っております。
また、今後は新しいUnityの機能を使った簡単な開発内容や、どのアセットを個別にどのように使用したかなどの事例紹介なども行っていく予定です 。