読者です 読者をやめる 読者になる 読者になる

unity study

Unityでサクッとモックを作れるようになることを目指して、基本的なテクニックを抑えていく過程を残すブログのつもりだったけど、今はただ自分のトラブルシューティングメモになってるブログ

あるストリームが発火したタイミングで、別のストリームの最後の値を流すようなストリーム

あるタイミングで発火するstreamAとstreamBがあり、streamAが発火したタイミングでstreamBの最後の値を流したい。
しかしstreamBが発火しても値は流れてこない。
というようなことをやりたいのだが、後者の条件が厄介でZipLatestでもないしCombineLatestでもないし、どうしたものかと考えた結果。

streamA.Select(_ => Time.frameCount)
	.CombineLatest(streamB.Select(x => new Tuple<int, string>(Time.frameCount, x)), (a, b) => a < b.Item1 ? null : b.Item2)
	.Where(x => x != null)
	.Subscribe(x => {});

それぞれのストリームの発生時刻を流して比較することで、どっちから発火したものかを判断して流す流さないを制御するようにした。

2つ並列処理完了待ち合わせをUniRxを使って書いてみたもの


2つのコルーチンの処理完了待ち合わせをUniRxを使って書いてみたもの

コルーチンをIObservableにして、zipして待ち合わせして、それをStartAsCoroutine()でyield returnできるようにしている。
回りくどいことやってるような気もしなくもないが、スコープの広いフラグ変数を使わなくていい分、コードはシンプルで良いと思う。

ついでに、ProcessA()が終わってからProcessB()をやって、そこまで終わるまで待つ場合はこう書く。

yield return Observable.SelectMany(streamA, streamB).StartAsCoroutine();

一度発火してから、一定時間以内に再発火しなかったら終了するストリーム

断続的に値が流れてくるストリームがあり、一定時間以内に値が来たら処理するが、一定時間以上値が流れて来なかったら終了する、という処理をしたいと思った。
例えばスコアを獲得する毎に値が流れてくるストリームがあるとして、これ自体は明確な終了がない。
しかし発火したタイミングで画面に表示を出したいが、表示するのは直近の1つだけにしたい。
更に表示から一定時間経過したら非表示にしたい。
というようなことをやる場合に、まぁ無理にストリームでやらなくても処理はできるのだが、スマートに書けないものか、と考えてみた。
で、こうなった。
例として、ボタン押下毎に発火するが、一定時間以上発火がなければ終了とするストリーム。

var tapStream = this.UpdateAsObservable().Where(_ => Input.GetMouseButtonDown(0));
tapStream
	.Select(_ => Time.frameCount)
	.TakeUntil(tapStream.Throttle(TimeSpan.FromSeconds(1)))
	.DoOnCompleted(() => Debug.Log("complete"))
	.RepeatUntilDestroy(this)
	.Subscribe(x => Debug.Log("frameCount: " + x)));

このThrottleさせたストリームを条件に使う、ってのがなかなかパッと思い浮かばなかったりするんだよなぁ。
Bufferの条件に使ったりとか、うまく使うと色々やりたいことをスマートに書けるので便利。

【追記】
上記の場合はUpdateAsObservableに対してRepeatするからいいけど、ソースがReactivePropertyだともうちょっとなんか考えないとダメっぽいな。

PHPのimplode()みたいなことを、LINQを使って実現する

例えば
var ids = new int { 1, 2, 3};
みたいな内容を
1, 2, 3
とログ出力したいことがちょくちょくある。
PHPだと
implode(',', ids);
でいけるんだが、C#でスラっと書けんかな、と思ってLINQで表現。。
ids.Select(x => x.ToString()).Aggregate((x, y) => x + ", " + y);

idsがstringなら
string.Join(",", ids);
の方がシンプルなんだけどな。

UniRxを使ったフリック検出

以前にもフリック検出するストリームを書きたいと思って取り組んだことがあったが、その際のやつはどうにも綺麗にまとまらなかった。
Downされてから一定時間以内にUpしないものは検出しない、という条件を落としこむのに苦戦したんだったかな。
ふと思い立って再挑戦してみたが、今回のはわりと綺麗にまとまったように思う。
ポイントはSelectManyの部分だと思うけど、うまく説明できるほど理解しきれてもいないんだよなぁ。

var start = this.UpdateAsObservable()
	.Where(_ => Input.GetMouseButtonDown(0))
	.Select(_ => Input.mousePosition);
var end = this.UpdateAsObservable()
	// 一定時間以内にUpされないと検出しない
	.TakeUntil(Observable.Timer(TimeSpan.FromSeconds(1)))
	.Where(_ => Input.GetMouseButtonUp(0))
	.Select(_ => Input.mousePosition)
	.Take(1);

start.SelectMany(startPos => end.Select(endPos => startPos - endPos))
	.Subscribe(v => {
		// 一定距離以上動かさないとクリック扱い
		if (v.magnitude < 50) {
			Debug.Log("Click");
		} else if (Math.Abs(v.x) > Mathf.Abs(v.y)) {
			if (v.x > 0) {
				Debug.Log("Left");
			} else {
				Debug.Log("Right");
			}
		} else {
			if (v.y > 0) {
				Debug.Log("Down");
			} else {
				Debug.Log("Up");
			}
		}
	});

PhotonUnityNetworkingでOnJoinedRoom()前にOnPhotonPlayerPropertiesChanged(object[])が呼ばれる?

Unity5.3.5f1 + PUN v1.69で確認。
既存ルームにJoinした際に、OnJoinedRoom()よりも先に既にルームにいるプレイヤーのOnPhotonPlayerPropertiesChanged(object)が呼ばれているっぽい?
既にルームいるプレイヤーがSetCustomProperties()を呼んでいるかどうかに関わらず呼ばれる。
それだけならまぁいいんだけど(微妙に都合悪いけど)、困ったことにこの際に渡されるPhotonPlayerの情報が正しくないっぽい。
確かにIDとかは既存プレイヤーのものなんだが、isMasterClientがtrueのはずなのにfalseになっていたりする。
OnJoinedRoom()後に既存プレイヤー側からSetCustomProperties()を呼んでみると、その際のOnPhotonPlayerPropertiesChanged(object
)に渡されてくるものは正しい。
う〜ん、なんなんだろうこれ。
微妙に初期化途中のデータが流れてきているのか?
APIDoc見る限りでは、どうもそれらしい記述が見当たらんのだが。

PhotonRx使っているので、対応として下記のような感じにしてみた。
GitHub - TORISOUP/PhotonRx: PUNをUniRxで扱いやすくするライブラリ

this.OnPhotonPlayerPropertiesChangedAsObservable()
	.SkipUntil(this.OnJoinedRoomAsObservable())
	.TakeUntil(this.OnLeftRoomAsObservable())
	.Repeat()
	.Subscribe(x => {
	});

ちなみに OnPhotonCustomRoomPropertiesChanged() もちょっと怪しい気がする。

SendMessageの謎挙動

PhotonUnityNetworkingを使っていて、OnPhotonJoinRoomFailed(object[] log) が呼ばれず、OnPhotonJoinRoomFailed()が呼ばれるという現象に遭遇し、色々と調査していたらどうもSendMessageの不思議な挙動が原因のようだ。
ちなみにUnity5.3.5f1での確認。
追記:Unity5.4.0f3でも検証したが同じ挙動

using UnityEngine;

public class SendMessageTest : MonoBehaviour
{
	void Foo()
	{
		Debug.Log("Foo() 1");
	}
	void Foo(string param)
	{
		Debug.Log("Foo() 2, " + param);
	}

	void Start()
	{
		// 当然こちらは Foo() が呼ばれる
		SendMessage("Foo", SendMessageOptions.DontRequireReceiver);
		// しかしこちらも Foo() が呼ばれる??
		SendMessage("Foo", "test", SendMessageOptions.DontRequireReceiver);
	}
}

何故か、引数付きのSendMessageも引数なしメソッドの方が呼ばれる。
SendMessageってこういう制約あるんだっけか?
で色々試してみたら

using UnityEngine;

public class Test : MonoBehaviour
{
	void Foo(string param)
	{
		Debug.Log("Foo() 2, " + param);
	}
	void Foo()
	{
		Debug.Log("Foo() 1");
	}

	void Start()
	{
		// エラーになる
		// Failed to call function Foo of class SendMessageTest
		// Calling function Foo with no parameters but the function requires 1.
		SendMessage("Foo", SendMessageOptions.DontRequireReceiver);
		// こちらは意図した通り Foo(string) が呼ばれる
		SendMessage("Foo", "test", SendMessageOptions.DontRequireReceiver);
	}
}

えー、メソッドの定義順を入れ替えると挙動が変わる!?
自分ではSendMessage()なんか全く使わんから、今までこういう挙動があること知らんかった。
というか、過去のバージョンではこういう挙動じゃなかったんじゃないかって気もする。
う〜ん、Unityのバージョン変えながら検証するの、めんどいなぁ。