Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Interactive UI with UniRx

57,667 views

Published on

https://unirx.doorkeeper.jp/events/25218
UniRx勉強会発表スライド

触って楽しい、見て面白いUIを、UniRxを使って作る方法を紹介。
先日リリースした弊社タイトルの事例を交えながらお話します。

Published in: Technology
  • Be the first to comment

Interactive UI with UniRx

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

  21. 21. DragStart SelectMany Select TakeUntil Repeat
  22. 22. アニメーション /// <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(); }

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

  36. 36. 指が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);

  37. 37. Y方向に拡縮する、ずれる ⇣ Y方向のずれを戻す //IObservable<Vector2>
 var cancelSlipYStream = scaleStream .Select(scale => defaultHeight * scale.x) .Select(y => new Vector2(0f, y - parent.anchoredPosition.y));

  38. 38. 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));

  39. 39. //移動 Observable.Merge(dragMoveStream, cancelSlipYStream, cancelSlipXStream) .Subscribe(move => parent.anchoredPosition += move)
 .AddTo(this); //拡縮
 scaleStream .Subscribe(scale => parent.localScale = scale) .AddTo(this);
 4) 動きを全て合成し、定義する
  40. 40. Demo
  41. 41. 関係性の定義によるUIの実装 • 原因(指の動き)と起こる結果(拡縮、移動)を  細かく分解し、そのまま記述する • 最後にUniRxで合成して適用する
  42. 42. Settings編
  43. 43. Settingsの動きの関係性 • 指が動く   触れた要素が動く • 要素が動く   真上にある要素が遅れて動く • 要素が動く ➔ 真下にある要素が遅れて動く
  44. 44. Settingsの動きの関係性 • 指が動く   触れた要素が動く • 要素が動く   直後にある要素が遅れて動く • 要素が動く ➔ 直前にある要素が遅れて動く ループしてしまう
  45. 45. Settingsの動きの関係性 • 指が動く   触れた要素が動く         前後の要素に動きが伝わる • 上から動きが伝わってくる ➔ 下に伝える • 下から動きが伝わってくる ➔ 上に伝える 伝播してきた動きは逆向きへ伝える
  46. 46. ToPrev つられて動く 動く要素 ToPrev つられて動く
  47. 47. public class ListItem : MonoBehaviour {
 
 //上に動きを伝えるパイプ
 public IObservable<Vector2> ToPrev {
 get { return toPrev.AsObservable(); }
 }
 
 //下に動きを伝えるパイプ
 public IObservable<Vector2> ToNext {
 get { return toNext.AsObservable(); }
 } • まず上下の要素を繋ぐパイプを用意する リスト要素のクラスを作成する
  48. 48. 上から動きが伝わってくる ⇣ 下に伝える var fromPrev = myTransform.parent.GetChild(siblingIndex - 1) .GetComponent<ListItem>() .ToNext .Delay(TimeSpan.FromMilliseconds(10)); fromPrev.Subscribe(toNext).AddTo(this);
  49. 49. 下から動きが伝わってくる ⇣ 上に伝える var fromNext = myTransform.parent.GetChild(siblingIndex + 1) .GetComponent<ListItem>() .ToPrev .Delay(TimeSpan.FromMilliseconds(10)); fromNext.Subscribe(toPrev).AddTo(this);
  50. 50. 指が動く ⇣ 上下に伝わる //ドラッグによる移動
 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);
  51. 51. 動きをまとめ、関係性として定義 Observable.Merge(fromPrev, fromNext, fromDrag) .Subscribe(delta => myTransform.anchoredPosition += delta) .AddTo(this);
  52. 52. Demo
  53. 53. 注意とまとめ • RxはUIでも便利。使えそうな所を見つけたらガ ンガンつかっていこう。 • 無理して使わない。 • 複雑な動きも分解してみると大したことないかも。
  54. 54. Thanks!

×