Interactive UI
With
UniRx
• 株式会社トライフォート
• Unity/JSエンジニア
• 24歳
岩下 侑冬
Yuto Iwashita
@y120sb
?UI
今日話すこと
MVなんちゃらの話ではない
• パターンの話は先人の記事を読もう
• MVVMとかMVPとか
最近のUIに求められるもの
• リッチなアニメーション
• 手触り感
• パフォーマンス
UIの実装はラクじゃない
• ビジネスロジックなんて大した問題ではない
• (個人的に)アプリの実装コストの7割はUI
• ドラッグ、アニメーション、etc…
ラクしたい
UniRxでできます!
UniRxが解決する2つの問題
ロジック的ややこしさ
と
手触り感の表現
ロジック的ややこしさ
ロジック的ややこしさ
• 仕様が複雑で実装も複雑になるやつ
• そういうUIをユーザーに分からせるには  
親切さも表現しなくてはならない        
➔ 色んな親切機能を追加する羽目に
例
トゥモローアイランド
http://tomorrowisland.trifort.jp/
• 弊社ソーシャルゲームタイトル第一弾
• 箱庭 × MO
• 開発期間1年くらい、          
Unityエンジニアは最大で5人
自分だけの箱庭を
作ってみたり
仲間と一緒に
冒険したり
サービス終了するけど…
• アプリを通してUniRxガッツリ!
• クローズしてもUniRxは便利
アイテム合成UI
• D&D
• ドラッグ通過判定
• 所要時間の表示
• アニメーション
複雑なUI
• D&D
• ドラッグ通過判定
• 所要時間の表示
• アニメーション
複雑なUI
全部UniRxの守備範囲
すべてをObservableへ
• IObservable<T>だらけにするとロジックから
UI、アニメーションへの繋ぎが簡単になる
• 書く人によって違う非同期処理の書き方も 
統一できる(C# event, callback, coroutine)
所要時間の表示
//ドラッグ開始
OnLongTapDragStart
.Select(tuple => tuple.Item2.Model.Recipe)
//スロットにレシピが投入されるストリーム
.SelectMany(recipe => OnThrowIn
.Select(slots => slots.Where(s => s.Thrown).Count())

.Select(count => new { recipe, count })
//ドラッグ開始時にレシピ1枚の場合の所要時間を表示するため

.StartWith(new { recipe, count = 1 }))
//所要時間に変換
.Select(anon => anon.recipe.CalculateNeedTime(anon.count))
//ドラッグ終了まで待ち受け

.TakeUntil(OnLongTapDragEnd)
//繰り返し

.Repeat()

.Subscribe(time => needTime.text = time)

DragStart
SelectMany
Select
TakeUntil
Repeat
アニメーション
/// <summary>

/// 箱のなかに吸い込まれる

/// </summary>

public IObservable<Unit> IntoBox (SlotBox box)
{
const float duration = 0.45f;
//拡縮

var tween = itemIconTransform.DOScale(Vector3.zero, duration);
//移動
itemIconTransform.DOMove(box.Position, duration);
//TweenerをIObservable<Tweener>に変換

return tween.AsObservable();
}

UniRxが解決する2つの問題
ロジック的ややこしさ
と
手触り感の表現
手触り感の表現
手触り感
• 最近の流行り(だと思う)
• 触ったら動く
• 動いたらそれが連鎖していく
例
Paper
https://www.facebook.com/paper
• Facebook製
• ぬるぬる動く、触れる
• 見やすくはない
• 指の動きと連動して徐々に変化するUI
• どうつくる?
動きの関係性を定義する
1) アクションから発生する動きを見つける
2) 動きを細かく分解する
3) 分解した動きをストリームとして記述する
4) 動きを全て合成し、定義する
動きの関係性を定義する
1) アクションから発生する動きを見つける
指が動く   要素が動く
2) 動きを細かく分解する
• 指がY方向に動く   拡縮する
• 指がX方向に動く   X方向に動く
2) 動きを細かく分解する
• 指がY方向に動く   拡縮する
• 指がX方向に動く   X方向に動く
• Y方向に拡縮する   Y方向の位置がずれる   
 ずれを戻す
• X方向に拡縮する   X方向の位置がずれる 
 ずれを戻す
3) 分解した動きを記述する
• UniRxのストリームとして動きを記述する
指がX方向に動く
⇣
X方向に動く
//IObservable<Vector2>
var dragMoveStream = onDrag
.Select(e => container.InverseTransformVector(e.delta).x)
.Select(deltaX => new Vector2(deltaX, 0f));

指がY方向に動く
⇣
拡縮する
//IObservable<Vector3>
var scaleStream = onDrag
.Select(e =>
parent.localScale.y *
ToScreenPosition(e.position).y /
ToScreenPosition(e.position - e.delta).y)
.Select(scale => Vector3.one * scale);

Y方向に拡縮する、ずれる
⇣
Y方向のずれを戻す
//IObservable<Vector2>

var cancelSlipYStream = scaleStream
.Select(scale => defaultHeight * scale.x)
.Select(y => new Vector2(0f, y - parent.anchoredPosition.y));

X方向に拡縮する、ずれる
⇣
X方向のずれを戻す
//IObservable<Vector2>
var cancelSlipXStream = onDrag
.Select(e => container.InverseTransformPoint(e.position))
.Zip(scaleStream.Select(scale => parent.localScale.x / scale.x),
(position, comparsionScale) => new { position, comparsionScale })
.Select(anon => (anon.comparsionScale - DefaultScale)
* (-parent.anchoredPosition.x + anon.position.x))
.Select(deltaX => new Vector2(deltaX, 0f));

//移動
Observable.Merge(dragMoveStream, cancelSlipYStream, cancelSlipXStream)
.Subscribe(move => parent.anchoredPosition += move)

.AddTo(this);
//拡縮

scaleStream
.Subscribe(scale => parent.localScale = scale)
.AddTo(this);

4) 動きを全て合成し、定義する
Demo
関係性の定義によるUIの実装
• 原因(指の動き)と起こる結果(拡縮、移動)を 
細かく分解し、そのまま記述する
• 最後にUniRxで合成して適用する
Settings編
Settingsの動きの関係性
• 指が動く   触れた要素が動く
• 要素が動く   真上にある要素が遅れて動く
• 要素が動く ➔ 真下にある要素が遅れて動く
Settingsの動きの関係性
• 指が動く   触れた要素が動く
• 要素が動く   直後にある要素が遅れて動く
• 要素が動く ➔ 直前にある要素が遅れて動く
ループしてしまう
Settingsの動きの関係性
• 指が動く   触れた要素が動く       
 前後の要素に動きが伝わる
• 上から動きが伝わってくる ➔ 下に伝える
• 下から動きが伝わってくる ➔ 上に伝える
伝播してきた動きは逆向きへ伝える
ToPrev
つられて動く
動く要素
ToPrev
つられて動く
public class ListItem : MonoBehaviour {



//上に動きを伝えるパイプ

public IObservable<Vector2> ToPrev {

get { return toPrev.AsObservable(); }

}



//下に動きを伝えるパイプ

public IObservable<Vector2> ToNext {

get { return toNext.AsObservable(); }

}
• まず上下の要素を繋ぐパイプを用意する
リスト要素のクラスを作成する
上から動きが伝わってくる
⇣
下に伝える
var fromPrev = myTransform.parent.GetChild(siblingIndex - 1)
.GetComponent<ListItem>()
.ToNext
.Delay(TimeSpan.FromMilliseconds(10));
fromPrev.Subscribe(toNext).AddTo(this);
下から動きが伝わってくる
⇣
上に伝える
var fromNext = myTransform.parent.GetChild(siblingIndex + 1)
.GetComponent<ListItem>()
.ToPrev
.Delay(TimeSpan.FromMilliseconds(10));
fromNext.Subscribe(toPrev).AddTo(this);
指が動く
⇣
上下に伝わる
//ドラッグによる移動

IObservable<Vector2> fromDrag = this.OnDragAsObservable()
.Select(e => myTransform.parent.InverseTransformVector(e.delta))

.Select(delta => new Vector2(delta.x, 0));

fromDrag.Subscribe(toPrev).AddTo(this);

fromDrag.Subscribe(toNext).AddTo(this);
動きをまとめ、関係性として定義
Observable.Merge(fromPrev, fromNext, fromDrag)
.Subscribe(delta => myTransform.anchoredPosition += delta)
.AddTo(this);
Demo
注意とまとめ
• RxはUIでも便利。使えそうな所を見つけたらガ
ンガンつかっていこう。
• 無理して使わない。
• 複雑な動きも分解してみると大したことないかも。
Thanks!

Interactive UI with UniRx