Movatterモバイル変換


[0]ホーム

URL:


信之 岩永, profile picture
Uploaded by信之 岩永
PPTX, PDF23,239 views

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

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

Embed presentation

Downloaded 57 times
Orange Cube自社フレームワーク++C++;Orange Cube岩永信之
自己紹介• C#でぐぐれ• C#でiOS/Androidゲームを作っています
本日の話• 自社フレームワークの話• 今、オープンなのは IteratorTasks だけ• できれば随時オープン化していきたい• 要求と解決策を中心に話す• チームが作っているゲームの性質・要求• 要求に対する技術的な課題• Unityという縛りの中での課題• どうフレームワークを整備したか• 見せれる範囲で実際のコード• 実物デモ
背景どういうものを作っていて、どういう要求があった
チームが作っているもの1. ストラテジー2. RPG、リアルタイム バトルもの3. ストラテジー
前提: 作っているゲームの性質• サーバーとの通信だらけ• 非同期処理を楽にしたい• 差分更新、更新された部分の変更通知がほしい• アクション性は低い/結構なページ数• ゲーム系フレームワークよりも、UI系フレームワーク使いたい
こんなフレームワークができあがりました• IteratorTasks• TaskInteraction• TaskNavigation• TypeGen• Inventories/MasterRepository• Binding-CodeGenライブコーディングでデモあり
非同期処理IteratorTasksSystem.Threading.Tasks.Task もどき
非同期処理• スマホゲームなんて非同期処理の塊• いろんなロジックがサーバー上で動いてる• サーバーからもらったデータを表示• そうでなくても、ネイティブUIには非同期処理が必要• 参考: フリーズしないアプリケーションの作り方エンド・ユーザーは、0.5秒のフリーズでストレスを感じ、3秒のフリーズはバグだと思う
例: バイト列読み込み• 同期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];s.BeginRead(buffer, 0, n, r =>{var result = s.EndRead(r);callback(buffer);}, null);}2個のメソッドをペアで呼ぶ必要あり後ろにさらに処理がつづいたり、分岐・ループさせるとかなり面倒
例: バイト列読み込み• 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)後ろにさらに処理がつづいたり、分岐・ループさせるとかなり面倒
例: バイト列読み込み• 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。つらい。本気でつらい。日々ソウルジェム濁る
IteratorTasks• しょうがないんで「もどき」を使って運用してる• UnityがCoroutine(yield return)ベースの非同期処理なんで、同じ方針の、Task互換ライブラリを作って使ってる• yield return+コールバック式のメソッドを、Task互換のクラスに変換• Coroutineと比べた利点• MonoBehaviourに依存しない、UnityEngine.dllに依存しない• 戻り値を返せるIteratorTaskshttps://github.com/OrangeCube/IteratorTasks
例: バイト列読み込み• 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 return1段ラップreturn result の代わりにcallback(result) 作る側は相変わらず面倒だけども、使う側は幾分かマシに
互換• 標準TaskとIteratorTasksでコード共有• 結構 #if 分岐でいける• 実際、後述のTaskInteraction, TaskNavigation, 通信コード生成 は#if で両対応してる#if UseIteratorTasksyield return Task.Delay(_pollingIntervalMilliseconds);#elseawait Task.Delay(_pollingIntervalMilliseconds).ConfigureAwait(false);#endifawait のところをyield return に
反省: Rx使わないの?• 値1つ取るだけ(ラウンドトリップ1回)の非同期処理にRxはいまいち• (awaitと比べるとの話。begin/endとか同期よりはだいぶいい)• (Coroutineそのまま使うくらいならRx推奨)• 同期の時と同じフローで書けなきゃ嫌• if → where、var → let になるのすら嫌• if-else で困る• イベント ストリーム的な非同期処理には、うちでもRx的なもの使ってる• こっちはほんとにRxの本領参考:Reactive Extensions(Rx)入門UniRx
反省: バッド ノウハウすぎる• 標準ライブラリの互換ライブラリなんてものは超バッド ノウハウ• 要らなくなるべきもの• UnityがMono 3系になるだけで無用の長物• 「すぐに要らなくなるはずだろう」が全然すぐじゃなかった…• おかげ様でものすごく安定したけども、それは恥だと思ってる• 所詮は劣化コピー• awaitと比べると不便• スタック トレースとか追えない
反省: 両対応は大変• IteraterTasks(IT)/System.Threading.Tasks(TT)両対応#if 分岐共通コードIT版ライブラリATT版ライブラリAIteratorTasks 標準ライブラリライブラリA共通コードライブラリAUnityゲーム編集ツール(Desktop)やサーバー4つ1セット
反省: 両対応は大変• ライブラリAに依存するライブラリBがあったとしてIteratorTasksAA.ForIteratorA.ForThreadingA.SharedBB.ForIteratorB.ForThreadingB.Sharedライブラリ1個増やすだけでも参照設定がかなり面倒
ビューとの相互アクションTaskInteractionチャネルを介したゲーム ロジックとビューとの非同期やり取りやり取りの記録、再現
ビューとの通信• よくあるシーケンス図ビュー ゲーム ロジック サーバーボタンAをタップアニメーション表示アニメーション表示ボタンBをタップよくあるミス:こいつが処理の起点本来:こいつが処理の主体
よくあるダメな実装• ビューのイベントが起点で、そこに多くのコードが入るclass View : MonoBehaviour, IPointerClickHandler{public void OnPointerClick(PointerEventData eventData){// ここにゲーム ロジック書く}} ダメ!絶対!
ビューとの通信• ロジックが主体ビュー ゲーム ロジック結果のアニメーション表示ボタンAをタップコマンド選択してコマンドAが選ばれた実行結果アニメーション再生終わったこの辺り結構複雑な処理• コマンド再入力が必要なことも• アニメーションないときもここが起点
ベタなロジック実装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の続きの処理
ベタなロジック実装の問題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){// ...}処理がとびとび• フロー図と合わせて見ないと何してるのかわからない呼んでほしいタイミングでだけ呼ばれる保証がない• ダメなタイミングで呼ばれた時のエラー処理が必要• ビューを作る人がわかるドキュメントが必須
フレームワークによっては• イベントの送り方、結果の戻し方が違ったりはする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段階クラスを挟んだり• フレームワークに適したクラスを挟んでるだけで、やっぱりメッセージ送信と応答の受信がわかれてしんどい「メッセンジャー パターン」とか言ったりする
解決の手がかり: ビューは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つ待つ
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
Channelの追加の役割Channelビュー ロジックコマンド選択してコマンドAが選ばれた選ばれた結果の記録Channel ロジックコマンド選択して記録した結果の再現Channel ロジックコマンド選択してサーバーと通信記録 再現 記録・再現ができることで• アプリ再起動時に、続きから再開• サーバー上でのチート検証• 対戦履歴の再生• オンライン対戦・協力プレイ同じ乱数シードと、同じユーザー入力を与えれば実行結果は一緒
反省• 他のフレームワークとのつなぎこみをフレームワーク化したい• つなぎ先• ビュー(データ バインディング)とのやり取り• サーバーとの通信• 今は、結構手作業• Channelにメッセージ ハンドラーを登録して、ビューを表示して、ユーザーの選択を入れて返して…• サーバーAPIたたいて、タイムアウト管理して、通信エラー時の復帰処理して…• アプリのサスペンド時にChannelの途中記録を読みだして、ストレージに保存して、再起動時に復元して…
ここでいったんデモTask, TaskInteractionの利用例
デモ内容• ゲームのログイン時の流れゲームロジックゲームロジックビュー• 確認ダイアログ表示• ユーザー名入力• 登録状況確認• 新規登録• ユーザー データ取得
デモ内容ゲーム開始登録状況確認サーバーと通信登録済み利用規約表示 同意しますいいえ名前入力ビュー表示、ユーザー応答待ち登録状況確認 有効な名前NGはいデータ取得まだ済みプレイ開始OK
デモ
ページ遷移TaskNavigationページ遷移をステート マシンとして管理遷移トリガーをTaskで表現
• 例: 装備画面ページ遷移ユニット装備装備一覧(空欄)装備詳細(装備中)装備一覧(変更)装備詳細(空欄)比較(変更)装備一覧(装備中)比較(装備中)空欄選択詳細変更戻る戻る戻る選択変更選択戻る戻る戻る装備選択戻る装備装備ユニットグループ装備グループ装備強化グループ
ステート マシンとTask• ページ遷移はステート マシン• ステート• どのページにいる• トリガー• どのボタンをタップした• リストのどの要素をタップした• タイムアウトしたステートAステートBトリガー1トリガー2Rx使えば、button.Tap.FirstAsync().ToTask()Rx使えば、list.Items.FirstAsync().ToTask()いずれにしろTaskが使えるこれら複数のうちの最初の1つを待つTask.Any(button.AwaitTap(),Task.Delay(timeout));Task.Delay(timeout)
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の両方で稼働
フレームワーク化したことで• 「戻る」(AndroidのBackボタン)対応• ビュー内のデータ(ViewModel※をスタックで保存、pop)• グループ• グループ内遷移: 1つのグループViewModelを共有• グループ間遷移: 一気に数ページ戻れる• ページのビューはページ内のことに専念• トリガーを起こすところまでがページの債務• ページ遷移やViewModelの記録はナビゲーターの債務※ ビュー内でだけ必要なデータを記録しておくモデル
独自のUnityシーン管理• UnityのApplication.LoadLevel使ってない• LoadLevelの問題• 全オブジェクトの一斉破棄かけてるみたいでとにかく重たい• リソース リーク防止の一番手っ取り早い方法ではあるけど、いくらなんでも遅い• Application.LoadLevelAdditiveAsyncで読み込んで、自前でシーン管理• 前のシーンを自前でDestroy• 読み込んだシーンをルート要素につなぎなおしというような処理を、ページ遷移管理のついでにフレームワーク化
反省: まだ結構定型コードが多い• ステート マシンの設定コードが結構煩雑• コード生成で対応(Unityエディター拡張)• 結構、黒魔術的グループ単位で設定グループ内のページ一覧
反省: テキストで書くものじゃない• ステート マシン設定なんて、テキスト ベースのプログラミング言語で書くものじゃない• ↓こういう絵で描けるVisualなDSL (と、編集用エディター拡張)が必要• 実装も大変だし、カスタマイズ性と両立難しそう• (Visualなエディター拡張ありのナビゲーション フレームワーク自体はUnity用のものもあるにはある)装備一覧(空欄)装備詳細(空欄)戻る選択戻る装備
反省: ダイアログ• 今の実装はページのみ• ダイアログは別系統フレームワーク• 実際の要件的には…• UIデザイナーから上がってくるページ遷移フローはページとダイアログが同列・混在• ダイアログの遷移も同じナビゲーション フレームワークで動かしたいことが多々ある
通信コード生成TypeGenAPI定義・型定義をC#で(C#→C#コード生成)
オンライン ゲーム• サーバーとの通信は定型文が多い• 手書きすると大量に似たようなコードを書く必要がある• シリアライズ、デシリアライズ• HTTP通信、エラー処理• 多くの通信フレームワークはリフレクションで実現していて…• iOSで死ぬ• 性能的に、携帯端末であまり動的な処理をしたくないコード生成
C# → C# コード生成• 型定義、メソッド定義はstrongly-typedな言語使うのが楽• (初代)XML、(2代目)RubyでDSL、(3代目)JSONとかで書いてた• だんだんやりたいことが複雑に• 配列に対応、nullableに対応、ジェネリック、型の派生に対応…• 要するに、型に厳しい言語で書けることと要件変わらなくなった• なら、最初からC#で書けばいい
型定義例[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定義(インターフェイスのメソッドを書く)プレーンなクラス、フィールドを書くいくつか、属性を付けて生成結果を制御
定義C#の読み込み• ビルドしたDLLからリフレクションで読み込み• 普通に System.Type を読んでる• 他の選択肢(作り始めた当時はなかったもの)• System.Reflection.Metadata• 依存先の解決できなくてもDLL単体で読める• Roslyn C# Scripting API (Microsoft.CodeAnalysis.Scripting.CSharp)• 今まだ簡単に使える段階にない• ほんとはこれでやりたかったけども、Roslynのリリース自体が思った以上に遅く
生成物: 基本• 型に厳しい言語で面倒なのは、シリアライズとUIバインディング• この辺りを生成• JSONシリアライズ/デシリアライズ• データバインディング用(INotifyPropertyChanged)実装• 通信処理• メソッドと対応するURL作って• 送りたいデータをJSON化してPOST• 返ってきたデータをJSONデシリアライズ
生成物: 普通のクラス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) { … }プロパティコンストラクター引数コピー コンストラクターディープ クローン
生成物: JSON化・JSON parsepartial 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;}…コード生成で静的に(ビルド時に)作ってしまえばリフレクション要らない
生成物: データ バインディング用クラス[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実装
生成物: 通信コード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 PostJSON Serialize/Deserialize 呼び出し全API共通のイベント発火引数・戻り値をそれぞれ1つのクラスにラップ(モック作成でその方が都合がよかった※)※ JSONでAPIの応答モック データを作れる引数の追加・削除後のモック コード修正が楽だった
生成物: その他• 型定義JSONも出力• C#で型定義しだす前の旧型式• (内部的には「contract JSON」と呼んでる)• サーバー側は外注、かつ、PHP• サーバー側のコード生成は発注先に任せてたので、C#を前提にできなかった• プロジェクトの途中でC#定義に切り替えた• 急に形式を変えるわけにもいかなかった• インベントリ/マスター リポジトリ• (次節で説明)
補足: サーバー側C#• 複雑なロジックだけサーバーもC#• 理由• 2重開発がさすがに無理• C#で書く方が楽• 動かし方• MonoでPHPと同一サーバー内稼働• 合成・レベルアップ• ダンジョン、対人バトルのチート検証• 一部(性能を求める)機能だけWindowsサーバー/IIS• リアルタイム バトル
反省: .NETの型システム引きずりすぎた• 非null参照型• voidnull非許容 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年くらい先?)
反省: 高機能化しすぎた• 黒魔術度合いが半端ない• コード生成で、ジェネリックや派生クラスに対応するの結構大変• リポジトリ(次節で説明)対応がやりすぎ感ある• 結構ぎりぎりのバランスで成り立ってて• 修正入れるのそこそこ大変に• ドキュメント整備できてないので公開してもきっと他人に使えない
反省: コード増えすぎる• JSON化、すべて静的コード生成• ソースコード量のうちの結構な割合がJSONがらみ• アプリのバイナリ サイズ肥大• 一方、利点もあって• ソースコードが目に見えるんで、問題を見つけやすい• JSON読み書きに問題あった時にブレイク ポイント仕掛けられる
リポジトリInventories/MasterRepositoryサーバーとのデータ同期、差分更新ローカル ストレージにデータをキャッシュ
データは全部サーバー上にある• 必要な分だけ通信でもらってる• クライアント上でも正規化した状態で管理• 差分更新UnitId: 1MasterId: 1EquipmentId: 120EquipmentId: 120MasterId: 39EnhancerIds: [ 11, 15, 21 ]EnhancerId: 11MasterId: 93Grade: 4{{ "action": "update", "item": { "id": 1, "master_id": 1, "equipment_id": 82 } },{ "action": "remove", "id": 2 }}変化したところだけもらう
問題: インスタンスが変わる• サーバーとの同期でインスタンスが変わる• 漏れなく追従するの、手動では無理UI インベントリUnitId: 1MasterId: 1EquipmentId: 120参照同期前UI インベントリUnitId: 1MasterId: 1EquipmentId: 120参照同期後UnitId: 1MasterId: 1EquipmentId: 82差分更新の粒度的にプロパティ1つだけの更新でもインスタンス丸ごと新しくなる古い方参照しっぱなしUI側が更新されない...インベントリ内でも同様参照先が変わる装備変更
2系統のデータ• マスター• ユニットや装備の名前、パラメーターなど、ユーザーによらないデータ• ほとんど更新されない• かなりデータ量が多い• インベントリ• ユーザーの手持ちの品• ことあるごとに更新がかかるデバイスのローカル ストレージにキャッシュを保存しておきたい
マスター リポジトリ• ライブラリを整備• バージョンとデータをローカルに保存• バージョンが一致していたらローカルから読む• 不一致ならサーバーから取り直す• ID をキーにした Dictionary 化• コード生成を整備• [Master]属性がついている型を束ねて MasterRepository 型を生成• LoadAsyncメソッドで、上記ライブラリを呼ぶ
インベントリ リポジトリ• ライブラリを整備• Inventoriesライブラリ• 現在のインスタンスをID検索できる• インスタンスの更新イベントを公開• コード生成を整備• 通信APIをフックして、リポジトリを自動更新• 他のインベントリ、マスターが必要なクラスにそれぞれのリポジトリを渡す• ユニット→装備 とかのID検索プロパティを生成
インベントリ リポジトリ• ライブラリを整備• 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 を定義可能
インベントリ リポジトリ• ライブラリを整備• 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);...
インベントリ リポジトリ• ライブラリを整備• 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検索して所望のインスタンスを得る
反省: IObservable• IObservableとほぼ同機能な型を作ってしまっている※• IEvent<T> ≒ IObservable<EventPettern<T>>• Unityがシングル スレッド動作なので、同時実行制御だけさぼってる• IObservable<T>との差は:• senderを取れる• OnError/OnCompleteがない• でも結局、senderはほとんど使ってない• IObservableでよかった• Rxに移行しようか悩み中• IEventに対して、Rxと同じような、Subject, Where, Select, Subscribe実装してる※ 時期の問題もあった。今から作るならUniRx使うと思う
データ バインディングBinding-CodeGenビューには「UI上のどこにどのデータを出したい」だけを記述データが更新されたらUIを自動更新
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ストア アプリ)の系譜
データ バインディング• データ バインディング• UI系フレームワークの要件:• UI上のどこにどのデータを出したい• データが更新されたらそこだけ更新したい<StackPanel<TextBox Text="{Binding X}" /><TextBox Text="{Binding Y}" /></StackPanel>new Point{X = 10,Y = 20,};オブザーバー パターンで実現※ この辺りはこれだけで1時間セッション コースになるので今回は割愛検索すればWPF/WinRTとか、JavaScript系フレームワークの記事が出てくるはず
データ バインディング コード生成• 自社フレームワークでは、コード生成で実現• リフレクションが使えないので• モデルのプロパティと、ビューのプロパティをつなぐだけの簡易なもの[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エディター拡張)
型に応じたプレハブの選択• ContentControl, ItemsControlクラス• たいていのUIは、• 「このデータ型に対して、このプレハブを作りたい」みたいな要件ばっかり• Unityインスペクターでプレハブを刺しとく• 1要素版がContentControl、リスト版がItemsControl
UI仮想化• VirtualizingListViewクラス• 仮想化• リストのうちの、画面に見えてる範囲だけ、プレハブをInstantiate• 残りは作らない、隠れたら消す• これがないと• 「アイテムは100個までしか持てません」みたいなゲームになる• 数が多いと一覧画面に入った瞬間に数秒フリーズ• スクロールもきつい
反省: 同一プロジェクト内コード生成• Unityエディター拡張は、コンパイル エラーがある状態で動かせない• コード生成結果でエラーになると、コード生成し直しがままならない• エラーがなくなるまでコードを元に戻してから生成しなおし
まとめ
まとめ (1/2)• IteratorTasks• System.Threading.Tasks.Task もどき• TaskInteraction• チャネルを介したゲーム ロジックとビューとの非同期やり取り• やり取りの記録、再現• TaskNavigation• ページ遷移をステート マシンとして管理• 遷移トリガーをTaskで表現
まとめ (2/2)• TypeGen• API定義・型定義をC#で(C#→C#コード生成)• Inventories/MasterRepository• サーバーとのデータ同期、差分更新• ローカル ストレージにデータをキャッシュ• Binding-CodeGen• ビューには「UI上のどこにどのデータを出したい」だけを記述• データが更新されたらUIを自動更新

Recommended

PPTX
C#/.NETがやっていること 第二版
PPTX
C#言語機能の作り方
PDF
組み込みでこそC++を使う10の理由
PDF
20分くらいでわかった気分になれるC++20コルーチン
 
PPTX
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
PDF
プログラムを高速化する話
PDF
【Unity】 Behavior TreeでAIを作る
PDF
ワタシはSingletonがキライだ
PDF
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~
PPTX
C# 9.0 / .NET 5.0
PPTX
CPU / GPU高速化セミナー!性能モデルの理論と実践:理論編
PDF
A quick tour of the Cysharp OSS
PDF
constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ
PDF
「龍が如くスタジオ」のQAエンジニアリング技術を結集した全自動バグ取りシステム
PPTX
【CEDEC2018】一歩先のUnityでのパフォーマンス/メモリ計測、デバッグ術
PPTX
C#で速度を極めるいろは
PDF
マーク&スイープ勉強会
 
PPTX
OpenVRやOpenXRの基本的なことを調べてみた
PPTX
C#とILとネイティブと
PDF
【Unite 2018 Tokyo】60fpsのその先へ!スマホの物量限界に挑んだSTG「アカとブルー」の開発設計
PDF
Unityでパフォーマンスの良いUIを作る為のTips
PDF
Rustで楽しむ競技プログラミング
 
PDF
やりなおせる Git 入門
PPTX
なぜなにリアルタイムレンダリング
PDF
Scapyで作る・解析するパケット
PDF
Unity開発で使える設計の話+Zenjectの紹介
PPTX
Windows system - memory개념잡기
PPT
Lockfree Queue
PPTX
C#や.NET Frameworkがやっていること
PDF
History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法

More Related Content

PPTX
C#/.NETがやっていること 第二版
PPTX
C#言語機能の作り方
PDF
組み込みでこそC++を使う10の理由
PDF
20分くらいでわかった気分になれるC++20コルーチン
 
PPTX
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
PDF
プログラムを高速化する話
PDF
【Unity】 Behavior TreeでAIを作る
PDF
ワタシはSingletonがキライだ
C#/.NETがやっていること 第二版
C#言語機能の作り方
組み込みでこそC++を使う10の理由
20分くらいでわかった気分になれるC++20コルーチン
 
[NDC 2018] 신입 개발자가 알아야 할 윈도우 메모리릭 디버깅
プログラムを高速化する話
【Unity】 Behavior TreeでAIを作る
ワタシはSingletonがキライだ

What's hot

PDF
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~
PPTX
C# 9.0 / .NET 5.0
PPTX
CPU / GPU高速化セミナー!性能モデルの理論と実践:理論編
PDF
A quick tour of the Cysharp OSS
PDF
constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ
PDF
「龍が如くスタジオ」のQAエンジニアリング技術を結集した全自動バグ取りシステム
PPTX
【CEDEC2018】一歩先のUnityでのパフォーマンス/メモリ計測、デバッグ術
PPTX
C#で速度を極めるいろは
PDF
マーク&スイープ勉強会
 
PPTX
OpenVRやOpenXRの基本的なことを調べてみた
PPTX
C#とILとネイティブと
PDF
【Unite 2018 Tokyo】60fpsのその先へ!スマホの物量限界に挑んだSTG「アカとブルー」の開発設計
PDF
Unityでパフォーマンスの良いUIを作る為のTips
PDF
Rustで楽しむ競技プログラミング
 
PDF
やりなおせる Git 入門
PPTX
なぜなにリアルタイムレンダリング
PDF
Scapyで作る・解析するパケット
PDF
Unity開発で使える設計の話+Zenjectの紹介
PPTX
Windows system - memory개념잡기
PPT
Lockfree Queue
【Unite Tokyo 2018 Training Day】C#JobSystem & ECSでCPUを極限まで使い倒そう ~C# JobSystem 編~
C# 9.0 / .NET 5.0
CPU / GPU高速化セミナー!性能モデルの理論と実践:理論編
A quick tour of the Cysharp OSS
constexpr関数はコンパイル時処理。これはいい。実行時が霞んで見える。cpuの嬌声が聞こえてきそうだ
「龍が如くスタジオ」のQAエンジニアリング技術を結集した全自動バグ取りシステム
【CEDEC2018】一歩先のUnityでのパフォーマンス/メモリ計測、デバッグ術
C#で速度を極めるいろは
マーク&スイープ勉強会
 
OpenVRやOpenXRの基本的なことを調べてみた
C#とILとネイティブと
【Unite 2018 Tokyo】60fpsのその先へ!スマホの物量限界に挑んだSTG「アカとブルー」の開発設計
Unityでパフォーマンスの良いUIを作る為のTips
Rustで楽しむ競技プログラミング
 
やりなおせる Git 入門
なぜなにリアルタイムレンダリング
Scapyで作る・解析するパケット
Unity開発で使える設計の話+Zenjectの紹介
Windows system - memory개념잡기
Lockfree Queue

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

PPTX
C#や.NET Frameworkがやっていること
PDF
History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
PDF
コルーチンの使い方
PPTX
C#の書き方
PDF
C#次世代非同期処理概観 - Task vs Reactive Extensions
PDF
async/awaitダークサイド is 何
PDF
iOSやAndroidアプリ開発のGoodPractice
PDF
BNN CAMP vol.3  インタラクションデザインの現在―プログラミング初心者のためのopenFrameworks入門 1
PDF
いろいろ見せますLord of Knightsのクライアント開発事例紹介
PPTX
Interaction channel
PDF
C# を使い倒す!クロス プラットフォーム アプリ開発とクラウド連携の新潮流 - Xamarin セッション
PPTX
C#の書き方
PPTX
Clrh 110827 wfho
PPTX
Pronama 0707 wf4
PDF
Windows 8時代のUXを支える非同期プログラミング
KEY
1.29.user,user,user
PPT
20050903
PDF
Async design with Unity3D
PDF
Why Reactive Matters #ScalaMatsuri
PPTX
Reactive Programming
C#や.NET Frameworkがやっていること
History & Practices for UniRx UniRxの歴史、或いは開発(中)タイトルの用例と落とし穴の回避法
コルーチンの使い方
C#の書き方
C#次世代非同期処理概観 - Task vs Reactive Extensions
async/awaitダークサイド is 何
iOSやAndroidアプリ開発のGoodPractice
BNN CAMP vol.3  インタラクションデザインの現在―プログラミング初心者のためのopenFrameworks入門 1
いろいろ見せますLord of Knightsのクライアント開発事例紹介
Interaction channel
C# を使い倒す!クロス プラットフォーム アプリ開発とクラウド連携の新潮流 - Xamarin セッション
C#の書き方
Clrh 110827 wfho
Pronama 0707 wf4
Windows 8時代のUXを支える非同期プログラミング
1.29.user,user,user
20050903
Async design with Unity3D
Why Reactive Matters #ScalaMatsuri
Reactive Programming

More from 信之 岩永

PPTX
C# コンパイラーの書き換え作業の話
PPTX
async/await のしくみ
PPTX
C# 8.0 非同期ストリーム
PPTX
Unicode文字列処理
PPTX
C# 8.0 null許容参照型
PPTX
Unityで使える C# 6.0~と .NET 4.6
PPTX
.NET Compiler Platform
PPTX
C# 8.0 Preview in Visual Studio 2019 (16.0)
PPTX
今から始める、Windows 10&新.NETへの移行戦略
PPTX
今から始める、Windows 10&新.NETへの移行戦略
PPTX
C# design note sep 2014
PPTX
C# 7.2 with .NET Core 2.1
PPTX
Coding Interview
PPTX
.NET vNext
PPTX
Deep Dive C# 6.0
PPTX
.NET Core 2.x 時代の C#
PPTX
Code Contracts in .NET 4
PPTX
Modern .NET
PPTX
YouTube ライブ配信するようになった話
PPTX
それっぽく、適当に
C# コンパイラーの書き換え作業の話
async/await のしくみ
C# 8.0 非同期ストリーム
Unicode文字列処理
C# 8.0 null許容参照型
Unityで使える C# 6.0~と .NET 4.6
.NET Compiler Platform
C# 8.0 Preview in Visual Studio 2019 (16.0)
今から始める、Windows 10&新.NETへの移行戦略
今から始める、Windows 10&新.NETへの移行戦略
C# design note sep 2014
C# 7.2 with .NET Core 2.1
Coding Interview
.NET vNext
Deep Dive C# 6.0
.NET Core 2.x 時代の C#
Code Contracts in .NET 4
Modern .NET
YouTube ライブ配信するようになった話
それっぽく、適当に

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

Editor's Notes

  • #10 要するに、「データの更新に画面遷移(リロード)が必要とか、画面遷移するたびにロード長いとかそんなクソゲー作るな」という話
  • #15 MonoBehaviour は1人で債務を持ちすぎ。債務分割まったくできてないど素人設計
  • #16 ちなみに、C# 5.0のawaitはこれと似たようなコードに展開されてる。要は、C# 5.0と同じことを自前でやってる。
  • #21 つまり、UnityとっととMonoのバージョン上げろよ
  • #29 命名規約的には、いまのところ await + イベント名でメソッド作ってる。いまいちかなぁと思いつつ定着。FirstAsync 無双。
  • #30 Begin/End系の非同期処理と同様、行きと帰りが違う口なのが問題。ラウンドトリップをTaskで一本化してしまえば案外楽。逆に言うと、メッセンジャー/コマンドのペアに分解する補助関数かけば、既存MVVMフレームワークにもつなげる。この辺りは後でデモでライブコーディングします
  • #39 ページ遷移をステート マシンで管理しようって言うのは割かしよくある発想。https://msdn.microsoft.com/ja-jp/magazine/dn818499.aspx
  • #64 インベントリは紆余曲折あった・ 昔、要るデータだけ取ってたら更新が保守しきれなくて心折れる・全同期やり始める・重たすぎてサーバー側に怒られる・差分更新をフレームワーク化、コード生成(今ここ)
  • #70 IteratorTasksでも言った通り、劣化コピーの実装は嫌
  • #72 つまるところ、「C#でクロスプラットフォーム」ってこと以外にUnity使ってる利点もない。マップ表示だけかなぁ、ゲーム的な描画最適化頑張らないといけないの
  • #73 このパターン守れないとだいたいスパゲッティ コード化して大変

[8]ページ先頭

©2009-2025 Movatter.jp