Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.
Orange Cube
自社フレームワーク
++C++;
Orange Cube
岩永信之
自己紹介
• C#でぐぐれ
• C#でiOS/Androidゲームを作っています
本日の話
• 自社フレームワークの話
• 今、オープンなのは IteratorTasks だけ
• できれば随時オープン化していきたい
• 要求と解決策を中心に話す
• チームが作っているゲームの性質・要求
• 要求に対する技術的な課題
• U...
背景
どういうものを作っていて、どういう要求があった
チームが作っているもの
1. ストラテジー
2. RPG、リアルタイム バトルもの
3. ストラテジー
前提: 作っているゲームの性質
• サーバーとの通信だらけ
• 非同期処理を楽にしたい
• 差分更新、更新された部分の変更通知がほしい
• アクション性は低い/結構なページ数
• ゲーム系フレームワークよりも、UI系フレームワーク使いたい
こんなフレームワークができあがりました
• IteratorTasks
• TaskInteraction
• TaskNavigation
• TypeGen
• Inventories/MasterRepository
• Binding-...
非同期処理
IteratorTasks
System.Threading.Tasks.Task もどき
非同期処理
• スマホゲームなんて非同期処理の塊
• いろんなロジックがサーバー上で動いてる
• サーバーからもらったデータを表示
• そうでなくても、ネイティブUIには非同期処理が必要
• 参考: フリーズしないアプリケーションの作り方
エン...
例: バイト列読み込み
• 同期
static byte[] ReadBytes(Stream s, int n)
{
var buffer = new byte[n];
s.Read(buffer, 0, n);
return buffer;...
例: バイト列読み込み
• begin/end、コールバック式
static void ReadBytes(Stream s, int n, Action<byte[]> callback)
{
var buffer = new byte[n]...
例: バイト列読み込み
• ContinueWith※、コールバック式
static Task<byte[]> ReadBytes(Stream s, int n)
{
var buffer = new byte[n];
return s.Re...
例: バイト列読み込み
• await(C# 5.0※)
• C#的には5.0(2012年に正式版)で解決した問題
• “Unityでなければ”、3年前に解消されているはずの問題
static async Task<byte[]> ReadBy...
IteratorTasks
• しょうがないんで「もどき」を使って運用してる
• UnityがCoroutine(yield return)ベースの非同期処理なんで、同じ方針の、
Task互換ライブラリを作って使ってる
• yield retu...
例: バイト列読み込み
• iterator → Task
• C#的には5.0(2012年に正式版)で解決した問題
• “Unityでなければ”、3年前に解消されているはずの問題
static Task<byte[]> ReadBytes(S...
互換
• 標準TaskとIteratorTasksでコード共有
• 結構 #if 分岐でいける
• 実際、後述のTaskInteraction, TaskNavigation, 通信コード生成 は
#if で両対応してる
#if UseIter...
反省: Rx使わないの?
• 値1つ取るだけ(ラウンドトリップ1回)の非同期処理にRxはいまいち
• (awaitと比べるとの話。begin/endとか同期よりはだいぶいい)
• (Coroutineそのまま使うくらいならRx推奨)
• 同期の...
反省: バッド ノウハウすぎる
• 標準ライブラリの互換ライブラリなんてものは超バッド ノウハウ
• 要らなくなるべきもの
• UnityがMono 3系になるだけで無用の長物
• 「すぐに要らなくなるはずだろう」が全然すぐじゃなかった…
• ...
反省: 両対応は大変
• IteraterTasks(IT)/System.Threading.Tasks(TT)両対応
#if 分岐
共通コード
IT版
ライブラリA
TT版
ライブラリA
IteratorTasks 標準ライブラリライブラリ...
反省: 両対応は大変
• ライブラリAに依存するライブラリBがあったとして
IteratorTasks
A
A.ForIterator
A.ForThreading
A.Shared
B
B.ForIterator
B.ForThreading...
ビューとの相互アクション
TaskInteraction
チャネルを介したゲーム ロジックとビューとの非同期やり取り
やり取りの記録、再現
ビューとの通信
• よくあるシーケンス図
ビュー ゲーム ロジック サーバー
ボタンAをタップ
アニメーション表示
アニメーション表示
ボタンBをタップ
よくあるミス:
こいつが処理の起点
本来:
こいつが処理の主体
よくあるダメな実装
• ビューのイベントが起点で、そこに多くのコードが入る
class View : MonoBehaviour, IPointerClickHandler
{
public void OnPointerClick(Pointe...
ビューとの通信
• ロジックが主体
ビュー ゲーム ロジック
結果のアニメーション表示
ボタンAをタップ
コマンド選択して
コマンドAが選ばれた
実行結果
アニメーション再生終わった
この辺り結構複雑な処理
• コマンド再入力が必要なことも
•...
ベタなロジック実装
public void Start()
{
// 開始処理
var d = CommandSelecting;
if (d != null) d(candidates);
}
public event Action<Comm...
ベタなロジック実装の問題
public void Start()
{
// 開始処理
var d = CommandSelecting;
if (d != null) d(candidates);
}
public event Action<C...
フレームワークによっては
• イベントの送り方、結果の戻し方が違ったりはする
using GalaSoft.MvvmLight.Messaging;
using System.Windows.Input;
class BattleEngine
...
解決の手がかり: ビューはTask
• ロジックから見て、ビュー上の動きは非同期処理(Task)
• (コマンド選択など)ユーザーのタップを1つ待つ
• (アニメーションなど)時間経過を待つ
public static Task AwaitTa...
Taskを使ったメッセンジャー
• Channelクラス
Channel _channel;
public async Task RunAsync()
{
// 開始処理
var selected = await _channel.Send<C...
Channelの追加の役割
Channelビュー ロジック
コマンド選択して
コマンドAが選ばれた
選ばれた
結果の記録
Channel ロジック
コマンド選択して
記録した
結果の再現
Channel ロジック
コマンド選択して
サーバーと
...
反省
• 他のフレームワークとのつなぎこみをフレームワーク化したい
• つなぎ先
• ビュー(データ バインディング)とのやり取り
• サーバーとの通信
• 今は、結構手作業
• Channelにメッセージ ハンドラーを登録して、ビューを表示し...
ここでいったんデモ
Task, TaskInteractionの利用例
デモ内容
• ゲームのログイン時の流れ
ゲーム
ロジック
ゲーム
ロジック
ビュー
• 確認ダイアログ表示
• ユーザー名入力
• 登録状況確認
• 新規登録
• ユーザー データ取得
デモ内容
ゲーム開始
登録状況確認
サーバーと通信
登録済み
利用規約表示 同意します
いいえ
名前入力
ビュー表示、ユーザー応答待ち
登録状況確認 有効な名前
NG
はい
データ取得
まだ
済み
プレイ開始
OK
デモ
ページ遷移
TaskNavigation
ページ遷移をステート マシンとして管理
遷移トリガーをTaskで表現
• 例: 装備画面
ページ遷移
ユニット
装備
装備一覧
(空欄)
装備詳細
(装備中)
装備一覧
(変更)
装備詳細
(空欄)
比較
(変更)
装備一覧
(装備中)
比較
(装備中)
空欄選択
詳細
変更
戻る
戻る
戻る
選択
変更
選択
...
ステート マシンとTask
• ページ遷移はステート マシン
• ステート
• どのページにいる
• トリガー
• どのボタンをタップした
• リストのどの要素をタップした
• タイムアウトした
ステート
A
ステート
B
トリガー1
トリガー...
Taskナビゲーションをフレームワーク化
• ステート マシンの設定例
AddState(S.EquipmentInventory,
new Transition
{
T.Item(ct => Cancel.AwaitTap, () => {}...
フレームワーク化したことで
• 「戻る」(AndroidのBackボタン)対応
• ビュー内のデータ(ViewModel※をスタックで保存、pop)
• グループ
• グループ内遷移: 1つのグループViewModelを共有
• グループ間遷移...
独自のUnityシーン管理
• UnityのApplication.LoadLevel使ってない
• LoadLevelの問題
• 全オブジェクトの一斉破棄かけてるみたいでとにかく重たい
• リソース リーク防止の一番手っ取り早い方法ではあるけ...
反省: まだ結構定型コードが多い
• ステート マシンの設定コードが結構煩雑
• コード生成で対応(Unityエディター拡張)
• 結構、黒魔術的
グループ単位で設定
グループ内のページ一覧
反省: テキストで書くものじゃない
• ステート マシン設定なんて、テキスト ベースのプログラミング言語で
書くものじゃない
• ↓こういう絵で描けるVisualなDSL (と、編集用エディター拡張)が必要
• 実装も大変だし、カスタマイズ性と...
反省: ダイアログ
• 今の実装はページのみ
• ダイアログは別系統フレームワーク
• 実際の要件的には…
• UIデザイナーから上がってくるページ遷移フローはページとダイアログが同
列・混在
• ダイアログの遷移も同じナビゲーション フレーム...
通信コード生成
TypeGen
API定義・型定義をC#で(C#→C#コード生成)
オンライン ゲーム
• サーバーとの通信は定型文が多い
• 手書きすると大量に似たようなコードを書く必要がある
• シリアライズ、デシリアライズ
• HTTP通信、エラー処理
• 多くの通信フレームワークはリフレクションで実現していて…
• i...
C# → C# コード生成
• 型定義、メソッド定義はstrongly-typedな言語使うのが楽
• (初代)XML、(2代目)RubyでDSL、(3代目)JSONとかで書いてた
• だんだんやりたいことが複雑に
• 配列に対応、nullab...
型定義例
[Comment("装備品")]
class Equipment
{
[Comment("アイテムID")]
int Id;
[MasterForeignKey(typeof(EquipmentMaster))]
[Comment("...
定義C#の読み込み
• ビルドしたDLLからリフレクションで読み込み
• 普通に System.Type を読んでる
• 他の選択肢(作り始めた当時はなかったもの)
• System.Reflection.Metadata
• 依存先の解決でき...
生成物: 基本
• 型に厳しい言語で面倒なのは、シリアライズとUIバインディング
• この辺りを生成
• JSONシリアライズ/デシリアライズ
• データバインディング用(INotifyPropertyChanged)実装
• 通信処理
• メ...
生成物: 普通のクラス
public partial class Equipment
{
/// <Summary>
/// アイテムID
/// </Summary>
public int Id { get; set; }
…
public ...
生成物: JSON化・JSON parse
partial class Serializer
{
private string Key(int index, Equipment _)
{
switch (index)
{
case 0: { r...
生成物: データ バインディング用クラス
[Serializable]
[DataContract]
[FileExtensionsAttribute("Equipment")]
[Description("装備品")]
public part...
生成物: 通信コード
namespace DataModels
{
public partial class Api : IUnitApi
{
public Task<AddEquipmentResponse> AddEquipmentAsyn...
生成物: その他
• 型定義JSONも出力
• C#で型定義しだす前の旧型式
• (内部的には「contract JSON」と呼んでる)
• サーバー側は外注、かつ、PHP
• サーバー側のコード生成は発注先に任せてたので、C#を前提にできなか...
補足: サーバー側C#
• 複雑なロジックだけサーバーもC#
• 理由
• 2重開発がさすがに無理
• C#で書く方が楽
• 動かし方
• MonoでPHPと同一サーバー内稼働
• 合成・レベルアップ
• ダンジョン、対人バトルのチート検証
•...
反省: .NETの型システム引きずりすぎた
• 非null参照型
• void
null非許容 null許容
値型 int int?
参照型 string ない※
こいつがつらい
• nullを認めたくない場合、[Requred]属性を付けてる...
反省: 高機能化しすぎた
• 黒魔術度合いが半端ない
• コード生成で、ジェネリックや派生クラスに対応するの結構大変
• リポジトリ(次節で説明)対応がやりすぎ感ある
• 結構ぎりぎりのバランスで成り立ってて
• 修正入れるのそこそこ大変に
•...
反省: コード増えすぎる
• JSON化、すべて静的コード生成
• ソースコード量のうちの結構な割合がJSONがらみ
• アプリのバイナリ サイズ肥大
• 一方、利点もあって
• ソースコードが目に見えるんで、問題を見つけやすい
• JSON読...
リポジトリ
Inventories/MasterRepository
サーバーとのデータ同期、差分更新
ローカル ストレージにデータをキャッシュ
データは全部サーバー上にある
• 必要な分だけ通信でもらってる
• クライアント上でも正規化した状態で管理
• 差分更新
Unit
Id: 1
MasterId: 1
EquipmentId: 120
Equipment
Id: 120
Mas...
問題: インスタンスが変わる
• サーバーとの同期でインスタンスが変わる
• 漏れなく追従するの、手動では無理
UI インベントリ
Unit
Id: 1
MasterId: 1
EquipmentId: 120
参照
同期前
UI インベントリ...
2系統のデータ
• マスター
• ユニットや装備の名前、パラメーターなど、ユーザーによらないデータ
• ほとんど更新されない
• かなりデータ量が多い
• インベントリ
• ユーザーの手持ちの品
• ことあるごとに更新がかかる
デバイスのローカ...
マスター リポジトリ
• ライブラリを整備
• バージョンとデータをローカルに保存
• バージョンが一致していたらローカルから読む
• 不一致ならサーバーから取り直す
• ID をキーにした Dictionary 化
• コード生成を整備
• ...
インベントリ リポジトリ
• ライブラリを整備
• Inventoriesライブラリ
• 現在のインスタンスをID検索できる
• インスタンスの更新イベントを公開
• コード生成を整備
• 通信APIをフックして、リポジトリを自動更新
• 他の...
インベントリ リポジトリ
• ライブラリを整備
• Inventoriesライブラリ
• 現在のインスタンスをID検索できる
• インスタンスの更新イベントを公開
• コード生成を整備
• 通信APIをフックして、リポジトリを自動更新
• 他の...
インベントリ リポジトリ
• ライブラリを整備
• Inventoriesライブラリ
• 現在のインスタンスをID検索できる
• インスタンスの更新イベントを公開
• コード生成を整備
• 通信APIをフックして、リポジトリを自動更新
• 他の...
インベントリ リポジトリ
• ライブラリを整備
• Inventoriesライブラリ
• 現在のインスタンスをID検索できる
• インスタンスの更新イベントを公開
• コード生成を整備
• 通信APIをフックして、リポジトリを自動更新
• 他の...
反省: IObservable
• IObservableとほぼ同機能な型を作ってしまっている※
• IEvent<T> ≒ IObservable<EventPettern<T>>
• Unityがシングル スレッド動作なので、同時実行制御だ...
データ バインディング
Binding-CodeGen
ビューには「UI上のどこにどのデータを出したい」だけを記述
データが更新されたらUIを自動更新
UIが多いゲーム
• 作ってるゲームの性質的にはUIフレームワーク中心
• 3Dとか物理エンジンとか要らない
• むしろ、XAML※的機能がほしい
• Data Binding (CommonView, DialogBase)
• Conent...
データ バインディング
• データ バインディング
• UI系フレームワークの要件:
• UI上のどこにどのデータを出したい
• データが更新されたらそこだけ更新したい
<StackPanel
<TextBox Text="{Binding X...
データ バインディング コード生成
• 自社フレームワークでは、コード生成で実現
• リフレクションが使えないので
• モデルのプロパティと、ビューのプロパティをつなぐだけの簡易なもの
[DataContextType(typeof(Equip...
型に応じたプレハブの選択
• ContentControl, ItemsControlクラス
• たいていのUIは、
• 「このデータ型に対して、このプレハブを作りたい」みたいな要件ばっかり
• Unityインスペクターでプレハブを刺しとく
•...
UI仮想化
• VirtualizingListViewクラス
• 仮想化
• リストのうちの、画面に見えてる範囲だけ、プレハブをInstantiate
• 残りは作らない、隠れたら消す
• これがないと
• 「アイテムは100個までしか持てま...
反省: 同一プロジェクト内コード生成
• Unityエディター拡張は、コンパイル エラーがある状態で動かせない
• コード生成結果でエラーになると、コード生成し直しがままならない
• エラーがなくなるまでコードを元に戻してから生成しなおし
まとめ
まとめ (1/2)
• IteratorTasks
• System.Threading.Tasks.Task もどき
• TaskInteraction
• チャネルを介したゲーム ロジックとビューとの非同期やり取り
• やり取りの記録、再現...
まとめ (2/2)
• TypeGen
• API定義・型定義をC#で(C#→C#コード生成)
• Inventories/MasterRepository
• サーバーとのデータ同期、差分更新
• ローカル ストレージにデータをキャッシュ
•...
Nächste SlideShare
Wird geladen in …5
×

Orange Cube 自社フレームワーク 2015/3

21.457 Aufrufe

Veröffentlicht am

UnityのためのC#勉強会 2015/3/21 にて発表

Veröffentlicht in: Technologie
  • accessibility Books Library allowing access to top content, including thousands of title from favorite author, plus the ability to read or download a huge selection of books for your pc or smartphone within minutes DOWNLOAD THIS BOOKS INTO AVAILABLE FORMAT ......................................................................................................................... ......................................................................................................................... Download Full PDF EBOOK here { http://bit.ly/2m6jJ5M } ......................................................................................................................... Download Full EPUB Ebook here { http://bit.ly/2m6jJ5M } ......................................................................................................................... ...................................ALL FOR EBOOKS................................................. Cookbooks, Manga, Memoir, Music, Mystery, Non Fiction, Paranormal, Philosophy, Poetry, Psychology, Religion, Art, Biography, Business, Chick Lit, Children's, Christian, Classics, Comics, Contemporary, Romance, Science, Science Fiction, Self Help, Suspense, Spirituality, Sports, Thriller, Travel, Young Adult, Crime, Ebooks, Fantasy, Fiction, Graphic Novels, Historical Fiction, History, Horror, Humor And Comedy,
       Antworten 
    Sind Sie sicher, dass Sie …  Ja  Nein
    Ihre Nachricht erscheint hier
  • If you want to download or read this book, copy link or url below in the New tab ......................................................................................................................... DOWNLOAD FULL PDF EBOOK here { http://bit.ly/2m6jJ5M } ......................................................................................................................... Download EPUB Ebook here { http://bit.ly/2m6jJ5M } .........................................................................................................................
       Antworten 
    Sind Sie sicher, dass Sie …  Ja  Nein
    Ihre Nachricht erscheint hier
  • If you want to download or read this book, copy link or url below in the New tab ......................................................................................................................... DOWNLOAD FULL PDF EBOOK here { http://bit.ly/2m6jJ5M } ......................................................................................................................... Download EPUB Ebook here { http://bit.ly/2m6jJ5M } .........................................................................................................................
       Antworten 
    Sind Sie sicher, dass Sie …  Ja  Nein
    Ihre Nachricht erscheint hier
  • DOWNLOAD THI5 BOOKS INTO AVAILABLE FORMAT (Unlimited) ......................................................................................................................... ......................................................................................................................... Download Full PDF EBOOK here { http://bit.ly/2m6jJ5M } ......................................................................................................................... Download Full EPUB Ebook here { http://bit.ly/2m6jJ5M } ......................................................................................................................... ACCESS WEBSITE for All Ebooks ......................................................................................................................... Download Full PDF EBOOK here { http://bit.ly/2m6jJ5M } ......................................................................................................................... Download EPUB Ebook here { http://bit.ly/2m6jJ5M } ......................................................................................................................... Download doc Ebook here { http://bit.ly/2m6jJ5M } ......................................................................................................................... ......................................................................................................................... ......................................................................................................................... .............. Browse by Genre Available eBooks ......................................................................................................................... Art, Biography, Business, Chick Lit, Children's, Christian, Classics, Comics, Contemporary, Cookbooks, Crime, Ebooks, Fantasy, Fiction, Graphic Novels, Historical Fiction, History, Horror, Humor And Comedy, Manga, Memoir, Music, Mystery, Non Fiction, Paranormal, Philosophy, Poetry, Psychology, Religion, Romance, Science, Science Fiction, Self Help, Suspense, Spirituality, Sports, Thriller, Travel, Young Adult,
       Antworten 
    Sind Sie sicher, dass Sie …  Ja  Nein
    Ihre Nachricht erscheint hier
  • DOWNLOAD THI5 BOOKS INTO AVAILABLE FORMAT (Unlimited) ......................................................................................................................... ......................................................................................................................... Download Full PDF EBOOK here { http://bit.ly/2m6jJ5M } ......................................................................................................................... Download Full EPUB Ebook here { http://bit.ly/2m6jJ5M } ......................................................................................................................... ACCESS WEBSITE for All Ebooks ......................................................................................................................... Download Full PDF EBOOK here { http://bit.ly/2m6jJ5M } ......................................................................................................................... Download EPUB Ebook here { http://bit.ly/2m6jJ5M } ......................................................................................................................... Download doc Ebook here { http://bit.ly/2m6jJ5M } ......................................................................................................................... ......................................................................................................................... ......................................................................................................................... .............. Browse by Genre Available eBooks ......................................................................................................................... Art, Biography, Business, Chick Lit, Children's, Christian, Classics, Comics, Contemporary, Cookbooks, Crime, Ebooks, Fantasy, Fiction, Graphic Novels, Historical Fiction, History, Horror, Humor And Comedy, Manga, Memoir, Music, Mystery, Non Fiction, Paranormal, Philosophy, Poetry, Psychology, Religion, Romance, Science, Science Fiction, Self Help, Suspense, Spirituality, Sports, Thriller, Travel, Young Adult,
       Antworten 
    Sind Sie sicher, dass Sie …  Ja  Nein
    Ihre Nachricht erscheint hier

Orange Cube 自社フレームワーク 2015/3

  1. 1. Orange Cube 自社フレームワーク ++C++; Orange Cube 岩永信之
  2. 2. 自己紹介 • C#でぐぐれ • C#でiOS/Androidゲームを作っています
  3. 3. 本日の話 • 自社フレームワークの話 • 今、オープンなのは IteratorTasks だけ • できれば随時オープン化していきたい • 要求と解決策を中心に話す • チームが作っているゲームの性質・要求 • 要求に対する技術的な課題 • Unityという縛りの中での課題 • どうフレームワークを整備したか • 見せれる範囲で実際のコード • 実物デモ
  4. 4. 背景 どういうものを作っていて、どういう要求があった
  5. 5. チームが作っているもの 1. ストラテジー 2. RPG、リアルタイム バトルもの 3. ストラテジー
  6. 6. 前提: 作っているゲームの性質 • サーバーとの通信だらけ • 非同期処理を楽にしたい • 差分更新、更新された部分の変更通知がほしい • アクション性は低い/結構なページ数 • ゲーム系フレームワークよりも、UI系フレームワーク使いたい
  7. 7. こんなフレームワークができあがりました • IteratorTasks • TaskInteraction • TaskNavigation • TypeGen • Inventories/MasterRepository • Binding-CodeGen ライブコーディングでデモあり
  8. 8. 非同期処理 IteratorTasks System.Threading.Tasks.Task もどき
  9. 9. 非同期処理 • スマホゲームなんて非同期処理の塊 • いろんなロジックがサーバー上で動いてる • サーバーからもらったデータを表示 • そうでなくても、ネイティブUIには非同期処理が必要 • 参考: フリーズしないアプリケーションの作り方 エンド・ユーザーは、 0.5秒のフリーズでストレスを感じ、 3秒のフリーズはバグだと思う
  10. 10. 例: バイト列読み込み • 同期 static byte[] ReadBytes(Stream s, int n) { var buffer = new byte[n]; s.Read(buffer, 0, n); return buffer; } ここでフリーズ の可能性あり
  11. 11. 例: バイト列読み込み • begin/end、コールバック式 static void ReadBytes(Stream s, int n, Action<byte[]> callback) { var buffer = new byte[n]; s.BeginRead(buffer, 0, n, r => { var result = s.EndRead(r); callback(buffer); }, null); } 2個のメソッドをペアで 呼ぶ必要あり 後ろにさらに処理がつづいたり、 分岐・ループさせるとかなり面倒
  12. 12. 例: バイト列読み込み • ContinueWith※、コールバック式 static Task<byte[]> ReadBytes(Stream s, int n) { var buffer = new byte[n]; return s.ReadAsync(buffer, 0, n).ContinueWith(t => buffer); } ※ 他のプログラミング言語だと Then という名前が多い いわゆる継続処理(continuation) 後ろにさらに処理がつづいたり、 分岐・ループさせるとかなり面倒
  13. 13. 例: バイト列読み込み • await(C# 5.0※) • C#的には5.0(2012年に正式版)で解決した問題 • “Unityでなければ”、3年前に解消されているはずの問題 static async Task<byte[]> ReadBytes(Stream s, int n) { var buffer = new byte[n]; await s.ReadAsync(buffer, 0, n); return buffer; } 同期の場合とほぼ同じ 書き方で、フリーズしない ※ UnityはC# 3.0。つらい。本気でつらい。日々ソウルジェム濁る
  14. 14. IteratorTasks • しょうがないんで「もどき」を使って運用してる • UnityがCoroutine(yield return)ベースの非同期処理なんで、同じ方針の、 Task互換ライブラリを作って使ってる • yield return+コールバック式のメソッドを、Task互換のクラスに変換 • Coroutineと比べた利点 • MonoBehaviourに依存しない、UnityEngine.dllに依存しない • 戻り値を返せる IteratorTasks https://github.com/OrangeCube/IteratorTasks
  15. 15. 例: バイト列読み込み • iterator → Task • C#的には5.0(2012年に正式版)で解決した問題 • “Unityでなければ”、3年前に解消されているはずの問題 static Task<byte[]> ReadBytes(Stream s, int n) { return Task.Run<byte[]>(c => ReadBytesIterator(s, n, c)); } static IEnumerator ReadBytesIterator(Stream s, int n, Action<byte[]> callback) { var buffer = new byte[n]; var t = s.ReadAsync(buffer, 0, n); if (!t.IsCompleted) yield return t; callback(t.Result); } await の代わりに yield return 1段ラップ return result の代わりに callback(result) 作る側は相変わらず面倒だけども、 使う側は幾分かマシに
  16. 16. 互換 • 標準TaskとIteratorTasksでコード共有 • 結構 #if 分岐でいける • 実際、後述のTaskInteraction, TaskNavigation, 通信コード生成 は #if で両対応してる #if UseIteratorTasks yield return Task.Delay(_pollingIntervalMilliseconds); #else await Task.Delay(_pollingIntervalMilliseconds).ConfigureAwait(false); #endif await のところを yield return に
  17. 17. 反省: Rx使わないの? • 値1つ取るだけ(ラウンドトリップ1回)の非同期処理にRxはいまいち • (awaitと比べるとの話。begin/endとか同期よりはだいぶいい) • (Coroutineそのまま使うくらいならRx推奨) • 同期の時と同じフローで書けなきゃ嫌 • if → where、var → let になるのすら嫌 • if-else で困る • イベント ストリーム的な非同期処理には、うちでもRx的なもの使ってる • こっちはほんとにRxの本領 参考:Reactive Extensions(Rx)入門 UniRx
  18. 18. 反省: バッド ノウハウすぎる • 標準ライブラリの互換ライブラリなんてものは超バッド ノウハウ • 要らなくなるべきもの • UnityがMono 3系になるだけで無用の長物 • 「すぐに要らなくなるはずだろう」が全然すぐじゃなかった… • おかげ様でものすごく安定したけども、それは恥だと思ってる • 所詮は劣化コピー • awaitと比べると不便 • スタック トレースとか追えない
  19. 19. 反省: 両対応は大変 • IteraterTasks(IT)/System.Threading.Tasks(TT)両対応 #if 分岐 共通コード IT版 ライブラリA TT版 ライブラリA IteratorTasks 標準ライブラリライブラリA 共通コード ライブラリA Unity ゲーム 編集ツール(Desktop)や サーバー 4つ1セット
  20. 20. 反省: 両対応は大変 • ライブラリAに依存するライブラリBがあったとして IteratorTasks A A.ForIterator A.ForThreading A.Shared B B.ForIterator B.ForThreading B.Shared ライブラリ1個増やすだけでも 参照設定がかなり面倒
  21. 21. ビューとの相互アクション TaskInteraction チャネルを介したゲーム ロジックとビューとの非同期やり取り やり取りの記録、再現
  22. 22. ビューとの通信 • よくあるシーケンス図 ビュー ゲーム ロジック サーバー ボタンAをタップ アニメーション表示 アニメーション表示 ボタンBをタップ よくあるミス: こいつが処理の起点 本来: こいつが処理の主体
  23. 23. よくあるダメな実装 • ビューのイベントが起点で、そこに多くのコードが入る class View : MonoBehaviour, IPointerClickHandler { public void OnPointerClick(PointerEventData eventData) { // ここにゲーム ロジック書く } } ダメ!絶対!
  24. 24. ビューとの通信 • ロジックが主体 ビュー ゲーム ロジック 結果のアニメーション表示 ボタンAをタップ コマンド選択して コマンドAが選ばれた 実行結果 アニメーション再生終わった この辺り結構複雑な処理 • コマンド再入力が必要なことも • アニメーションないときも ここが起点
  25. 25. ベタなロジック実装 public void Start() { // 開始処理 var d = CommandSelecting; if (d != null) d(candidates); } public event Action<CommandCandidate[]> CommandSelecting; public void SelectCommand(CommandCandidate selected) { // 選ばれたコマンドを実行 var d = CommandExecuted; if (d != null) d(commandResult); } public event Action<CommandCandidate[]> CommandExecuted; public void EndCommand(CommandCandidate selected) { // ... } ビュー側に「コマンド選択して」 メッセージを投げる ビュー側から「コマンド選択結果」 を呼んでもらう Startの続きの処理 ここでいったん処理中断 ビュー側に「コマンド実行結果」 メッセージを投げる ビュー側から「結果アニメーション再生終わった」 を呼んでもらう SelectCommandの続きの処理
  26. 26. ベタなロジック実装の問題 public void Start() { // 開始処理 var d = CommandSelecting; if (d != null) d(candidates); } public event Action<CommandCandidate[]> CommandSelecting; public void SelectCommand(CommandCandidate selected) { // 選ばれたコマンドを実行 var d = CommandExecuted; if (d != null) d(commandResult); } public event Action<CommandCandidate[]> CommandExecuted; public void EndCommand(CommandCandidate selected) { // ... } 処理がとびとび • フロー図と合わせて見ない と何してるのかわからない 呼んでほしいタイミングでだけ呼ばれる保証が ない • ダメなタイミングで呼ばれた時のエラー処理 が必要 • ビューを作る人がわかるドキュメントが必須
  27. 27. フレームワークによっては • イベントの送り方、結果の戻し方が違ったりはする using GalaSoft.MvvmLight.Messaging; using System.Windows.Input; class BattleEngine { private Messenger _messenger; public void Start() { // 開始処理 _messenger.Send<CommandCandidate[]>(candidates); } public ICommand CommandSelecting { get; private set; } } ※ WPF, MVVM Lightの例 • ビューにメッセージを送る用のライブラリがあったり • ビューからの応答はメソッドじゃなくて、1段階クラスを挟んだり • フレームワークに適したクラスを挟んでるだけで、 やっぱりメッセージ送信と応答の受信がわかれてしんどい 「メッセンジャー パターン」 とか言ったりする
  28. 28. 解決の手がかり: ビューはTask • ロジックから見て、ビュー上の動きは非同期処理(Task) • (コマンド選択など)ユーザーのタップを1つ待つ • (アニメーションなど)時間経過を待つ public static Task AwaitTap(this Button button) public static Task PlayAsync(this Animation anim) public static Task Delay(TimeSpan delay) Task使ったメッセンジャー パターンで解決 Rx使えば、 button.Tap.FirstAsync().ToTask() イベントを1つ待つ
  29. 29. Taskを使ったメッセンジャー • Channelクラス Channel _channel; public async Task RunAsync() { // 開始処理 var selected = await _channel.Send<Command[], Command>(candidates); // 選ばれたコマンドを実行 await _channel.Send<CommandResult>(commandResult); // ... } CommandSelectingメッセージと SelectCommandメソッドをペアに CommandExecutedメッセージと EndCommandメソッドをペアに ビューの処理を 非同期にawait ※ IteratrTasks版だと、awaitのところがyield return
  30. 30. Channelの追加の役割 Channelビュー ロジック コマンド選択して コマンドAが選ばれた 選ばれた 結果の記録 Channel ロジック コマンド選択して 記録した 結果の再現 Channel ロジック コマンド選択して サーバーと 通信 記録 再現 記録・再現ができることで • アプリ再起動時に、続きから再開 • サーバー上でのチート検証 • 対戦履歴の再生 • オンライン対戦・協力プレイ 同じ乱数シードと、 同じユーザー入力を与えれば 実行結果は一緒
  31. 31. 反省 • 他のフレームワークとのつなぎこみをフレームワーク化したい • つなぎ先 • ビュー(データ バインディング)とのやり取り • サーバーとの通信 • 今は、結構手作業 • Channelにメッセージ ハンドラーを登録して、ビューを表示して、ユーザーの選択を入れ て返して… • サーバーAPIたたいて、タイムアウト管理して、通信エラー時の復帰処理して… • アプリのサスペンド時にChannelの途中記録を読みだして、ストレージに保存して、再起 動時に復元して…
  32. 32. ここでいったんデモ Task, TaskInteractionの利用例
  33. 33. デモ内容 • ゲームのログイン時の流れ ゲーム ロジック ゲーム ロジック ビュー • 確認ダイアログ表示 • ユーザー名入力 • 登録状況確認 • 新規登録 • ユーザー データ取得
  34. 34. デモ内容 ゲーム開始 登録状況確認 サーバーと通信 登録済み 利用規約表示 同意します いいえ 名前入力 ビュー表示、ユーザー応答待ち 登録状況確認 有効な名前 NG はい データ取得 まだ 済み プレイ開始 OK
  35. 35. デモ
  36. 36. ページ遷移 TaskNavigation ページ遷移をステート マシンとして管理 遷移トリガーをTaskで表現
  37. 37. • 例: 装備画面 ページ遷移 ユニット 装備 装備一覧 (空欄) 装備詳細 (装備中) 装備一覧 (変更) 装備詳細 (空欄) 比較 (変更) 装備一覧 (装備中) 比較 (装備中) 空欄選択 詳細 変更 戻る 戻る 戻る 選択 変更 選択 戻る 戻る 戻る 装備 選択 戻る 装備 装備 ユニット グループ 装備 グループ 装備強化 グループ
  38. 38. ステート マシンとTask • ページ遷移はステート マシン • ステート • どのページにいる • トリガー • どのボタンをタップした • リストのどの要素をタップした • タイムアウトした ステート A ステート B トリガー1 トリガー2 Rx使えば、 button.Tap.FirstAsync().ToTask() Rx使えば、 list.Items.FirstAsync().ToTask() いずれにしろTaskが使える これら複数のうちの最初の1つを待つ Task.Any( button.AwaitTap(), Task.Delay(timeout)); Task.Delay(timeout)
  39. 39. Taskナビゲーションをフレームワーク化 • ステート マシンの設定例 AddState(S.EquipmentInventory, new Transition { T.Item(ct => Cancel.AwaitTap, () => {}, TransitionKey.PageBack), T.Item(ct => Detail.AwaitTap(ct), x => SelectedItem = x, S.EquipmentDetail), }); AddState(S.EquipmentDetail, … どのステートのときに (一覧画面にいる) どういうトリガーで (戻るボタンを押した) どう遷移する (戻る) (詳細ボタンを押した) (詳細画面に遷移) 遷移前の処理 (選択したアイテムを記憶) CancellationTokenを受け取って Taskを返すメソッド (1つ終わったら残りはキャンセルする) ※ こいつも、WPFとUnityの両方で稼働
  40. 40. フレームワーク化したことで • 「戻る」(AndroidのBackボタン)対応 • ビュー内のデータ(ViewModel※をスタックで保存、pop) • グループ • グループ内遷移: 1つのグループViewModelを共有 • グループ間遷移: 一気に数ページ戻れる • ページのビューはページ内のことに専念 • トリガーを起こすところまでがページの債務 • ページ遷移やViewModelの記録はナビゲーターの債務 ※ ビュー内でだけ必要なデータを記録しておくモデル
  41. 41. 独自のUnityシーン管理 • UnityのApplication.LoadLevel使ってない • LoadLevelの問題 • 全オブジェクトの一斉破棄かけてるみたいでとにかく重たい • リソース リーク防止の一番手っ取り早い方法ではあるけど、いくらなんでも遅い • Application.LoadLevelAdditiveAsyncで読み込んで、自前でシーン管理 • 前のシーンを自前でDestroy • 読み込んだシーンをルート要素につなぎなおし というような処理を、ページ遷移管理のついでにフレームワーク化
  42. 42. 反省: まだ結構定型コードが多い • ステート マシンの設定コードが結構煩雑 • コード生成で対応(Unityエディター拡張) • 結構、黒魔術的 グループ単位で設定 グループ内のページ一覧
  43. 43. 反省: テキストで書くものじゃない • ステート マシン設定なんて、テキスト ベースのプログラミング言語で 書くものじゃない • ↓こういう絵で描けるVisualなDSL (と、編集用エディター拡張)が必要 • 実装も大変だし、カスタマイズ性と両立難しそう • (Visualなエディター拡張ありのナビゲーション フレームワーク自体はUnity用のものもあ るにはある) 装備一覧 (空欄) 装備詳細 (空欄)戻る 選択 戻る 装備
  44. 44. 反省: ダイアログ • 今の実装はページのみ • ダイアログは別系統フレームワーク • 実際の要件的には… • UIデザイナーから上がってくるページ遷移フローはページとダイアログが同 列・混在 • ダイアログの遷移も同じナビゲーション フレームワークで動かしたいことが 多々ある
  45. 45. 通信コード生成 TypeGen API定義・型定義をC#で(C#→C#コード生成)
  46. 46. オンライン ゲーム • サーバーとの通信は定型文が多い • 手書きすると大量に似たようなコードを書く必要がある • シリアライズ、デシリアライズ • HTTP通信、エラー処理 • 多くの通信フレームワークはリフレクションで実現していて… • iOSで死ぬ • 性能的に、携帯端末であまり動的な処理をしたくない コード生成
  47. 47. C# → C# コード生成 • 型定義、メソッド定義はstrongly-typedな言語使うのが楽 • (初代)XML、(2代目)RubyでDSL、(3代目)JSONとかで書いてた • だんだんやりたいことが複雑に • 配列に対応、nullableに対応、ジェネリック、型の派生に対応… • 要するに、型に厳しい言語で書けることと要件変わらなくなった • なら、最初からC#で書けばいい
  48. 48. 型定義例 [Comment("装備品")] class Equipment { [Comment("アイテムID")] int Id; [MasterForeignKey(typeof(EquipmentMaster))] [Comment("アイテムマスターID")] int MasterId; [InventoryForeignKey("Enhancers")] [Comment("装備強化アイテムのID")] int?[] EnhancerIds; [Required(false, true)] [Comment("インスタンスごとの追加能力")] AbilityIndexedValue[] InstanceAbilities; } [Comment("装備する")] void AddEquipment( [Comment("誰の(ユニットのID)")] int unitId , [Comment("何番目の装備スロットに")] int slot , [Comment("何を(装備品のID)")] int equipmentId , [Required] [Comment("変更結果")] out SyncDifference<Unit>[] Units ); 型定義(C#でクラスを書く) API定義(インターフェイスのメソッドを書く) プレーンなクラス、フィールドを書く いくつか、属性を付けて生成結果を制御
  49. 49. 定義C#の読み込み • ビルドしたDLLからリフレクションで読み込み • 普通に System.Type を読んでる • 他の選択肢(作り始めた当時はなかったもの) • System.Reflection.Metadata • 依存先の解決できなくてもDLL単体で読める • Roslyn C# Scripting API (Microsoft.CodeAnalysis.Scripting.CSharp) • 今まだ簡単に使える段階にない • ほんとはこれでやりたかったけども、Roslynのリリース自体が思った以上に遅く
  50. 50. 生成物: 基本 • 型に厳しい言語で面倒なのは、シリアライズとUIバインディング • この辺りを生成 • JSONシリアライズ/デシリアライズ • データバインディング用(INotifyPropertyChanged)実装 • 通信処理 • メソッドと対応するURL作って • 送りたいデータをJSON化してPOST • 返ってきたデータをJSONデシリアライズ
  51. 51. 生成物: 普通のクラス public partial class Equipment { /// <Summary> /// アイテムID /// </Summary> public int Id { get; set; } … public Equipment(int id, …) { … Id = id; … } public Equipment(Equipment x) { … } public static Equipment Clone(Equipment x) { … } プロパティ コンストラクター引数 コピー コンストラクター ディープ クローン
  52. 52. 生成物: JSON化・JSON parse partial class Serializer { private string Key(int index, Equipment _) { switch (index) { case 0: { return "id"; } … default: return null; } } private void Serialize(int index, Equipment x) { switch (index) { case 0: { Serialize(x.Id); break; } … } } … partial class Deserializer { private Equipment Deserialize(string key, Equipment x) { x = x ?? new Equipment(); switch (key) { case "id": { x.Id = Deserialize(default(int)); break; } … } return x; } … コード生成で静的に(ビルド時に)作ってしまえば リフレクション要らない
  53. 53. 生成物: データ バインディング用クラス [Serializable] [DataContract] [FileExtensionsAttribute("Equipment")] [Description("装備品")] public partial class Equipment : BindableBase, IIdentifiable, IChild, IValidatable { /// <Summary> /// アイテムID /// </Summary> [DataMember] [JsonProperty(PropertyName = "id")] public int Id { get { return _id; } set { SetProperty(ref _id, value); } } private int _id; … 主に編集ツール(Windowsデスクトップ、WPF)用 INotifyPropertyChanged実装
  54. 54. 生成物: 通信コード namespace DataModels { public partial class Api : IUnitApi { public Task<AddEquipmentResponse> AddEquipmentAsync(AddEquipmentRequest arg, …) { OnRequest(arg); var t = _client.Post("AddEquipment", "/Hero/addEquipment", arg, … t.ContinueWith(_ => OnResponse(_.Result)); return t; } … HTTP Post JSON Serialize/Deserialize 呼び出し 全API共通のイベント発火 引数・戻り値をそれぞれ1つのクラスにラップ (モック作成でその方が都合がよかった※) ※ JSONでAPIの応答モック データを作れる 引数の追加・削除後のモック コード修正が楽だった
  55. 55. 生成物: その他 • 型定義JSONも出力 • C#で型定義しだす前の旧型式 • (内部的には「contract JSON」と呼んでる) • サーバー側は外注、かつ、PHP • サーバー側のコード生成は発注先に任せてたので、C#を前提にできなかった • プロジェクトの途中でC#定義に切り替えた • 急に形式を変えるわけにもいかなかった • インベントリ/マスター リポジトリ • (次節で説明)
  56. 56. 補足: サーバー側C# • 複雑なロジックだけサーバーもC# • 理由 • 2重開発がさすがに無理 • C#で書く方が楽 • 動かし方 • MonoでPHPと同一サーバー内稼働 • 合成・レベルアップ • ダンジョン、対人バトルのチート検証 • 一部(性能を求める)機能だけWindowsサーバー/IIS • リアルタイム バトル
  57. 57. 反省: .NETの型システム引きずりすぎた • 非null参照型 • void null非許容 null許容 値型 int int? 参照型 string ない※ こいつがつらい • nullを認めたくない場合、[Requred]属性を付けてる • コード生成の分岐が増えて大変 • .NET経験のない人への説明が大変 ※ .NET最大の後悔(million dollar mistake って言われてる) 後からの修正でフレームワークに組み込むのはものすごく大変 Task A() Task B(T arg) Task<U> C() Task<U> D(T arg) Task<void> A(void arg) Task<void> B(T arg) Task<U> C(void arg) Task<U> D(T arg) 引数・戻り値の有無で4パターンの分岐 こう書けると楽だった † どちらも今、C#チームが新機能として検討中だけど、入るとしてC# 7.0(2年くらい先?)
  58. 58. 反省: 高機能化しすぎた • 黒魔術度合いが半端ない • コード生成で、ジェネリックや派生クラスに対応するの結構大変 • リポジトリ(次節で説明)対応がやりすぎ感ある • 結構ぎりぎりのバランスで成り立ってて • 修正入れるのそこそこ大変に • ドキュメント整備できてないので公開してもきっと他人に使えない
  59. 59. 反省: コード増えすぎる • JSON化、すべて静的コード生成 • ソースコード量のうちの結構な割合がJSONがらみ • アプリのバイナリ サイズ肥大 • 一方、利点もあって • ソースコードが目に見えるんで、問題を見つけやすい • JSON読み書きに問題あった時にブレイク ポイント仕掛けられる
  60. 60. リポジトリ Inventories/MasterRepository サーバーとのデータ同期、差分更新 ローカル ストレージにデータをキャッシュ
  61. 61. データは全部サーバー上にある • 必要な分だけ通信でもらってる • クライアント上でも正規化した状態で管理 • 差分更新 Unit Id: 1 MasterId: 1 EquipmentId: 120 Equipment Id: 120 MasterId: 39 EnhancerIds: [ 11, 15, 21 ] Enhancer Id: 11 MasterId: 93 Grade: 4 { { "action": "update", "item": { "id": 1, "master_id": 1, "equipment_id": 82 } }, { "action": "remove", "id": 2 } } 変化したところだけもらう
  62. 62. 問題: インスタンスが変わる • サーバーとの同期でインスタンスが変わる • 漏れなく追従するの、手動では無理 UI インベントリ Unit Id: 1 MasterId: 1 EquipmentId: 120 参照 同期前 UI インベントリ Unit Id: 1 MasterId: 1 EquipmentId: 120 参照 同期後 Unit Id: 1 MasterId: 1 EquipmentId: 82 差分更新の粒度的に プロパティ1つだけの更新でも インスタンス丸ごと新しくなる 古い方参照しっぱなし UI側が更新されない ... インベントリ内でも同様 参照先が変わる 装備 変更
  63. 63. 2系統のデータ • マスター • ユニットや装備の名前、パラメーターなど、ユーザーによらないデータ • ほとんど更新されない • かなりデータ量が多い • インベントリ • ユーザーの手持ちの品 • ことあるごとに更新がかかる デバイスのローカル ストレージに キャッシュを保存しておきたい
  64. 64. マスター リポジトリ • ライブラリを整備 • バージョンとデータをローカルに保存 • バージョンが一致していたらローカルから読む • 不一致ならサーバーから取り直す • ID をキーにした Dictionary 化 • コード生成を整備 • [Master]属性がついている型を束ねて MasterRepository 型を生成 • LoadAsyncメソッドで、上記ライブラリを呼ぶ
  65. 65. インベントリ リポジトリ • ライブラリを整備 • Inventoriesライブラリ • 現在のインスタンスをID検索できる • インスタンスの更新イベントを公開 • コード生成を整備 • 通信APIをフックして、リポジトリを自動更新 • 他のインベントリ、マスターが必要なクラスに それぞれのリポジトリを渡す • ユニット→装備 とかのID検索プロパティを生成
  66. 66. インベントリ リポジトリ • ライブラリを整備 • Inventoriesライブラリ • 現在のインスタンスをID検索できる • インスタンスの更新イベントを公開 • コード生成を整備 • 通信APIをフックして、リポジトリを自動更新 • 他のインベントリ、マスターが必要なクラスに それぞれのリポジトリを渡す • ユニット→装備 とかのID検索プロパティを生成 class DictionaryInventory<T> { IEnumerable<T> Items { get; } IEvent<ChangedArg<T>> Changed { get; } } • IEventはIObservableと似たような機能 • つまり、IEnumerableかつIObservableな型 • 現在の値を取る → IEnumerable • 値の変化をもらう → IObservable • LINQ+Rx で、Where とか Select を定義可能
  67. 67. インベントリ リポジトリ • ライブラリを整備 • Inventoriesライブラリ • 現在のインスタンスをID検索できる • インスタンスの更新イベントを公開 • コード生成を整備 • 通信APIをフックして、リポジトリを自動更新 • 他のインベントリ、マスターが必要なクラスに それぞれのリポジトリを渡す • ユニット→装備 とかのID検索プロパティを生成 internal IEnumerable<IDisposable> Change(SyncDifferenceItem diff) { switch(diff.PropertyName) { case "Unit": return Units.Change(diff.Difference); case "Equipments": return Equipments.Change(diff.Difference); case "Enhancers": return Enhancers.Change(diff.Difference); ...
  68. 68. インベントリ リポジトリ • ライブラリを整備 • Inventoriesライブラリ • 現在のインスタンスをID検索できる • インスタンスの更新イベントを公開 • コード生成を整備 • 通信APIをフックして、リポジトリを自動更新 • 他のインベントリ、マスターが必要なクラスに それぞれのリポジトリを渡す • ユニット→装備 とかのID検索プロパティを生成 public partial class Equipment : IDependent<MasterRepository> { protected MasterRepository _masters; void SetRepository(MasterRepository repository) { _masters = repository; if (InstanceAbilities != null) InstanceAbilities.SetRepository(reposito } public EquipmentMaster EquipmentMaster { get { return _masters.GetEquipment( } 通信APIをフックして、このインターフェイス を持ったクラスにリポジトリを渡す ID検索して所望のインスタンスを得る
  69. 69. 反省: IObservable • IObservableとほぼ同機能な型を作ってしまっている※ • IEvent<T> ≒ IObservable<EventPettern<T>> • Unityがシングル スレッド動作なので、同時実行制御だけさぼってる • IObservable<T>との差は: • senderを取れる • OnError/OnCompleteがない • でも結局、senderはほとんど使ってない • IObservableでよかった • Rxに移行しようか悩み中 • IEventに対して、Rxと同じような、Subject, Where, Select, Subscribe実装してる ※ 時期の問題もあった。今から作るならUniRx使うと思う
  70. 70. データ バインディング Binding-CodeGen ビューには「UI上のどこにどのデータを出したい」だけを記述 データが更新されたらUIを自動更新
  71. 71. UIが多いゲーム • 作ってるゲームの性質的にはUIフレームワーク中心 • 3Dとか物理エンジンとか要らない • むしろ、XAML※的機能がほしい • Data Binding (CommonView, DialogBase) • ConentControl, ItemsControl • UI仮想化 • ゲームだからって常にゲーム フレームワークが最適じゃない • UIが得意なのは一般OS • 一般OSのUIフレームワークの上にゲーム描写を重ねたい • 実際、Win8アプリはXAML UIの上にDirect Xサーフェスを 重ねれる ※ WPF(Windowsデスクトップ)、Silverlight(Webプラグイン)、WinRT(Windowsストア アプリ)の系譜
  72. 72. データ バインディング • データ バインディング • UI系フレームワークの要件: • UI上のどこにどのデータを出したい • データが更新されたらそこだけ更新したい <StackPanel <TextBox Text="{Binding X}" /> <TextBox Text="{Binding Y}" /> </StackPanel> new Point { X = 10, Y = 20, }; オブザーバー パターンで実現 ※ この辺りはこれだけで1時間セッション コースになるので今回は割愛 検索すればWPF/WinRTとか、JavaScript系フレームワークの記事が出てくるはず
  73. 73. データ バインディング コード生成 • 自社フレームワークでは、コード生成で実現 • リフレクションが使えないので • モデルのプロパティと、ビューのプロパティをつなぐだけの簡易なもの [DataContextType(typeof(EquipmentContentModel))] partial class EquipmentContent : MonoBehaviour { [BindingProperty("Equipment")] public Equipment Equipment { set { SetThumbnail(value); } } ビューのコード partial class EquipmentContent { public EquipmentContentModel ViewModel { get … void SourcePropertyChanged(object sender, … { base.SourcePropertyChanged(sender, e); var data = DataContext as EquipmentContentModel; if(data == null) return; if (e.PropertyName == "Equipment") Equipment = data.Equipment; … コード生成結果 コード生成 (Unityエディター拡張)
  74. 74. 型に応じたプレハブの選択 • ContentControl, ItemsControlクラス • たいていのUIは、 • 「このデータ型に対して、このプレハブを作りたい」みたいな要件ばっかり • Unityインスペクターでプレハブを刺しとく • 1要素版がContentControl、リスト版がItemsControl
  75. 75. UI仮想化 • VirtualizingListViewクラス • 仮想化 • リストのうちの、画面に見えてる範囲だけ、プレハブをInstantiate • 残りは作らない、隠れたら消す • これがないと • 「アイテムは100個までしか持てません」みたいなゲームになる • 数が多いと一覧画面に入った瞬間に数秒フリーズ • スクロールもきつい
  76. 76. 反省: 同一プロジェクト内コード生成 • Unityエディター拡張は、コンパイル エラーがある状態で動かせない • コード生成結果でエラーになると、コード生成し直しがままならない • エラーがなくなるまでコードを元に戻してから生成しなおし
  77. 77. まとめ
  78. 78. まとめ (1/2) • IteratorTasks • System.Threading.Tasks.Task もどき • TaskInteraction • チャネルを介したゲーム ロジックとビューとの非同期やり取り • やり取りの記録、再現 • TaskNavigation • ページ遷移をステート マシンとして管理 • 遷移トリガーをTaskで表現
  79. 79. まとめ (2/2) • TypeGen • API定義・型定義をC#で(C#→C#コード生成) • Inventories/MasterRepository • サーバーとのデータ同期、差分更新 • ローカル ストレージにデータをキャッシュ • Binding-CodeGen • ビューには「UI上のどこにどのデータを出したい」だけを記述 • データが更新されたらUIを自動更新

×