×
  • Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
 

「宴」実装時に得られたUnityプログラムノウハウ

on

  • 1,455 views

AssetStoreで公開中のUnity用ADV制作ツール「宴」

AssetStoreで公開中のUnity用ADV制作ツール「宴」
その実装時に得られたUnityプログラムのノウハウのまとめ

Statistics

Views

Total Views
1,455
Views on SlideShare
1,269
Embed Views
186

Actions

Likes
16
Downloads
28
Comments
0

7 Embeds 186

https://twitter.com 68
http://terasur.blog.fc2.com 67
http://takashicompany.hatenablog.com 27
http://feedly.com 12
http://admin.blog.fc2.com 11
https://www.chatwork.com 1
http://www.plurk.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via SlideShare as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

「宴」実装時に得られたUnityプログラムノウハウ 「宴」実装時に得られたUnityプログラムノウハウ Presentation Transcript

  • 「宴」実装時に得られた Unityプログラムのノウハウ Unity用ADV制作ツール「宴」 http://madnesslabo.net/utage/ 2014/05/19 マッドネスラボ代表 時村良平 https://twitter.com/rodostw
  • 概要 • Unityにおけるプログラム一般( C# ) • これが重要!どうやって探して、どこに渡す? • シリアライズ • コルーチン • エディタ拡張 • より具体的なプログラムについて • 2D • 画面サイズに対応 • プロジェクト設定 • ADVエンジン(シナリオ解析) • エクセルの読み込み • リソースのDLとメモリ管理 • セーブロード
  • 「宴」 このスライドでは「宴」の実装を通して得られた Unityプログラムのノウハウを紹介します ADV(ビジュアルノベル)をUnity で制作するためのツール。 AssetStoreで販売中。 https://www.assetstore.unity3d .com/#!/content/15905 詳細は「宴」のサイトを! 無料体験版もあります。 http://madnesslabo.net/utage/
  • 「宴」のプログラムの構成の紹介 参考 以下のスライドでは、参考になりそうなリンクや「宴」のソースコードパスをここに記述 ADVに特化した部分 ゲームプログラム 全般に汎用的に使う サンプルコード 主にUI処理を記 述
  • Unityにおける プログラム一般( C# )
  • これが重要! どうやって探して どう呼び出す? ~ コンポーネントを使いこなそう ~
  • どうやって探す? ~ 目当てのコンポーネントはどこにある? ~
  • • たとえば、GameManagerというコンポーネントから Playerというコンポーネントを使うことを考える。
  • • ただのC#なら プロパティを使う なりして、あらかじ めメンバ変数に セットしておく もちろんこれでもいい または、メソッドの 引数に持たせて 呼び出す
  • コンポーネント ならではの使い方
  • • 一番素直なのはインスペクターを使う GameManagerと Playerのオブジェ クトが別々の場合 ドラッグ&ドロップでGameManagerの インスペクターにPlayerを設定してやる
  • • インスペクターで設定するのが基本だけど プレハブ化すると 他のオブジェクトへの参照はMissingに なってしまう。 他にもスクリプトから動的にオブジェクトを作った場合にも、 インスペクター経由での設定はできない
  • 他の手段は なにがある?
  • • GetComponentを使うパターン GameManagerと Playerのオブジェ クトが同じ場合 インスペクターで設定する必要はない →プレハブ化などすると、インスペクターを使ったオブジェクト の参照は不安定なので、可能ならこのほうが安全で手軽
  • • GetComponentInChildrenを使うパターン Playerが子 親子構造が保障されているときや、 子オブジェクトを動的に生成した場合にも
  • • 親オブジェクトのパターン Playerが親 親子構造が保障されているときや、 親子オブジェクトを動的に生成した場合にも Unity4.5で GetComponentInParentが 追加されたのでそっちのほ うがいい
  • • GameObject.Findを使うパターン 名前だけは分かっ ている シーン編集時には存在しないオブジェクト(動的に生成した GameObjectや、他のシーンで生成したGameObject)を参照 するときなど。 速度は遅いので注意 子にあることが分かってる なら、transfom.Findというの もある。
  • • GameObject.FindWithTagを使うパターン GameObject.Findと似ているが、タグを指定するのでちょっと 早い模様。 ただし、タグはプロジェクト間で共有が非常にしづらいので、 汎用性に欠ける 速度はちょっと遅い タグを指定
  • • FindObjectOfType<T>を使うパターン Activeであるのが 必須 GameObject.Findと似ているが、型を指定できるので便利 コンポーネント以外に、TextureなどのAssetの型も指定できる 速度は遅い?
  • 今までは対象が一つの場合を想定 とはいえ複数の場合も応用すればOK 複数用の処理 ・GetComponents ・GetComponentsInChildren ・GameObject.FindGameObjectsWithTag ・FindObjectsOfType
  • • ゲーム中に必ず一つという保障があるなら、シング ルトンを使うパターンもあり 手軽で速度低下もないが、シングルトンで作れなくなったとき に構造を大幅に変える必要がでてくるので、多用は禁物 シングルトンな処理 を作る Unityのシングルトン の書き方はいろいろ あるが、一番単純な パターンがこれ
  • ここまでが基本
  • もうちょい頑張る
  • 遅い処理はキャッシュしよう • FindやGetComponentは遅い 参考 http://docs.unity3d.com/410/Documentation/ScriptReference/index.Performance_Optimization.html プロパティと null 合体演算子を使うと コードがスッキリする! オススメ! 一度みつけたら、変数として保存(キャッシュ)しておくと 二回目以降はすぐ呼び出せる
  • 遅い処理はキャッシュしよう • Transformは注意 Transformの==はカスタムされているのが原因 参考 http://blogs.unity3d.com/2014/05/16/custom-operator-should-we-keep-it/ this.transformは内部でGetComponentしてるようなので、 処キャッシュしたほうがいい。 ただし、 null 合体演算子だとバグる! こうする
  • RequireComponent • そのコンポーネントを使う場合、 必ず同時に使うコンポーネント を指定できる 参考ソース Utage¥Scripts¥ADV¥AdvEngine.cs AddComponentすると、自動的に RequireComponentで指定されてるコン ポーネントもAddされる
  • RequireComponent • これを使うと、安全なコードが書ける。 参考ソース Utage¥Scripts¥ADV¥AdvEngine.cs コンポーネントがあることが保障されているので GetComponetでnullにならない!
  • RequireComponent ・RequireComponent ・プロパティ内のnull合体演算子からのGetComponet 組み合わせて使うと便利!凄くオススメ
  • どう呼び出す? ~ 命令を呼ぶ ~
  • • 普通は、メソッドを呼べばいい
  • • コンポーネントの種類を指定したくないとき 型はPlayerとEnemyの二種類が ある。 C#的には継承を使うのが普通
  • • SendMessage Classの型を気にせず 柔軟に使える メソッド名を指定し てSendMessage
  • • SendMessageの真の力 TargetとFunctionをインスペクター上で変えれば、 ソースコードを変えることなく 好きな相手に好きな処理をさせられる
  • • SendMessageのさらなる力 SendMessageには引数を一つだけもたせられる ただし、返り値はもてない。 それでも、こんな感じで処理を繋げられる。
  • • SendMessage、SendMessage ・・・ 引数をGgamaObjectにすると、「誰から呼ばれたか」 も制約せずに柔軟に使える。 引数をGgamaObject にしても、GetComponentを使 えば、コンポーネントを取得することもできる。 SendMessageからSendMessageを呼ぶことも可能。
  • • SendMessage、SendMessage ・・・ SendMessageはやりすぎる と、わけがわからなくなるの で注意。 参照関係をエディタ上で追 えなくなりがち。 どこからも呼ばれてないよう に見えてしまう
  • • プログラム内で完結させるならデリゲートを使う手もある 型安全に使える。ただし、プログラムを書かないと処理を切り替え られない。 ゲーム内で「プレイヤーと敵」のように頻繁に処理が切り替わる場 合は向かないかも。 ロードの終了とかのフレームをまたぐ処理に使うのがよさげ。 (宴では、ユーザの独自拡張のための入り口とかによく使ってる)
  • SendMessage GameObjectに対して名前指定で処理を呼ぶ。引数を一つだけ持たせ られる。 • メリット • 名前指定なので、ソースコードを変えずに呼び出し先を変えるのに使える。 • たとえば、「ボタンを押されたら呼び出す処理名」などをインスペクターに登録しやすい。 • 複数のコンポーネントに対しても個別に処理をさせらる。 • BroadCastを使えば子階層のオブジェクトにも処理をさせられる。 • デメリット • 返り値がもてない。 • 複数処理がされる場合は、処理順が制御できない? • 非アクティブなオブジェクトには使えない。(ので、初期化処理などには使いづらい)
  • Func,Actionなどのデリゲード(コールバック) C#の標準的な仕組み。 メリット • 型指定なので、ある程度複雑な処理も安全に使いやすい。 • ロード終了時など、「タイミングが来たらこの処理を呼んで」という形で使える。 • 「追加の独自拡張」を想定する場合にも使える。 • 返り値がもてる • デメリット • プログラムとして記述しなければ使えない。 (SendMessageのように「インスペクター上に記述された文字列の処理を呼ぶ」ということはで きない。)
  • シリアライズ ~ Unityのシリアライズは奥が深い・・・・・・ ~
  • シリアライズって? • Wikiより引用 • コンピュータプログラミングにおいて、シリアライズ、もしくはシリアル 化 (serialize) という用語は、次のような異なる2つの意味を有する。 • ある一つの資源を、複数の主体が利用しようとするときに、それを調 整(同期)して、一つの時点では一つの主体だけがそれを利用する ようにすること。この意味では逐次化という訳語が用いられる。対義 語は並列化である。 • ある環境に存在しているオブジェクトをバイト列やXMLフォーマットに 変換すること。この意味では直列化という訳語が用いられる。同義 語にMarshallingがある。対義語は直列化復元ないしデシリアライズ である。
  • Unityのシリアライズ • シーンやプレハブの情報はYAMLという書式で保存している AssetServer使っているなら、 対象のAssetを選択→右クリック→Compare Binary で 中身がみれる。 どこを変えると、どう変わるか見てみるのも一興 参考 http://docs-jp.unity3d.com/Documentation/Manual/FormatDescription.html
  • Unityのシリアライズ 基本
  • シリアライズって? • イメージ的には「インスペクターで扱える」でOk
  • 何がいいことあるの? • シーンやプレハブ、Assetとしてデータを編集・保存できるようになる • インスペクター上のGUIで編集できるようになるので楽 • シリアライズできない=Unityエディタ上では扱いづらいと考えてOK 参考 http://docs-jp.unity3d.com/Documentation/ScriptReference/SerializeField.html
  • どうすればいい? • 基本的に対象になるクラスは • MonoBehaviourを継承するクラス(コンポーネント) • ScriptableObjectを継承するクラス • 通常のC#クラスも[Serializable]を使えばOK • 対象になるメンバ変数は • Public • [SerializeField]を適用すれば、privateな型でもOK 参考 http://docs-jp.unity3d.com/Documentation/ScriptReference/SerializeField.html
  • 似てるけど違う コンポーネントとScriptableObject コンポーネント MonoBehaviourを継承するクラス GameObjectにアタッチする Sceneビューの GameObjectにアタッチ する。 各オブジェクトの 「機能」のイメージ
  • 似てるけど違う コンポーネントとScriptableObject ScriptableObject ProjectビューにAsset として作成される。 つまり、テクスチャな どと同じく素材・デー タのイメージに近い ScriptableObjectを継承するクラス コンポーネントと同じく、 インスペクターを持つ
  • 似てるけど違う コンポーネントとScriptableObject • コンポーネント(MonoBehavior) • オブジェクトにアタッチする「機能」 • ScriptableObject • データの塊(独自定義のAsset) • 宴での使用例「ADVのシナリオデータ、フォント定義、描画順定義・・・など」 ・・・という感じで使い分けるとよさげ。 (本来はScriptableObject はUnityの基本データクラスみたいなもの MonoBehabiorやEditorWindowもScriptableObject を継承しているハズ)
  • シリアライズのための記述方法まとめ • [Serializable] • 独自定義のクラスをシリアライズ 可能にする • クラスに定義する • C#の標準的な機能 • [SerializeFiled] • 非publicなメンバ変数をシリアライズ の対象にする • 変数に定義する • Unity独自の機能 似てるのでまとめ。 使っていればたぶんすぐ慣れる。 参考 http://docs-jp.unity3d.com/Documentation/ScriptReference/SerializeField.html
  • Unityのシリアライズの基本 • Publicか[SerializeFiled]か 参考 http://docs-jp.unity3d.com/Documentation/ScriptReference/SerializeField.html 簡単に書くならpublicにするだけ [SerializeField]にすると、privateな 変数もシリアライズできる こうすれば、プロパティ(get,set)でアクセスを制御できる。 C#的はpublicを使わずに、プロパティを使うのが基本。 Unity的にはどっちがベターか?
  • Unityのシリアライズ 一歩先
  • Dictionaryのシリアライズ • Unity(というかC#)では、 Dictionaryはシリアライズ不可 ( Dictionaryはジェネリックの入れ子 的な使い方をしているせい。詳細は 割愛) • 使用頻度は高いので、シリアラ イズ可能なDictionaryを作った。 • ただしキーは文字列のみとする 参考ソース Utage¥Scripts¥GameLib¥Dictionary
  • ジェネリックのシリアライズ • ジェネリックを使う場合の注意 クラス定義を型ごとにすること MyGenecricClass<int>はダメ class MyGenecricClassInt : MyGenecricClass<int>{} と定義すれば使えるようになる。 参考 http://answers.unity3d.com/questions/214300/serializable-class-using-generics.html
  • ネームスペース&デフォルト引数は禁止 • ネームスペースを使った場合、デフォルト引数を使ったメソッドを持つ クラスはシリアライズができなくなる。 • デフォルト引数は使わないように修正。 • コルーチンにデフォルト引数を使っている場合はちょっと注意。 https://www.facebook.com/groups/unityuserj/permalink/620981311295146/? comment_id=621128654613745&offset=0&total_comments=7 参考 https://www.facebook.com/permalink.php?story_fbid=541323405927604&id=167184853341463 Unity4.5では問題なく使える模様 http://terasur.blog.fc2.com/blog-entry-795.html
  • 子 親 親子参照でバグ? • ScriptableObject内親子構 造にある[Serializable]なク ラスに互いへの参照をも たせた • なぜかUnityエディタがこ とあるごとに遅くなった。 (コンパイルで一分以上 かかる) • アプリの起動時は特に影 響なし。エディタのみ? 参考ソース Utage¥Scripts¥GameLib¥StringGrid こうすると 妙に重い コールバックで参照する というよくわからない処 理にしたら解消。 Unityのバグ?
  • Mono DLL ~ たぶん殆どの人は使わなくてもOK ~
  • DLL化 • C#のプログラムをDLL化して使うこと ができる。 • 体験版などでコードを隠蔽する場合 や、複数プロジェクトにまたがる共 通ライブラリを作る場合に使える。 参考 http://docs-jp.unity3d.com/Documentation/Manual/UsingDLL.html http://terasur.blog.fc2.com/blog-entry-312.html
  • またしてもデフォルト引数が! • シリアライズ関係ない、通常のC#のクラスでもデフォルト引数はダメ な模様。 • DLLは.Net3.5以下しか対応してないけど、デフォルト引数は3.5だとコ ンパイルできない模様 もうUnityでデフォルト引数は全面的に避けたほうがいいかも・・・ 4.5以降ならDLL使わなければいいので、ほぼ問題ない?
  • Missing・・・ • コンポーネントやScriptableObjectをDLL化する と、作成済みのシーンやプレハブは全部 Missingになってしまう・・・ (GUIDを使ったUnity内部での参照関係がおかしく なってしまうらしい)
  • Missing・・・ • 回避するには継承を使う。 参考ソース Utage¥Scripts¥GameLib¥File¥FileIOManagerBase DLL化したいソースはスーパーク ラスに記述 スーパークラスだけDLL化する スーパークラス作って、それを継 承する形にする
  • シリアライズと リファクタリング ~ もうMissingはうんざりだ! ~
  • Missingを避けるための、リネームの法則 リネームしていいか? シリアライズが関係ないクラス名やメンバー名 ○ コンポーネントとScriptableObjectのクラス名 ○ コンポーネントとScriptableObjectの [SerializeField]またはpublicなメンバー変数名 × [Seriazible]なクラス名 × [Seriazible]なクラスの[SerializeField]またはpublicなメンバー変数名 × 基本的にシリアライズが絡むなら、コンポーネントとScriptableObjectのクラス名以外は変えてはいけない。 それ以外は、リネームするとシーンやプレハブに設定された値は初期値にリセットされる。 シーンやプレハブに設定された値を残したまま、インスペクター上の表示名だけ変えたい場合は エディタ拡張をするしかない。 シーンやプレハブに設定せずに、AddComponentなどで動的に生成する場合は、リネームしてもOK (シーンやプレハブに設定されたメタデータの問題なので・・・)
  • コンポーネントのクラス名の変えかた まず、Unity上でファイル名を変える (F2でリネームできる) 次にMonodevelopなど、エディタ上でクラス 名を変える MonodevelopやVisualStudioならクラス名を 記述してる場所は全て書き換えてくれる機 能がある。 Monodevelopならクラス名でフォーカスして 右クリックでRefactorが出てくる。
  • シリアライズおわり Unityのシリアライズは奥が深かった・・・・・・
  • コルーチン ~ その裏技 ~
  • コルーチンからコルーチンを呼ぶ void Load() { StartCoroutine( CoLoad ()); } IEnumerator CoLoad() { yield return StartCoroutine(CoLoadSub1()); yield return StartCoroutine(CoLoadSub2()); } IEnumerator CoLoadSub1() { yield return new WWW(url1); } IEnumerator CoLoadSub2() { yield return new WWW(url2); } コルーチン内部で yield return StartCoroutine() とする。 この例では → CoLoadSub1()が終わってから、 CoLoadSub2()が始まる
  • コルーチンをMonovehavior以外で使う裏技 public class MainClass : MonoBehaviour { SubClass sub = new SubClass(); void Load1() { StartCoroutine(sub.CoLoad1()); } void Load2() { StartCoroutine(sub.CoLoad2(this)); } } //コルーチンをもつ通常のクラス public class SubClass { //コルーチン public IEnumerator CoLoadSub1() { yield return new WWW(url1); } //コルーチン内部でStartCoroutineを使うには、MonoBehaviourを渡す public IEnumerator CoLoadSub2(MonoBehaviour parent) { yield return parent.StartCoroutine(CoLoadSub1()); yield return new WWW(url2); } } StartCoroutineはMonovehaviorからしか呼べ ない。 IEnumerator で宣言するコルーチン自体 は通常のC#のクラスでもOK。 その内部でさらにStartCoroutineをした いなら、Monobehaviorを渡してやるとい う手もある。
  • 止め方に注意 • StopAllCoroutinesを呼んでも、 呼び出したコルーチンはそのフ レームの最後までは動くので 注意。 IEnumerator CoLoadSub2() { while(true) { if(isEnd) { StopAllCoroutines(); //ここで止めても } Debug.Log("!"); //ここは呼ばれる yield return 0; } }
  • エディタ拡張 ~ 手抜きのレシピ ~
  • OnValidateが便利 /// <summary> /// インスペクターから値が変更された場合 /// </summary> void OnValidate() { dataTbl.RefreshDictionary(); } リファレンス http://docs-jp.unity3d.com/Documentation/ScriptReference/MonoBehaviour.OnValidate.html インスペクターのエディタ拡張をせずに、 インスペクターで値が変えられたときの 操作を記述できる。 簡単な処理ならこれでOK 参考ソース Utage¥Scripts¥GameLib¥CustomProjectSetting¥Node2DSortData.cs
  • OnValidate +MarkAsChangedパターン 参考ソース Utage¥Scripts¥GameLib¥2D¥2D¥Node2D.cs /// <summary> /// インスペクターから値が変更された場合 /// </summary> protected virtual void OnValidate() { MarkAsChanged(); } /// <summary> /// 毎フレームの最後の更新 /// </summary> protected virtual void LateUpdate() { if ( CachedTransform.parent != lastParent || hasChanged ) {//構造に変化があった Refresh(); } } インスペクターでの変更だけなく、 ・スクリプト内部からのsetなどで変更されうる ・パラメーターが多く、かつ更新による負荷が大きい など、ある程度複雑になる場合は 「変更があった」というフラグのみ設定して、 LateUpdateやUpdateでチェックして一度だけ変更す るという手もある。
  • EditorGUILayout.PropertyFieldを使うと楽 参考ソース Utage¥Editor¥Scripts¥Inspector UtageEditorToolKit.PropertyField(serializedObject, "engine", "Engine"); UtageEditorToolKit.PropertyField(serializedObject, "isAutomaticPlay", "Is Automatic Play"); UtageEditorToolKit.PropertyField(serializedObject, "startScenario", "Start Scenario Label"); public static void PropertyField(SerializedObject serializedObject, string propertyPath, string label, params GUILayoutOption[] options) { SerializedProperty property = serializedObject.FindProperty(propertyPath); if (property == null) { Debug.LogError(propertyPath + " is Not Found"); } else { EditorGUILayout.PropertyField(property, new GUIContent(label), options); } } メンバ変数名 インスペクターに表示する名前 EditorGUILayout.PropertyFieldを使 うと、stringやMonobehavior、 Vector3、enumなどの型によらず 適切なGUIで表示してくれる。 細かい制限をかけない(エディタ 拡張しないでよい)パラメーターに 関してはこれで書いたほうが楽 エディタ拡張をする場合でも、なるべく簡単に書く。
  • Property Drawerを使う リファレンス http://docs-jp.unity3d.com/Documentation/Components/editor-PropertyDrawers.html 参考ソース Utage¥Editor¥Scripts¥Attribute¥EnumFlagsAttributeDrawer.cs EnumFlagsという、フラグを表示する Property Drawerを作成しておく こう書くと [System.Flags] enum DebugOutPut { Log = 0x01, Waiting = 0x02, CommandEnd = 0x04, }; [SerializeField] [EnumFlags] DebugOutPut debugOutPut = 0; Property Drawerを使うと、コンポーネ ントごとにエディタ拡張をしなくても、 特定の表示パターンを指定できる。 Enumとして表示さ れてしまう フラグ操作に適したUIで表示
  • より具体的な プログラム
  • 画面サイズに対応 ~ アスペクト比を変えたり、レターボックスをつけたり ~
  • デバイスごとにバラバラな解像度に対応 デバイスの解像度に応じて描画領域が広げる ように設定もできる レターボックス (設定したアスペクト比を越える部分は黒で塗りつぶす ) NGUIはなぜか縦長タイプに対応 できないが、そこもカバー
  • アスペクト比の範囲を設定可能 参考ソース Utage¥Scripts¥GameLib¥Camera¥CameraManager.cs" アスペクト比を固定のままなら、全部同じ値にする (殆どはこのパターンだと思われる)
  • 実装のコツ 参考ソース Utage¥Scripts¥GameLib¥Camera¥CameraManager.cs" その他のカメラの ViewPortを調整して 描画範囲を操作する カメラを二つ以上使う。 全画面を黒で塗りつぶすだけのClearCameraを描画 順が先に来るように設定
  • 2D ~ Spriteのアレな感じを使いやすく ~
  • Unity4.5(Spriteの改良) や Unity4.6(uGUI)次第では この辺全部無駄になる可能性 あります
  • Unityの2DSpriteの問題点 • 描画順は、ヒエラルキーを無視してしまう たとえば、こんな風に 一画面丸ごとの描画順を変えようとしても
  • Unityの2DSpriteの問題点 描画順は、各スプライトの一つ一つのSortingLayer かOrder In Layerを設定するしかない 親オブジェクトを一つ変えれば、 子の描画順も全部変わる! ・・・とかができない
  • Unityの2DSpriteの問題点 • そのくせ、コリジョンの優先順はZ値なのでヒエラルキーに依存する。 Z値と描画順を合わせないと、 一番手前に描かれてるスプライト にタッチ判定・・・とかができない 手前に描画するものはZ値も手前 にする必要が・・・
  • 描画順に親子関係を反映するようにした 参考ソース Utage¥Scripts¥GameLib¥2D¥2D¥Node2D.cs 親の描画順 1500 親の影響を受けて、 子のグローバルな 描画順は1500 子のローカルな 描画順 0
  • カラーの値も親子関係に影響するように 親を半透明に 一つ変えるだけで、画 面全部が半透明に
  • 描画順制御のデータテーブルを作成 参考ソース Utage¥Scripts¥GameLib¥CustomProjectSetting¥Node2DSortData.cs 描画順は手作業 でも設定できるし、 あらかじめ用意し たデータテーブル からも設定できる 描画順とZ値を プロジェクト内で共通の 値として管理できるよう にした。
  • カスタムプロジェクト設定 ~ 共通設定!一元管理! ~
  • 共通で使う値を、一元管理したい • ゲーム内で共通で使うデータを一元管理したい • 描画順のテーブル • ローカライズ用のキーワード • エディタ上からもいつでもロードして使いたい
  • 共通で使う値を、一元管理したい 参考ソース Utage¥Scripts¥GameLib¥CustomProjectSetting カスタムプロジェクト 設定を作って 起動時に読み込むようにした
  • しかし、どうにもバグというかスッキリしない まだ検討の余地あり • 課題 • カステムプロジェクト設定といってもプロジェクト内に必ず一つと決められない。 ツールとして提供するときは、サンプル設定データが必要になる 多言語対応も考えると、もっとややこしかったり • エディタ中でも参照するためシーンを開いていないときですら、参照したい。 • 色々やり方はあるが、(初回起動時など)どうしても読み込めないときが出てきてしまう。 • この辺のノウハウが欲しい・・・
  • ADVエンジン ~ 「宴」のADVエンジンの構造 ~
  • ADVエンジンの構造と流れ シナリオのエクセルデータを 解析 シナリオの コマンドデータ作成 ゲーム実行中 コマンドデータを実行し ていく おおざっぱな処理の流れ
  • エクセルの読み込み ~ とっても便利!一度使うとやめられない! ~
  • Npoi使えば楽 • 各Cellの文字列を読み 込むのは楽 • あとは文字列を解析す るだけ 参考 http://terasur.blog.fc2.com/blog-entry-511.html 参考ソース Utage¥Assets¥Utage¥Editor¥Scripts¥ExcelParser.cs
  • StringGrid • 宴では、StringGridとい う文字列のグリッド解析 クラスを用意して、エク セルやCSVの文字列を 解析している。 参考 Utage¥Scripts¥GameLib¥StringGrid
  • インポートの設定だけちょっと面倒 • 「拡張子がエクセルファイ ルだったらインポート」と かやってしまうと、他の環 境と衝突するかもしれな い。 参考ソース Editor¥Scripts¥Menu¥ScenarioData¥AdvScenarioDataBuilderWindow.cs プロジェクト設定的なものを 作って、そこで管理している ファイルだけインポートする ようにする。
  • インポートしたらScriptableObjectに 参考ソース Assets¥Utage¥Editor¥Scripts¥Menu¥ScenarioData¥AdvExcelImporter.cs 出力したものは、 ScriptableObjectにするのが 良い。
  • 一度作ってしまえば、すごく便利! • OnPostprocessAllAssetsを使えば、インポートは、ファイルが更新され ると自動で行われる • エクセルを編集→上書き保存→Unityエディタに戻る 自動で更新が行われる。凄く便利!
  • ADV用の コマンドを実行していく ~ キャラを表示したり、テキストを表示したり、音を鳴らしたり ~
  • コマンドの処理の流れ 参考ソース Assets¥Utage¥Scripts¥ADV¥Scenario¥AdvScenarioPlayer.cs 初期化 リソースのロード コマンド実行 待機処理 次のコマンドへ 改ページ待ちなど 毎フレームの処理 オートセーブ
  • 実際にはもう少し複雑 • ジャンプ命令や、既読スキップ、シーン回想への対応など • 特にセーブがらみはわりと面倒。 • 「いつでもセーブできる」とした場合、演出の途中でセーブロードするのが凄 く大変 • シーンの冒頭など「セーブポイント」でしかセーブできない代わりに、「動的な 演出に凝れるモード」もあったほうがいいかも? • 動的な演出についてはこのまとめがわかりやすい http://www.slideshare.net/tunacook/ss-35094307
  • オートセーブ • 基本は改ページの直後の状態をセーブデータとして記録し ておく • スプライトやサウンドなど、「シーンのそのときの状態」をセーブ データとして利用する。 • わりと簡単に作れる。 • コマンドを追加拡張するごとにセーブデータの拡張も必要になる可能性 がある。 • 宴ではこっちを採用 • これとは別に、シナリオのページラベルをキーにして、「そのページ の状態」をシナリオデータから再現できるようにするという手もある。 • こっちは難しいが、柔軟に使えるという利点もある。 • コマンドを拡張しても、セーブデータ自体はいじらないですむ場合もある IOの仕組みは後述
  • コマンドの処理の流れ 初期化 リソースのロード コマンド実行 待機処理 次のコマンドへ 改ページ待ちなど 毎フレームの処理 一番のポイントはこれ オートセーブ
  • リソースのロード • ロード待ちを減らすため、あらかじめ素材をロードする • 今のコマンドだけではなく、この先数ページぶんのコマンドのロードだけ先に やっておく。(いわゆる裏読み・バックグラウンドロード) • これがADVエンジン制作の一番難易度が高いポイント・・・と思ってる • UnityではResouces.Loadが同期しかなかったり、ロード後のリソース の初期化とかは同期だったりするので、実際はどうしてもロードによ る処理落ちができる。
  • リソースのDLとメモリ管理 ~ 大量のリソースをどうさばくか ~
  • 「宴」のAssetファイルマネージャー • 目標 • ダウンロード機能をつける • AssetBundleを使わない(UnityBasicで動くようにする) • なるべく早くロードする • 必要以上のメモリを使わない 参考ソース Utage¥Scripts¥GameLib¥File¥AssetFileManager.cs
  • まとめるとこんな感じ 参考ソース Utage¥Scripts¥GameLib¥File¥AssetFileManager.cs サーバー 必要になったら素材をDL 普通の ロードは 遅い サーバー デバイスストレージに キャッシュ システムメモリに プール シナリオを解析 あらかじめロード ロード済みファイルはすぐに解放せず、次 のロードに備える。 メモリ使用量に応じて自動で解放 次ページ以降の素材をあらかじめロード し、ロード待ちを減らす バックグラウンドでDL 一度DLしたら、ローカルに保存 二回目以降の余計なDLを避ける ロードを 最適化
  • ファイルロードの課題 • Resouces.Loadは? • ダウンロードできない • アプリサイズが増える • リソースの修正にはアプリ自体のアップデートが必要になる • AssetBundleは? • UnityProじゃないとAssetBundleの作成ができない • Unityのアップデートによっては、過去のAssetBundleと互換性が取れない • サウンドのStream再生ができない。(メモリ消費が大きい) • WWWは? • 毎回ダウンロードすることになる 参考ソース Utage¥Scripts¥GameLib¥File¥AssetFileWork.cs
  • 「宴」のAssetファイルマネージャー • 目標 • WWWとResoureces.Load両方でロードできるようにする • ロード&メモリ管理機能をつける • WWWでロードする場合はキャッシュ機能をつける • 一度DLしたファイルはキャッシュファイルとして保存・再利用する • ロードした後のリソースはプール機能をつけて管理する • 一度ロードしたAssetは、メモリ内に残しておいて再利用する • メモリ不足になったら、使っていない古いAssetをアンロードする 参考ソース Utage¥Scripts¥GameLib¥File¥AssetFileWork.cs
  • デバイスキャッシュ 参考ソース Utage¥Scripts¥GameLib¥File¥AssetFileManager.cs Tool>Utage>Open Output Foloder>Cache キャッシュファイルを保存しているフォルダ が開く キャッシュファイルは連番でファイル名がつく。 そのリソースがバージョンアップしたら、新し いファイルを書き込んで、古いファイルは消 す。 WebPlayerではファイルIOができないので、 キャッシュ機能は実装できない
  • デバイスキャッシュ 参考ソース Utage¥Scripts¥GameLib¥File¥AssetFileManager.cs ローカルに書き込む際 に、圧縮・暗号化もか けられる。 符号化したシナリオファイル バイナリエディタなどで覗い てもネタバレはしない
  • • 目的は、LoadFromCacheOrDownload と殆ど同じ • つまり、ファイルのバージョン管理つきのDLとローカルへの保存 • ただし、アセットバンドルではなく.pngや.wavなど汎用的なファイルを扱う • メリット • アセットバンドル化しないので、pngなどはサイズが小さいままDLできる • Unityのバージョンアップに左右されない • →アセットバンドルはUnityのバージョンアップでDLしなおしになる可能性がある • デメリット • 今のところアセットバンドルに未対応 • つまり、3DモデルなどはDLできない • 将来的にはアセットバンドルも併用したいけど検討中。 デバイスキャッシュ 参考ソース Utage¥Scripts¥GameLib¥File¥AssetFileManager.cs
  • アンロードする際には、 可能な限りこのサイズ以 下になるようにする システムメモリにプール 参考ソース Utage¥Scripts¥GameLib¥File¥AssetFileManager.cs ファイルマネージャー こんな感じ DLのタイムアウト時間 エラー時のリトライ回数 同時にロードするファイル の最大数このサイズ(MB)を越えた ら、プールしてるリソース をアンロードする
  • システムメモリにプール 参考ソース Utage¥Scripts¥GameLib¥File¥AssetFileManager.cs ファイルマネージャー をデバッグモードでみる 管理中のファイル情報 を見れる 使用中のファイル ロード中のファイル ロード待機してる ファイル 使用済みのファイル (アンロードせずに プールしてある)
  • システムメモリにプール 参考ソース Utage¥Scripts¥GameLib¥File¥AssetFileManager.cs プールしてあるファイルが 必要になったらすぐ使える (ロードが必要なくなる) 使用し終わっても、アンロー ドせずに、いったん使用済 みとしてプールする
  • メモリ管理→参照管理 • プールの仕組みを実現するには 「どのファイルが使用中なのか?」という 参照管理が必須になる。 参考ソース Utage¥Scripts¥ADV¥Scenario¥Command¥AdvCommand.cs どのobjectから参照してる か、参照がなくなったかを 常に設定する (この場合はthis)
  • この仕組みは テクスチャを大量に使うゲーム (カードゲームとか) にも使えるハズ
  • けっこう作るのは面倒
  • ブラッシュアップしていって そのうち「宴」から独立させて AssetStoreでリリースするかも?
  • セーブロード ~ たぶんUnityではこれが正解!WebPlayerでも使える ~
  • PlayerPrefsは手軽だけど、柔軟には使いづ らい・・・ • 大規模になると管理がつらい • 数が少ないうちはいいが、セーブする要素が増えたりすると困る • サイズ可変の配列だったり、セーブデータを複数もったり・・・ • 遅いとか、レジストリをいじるとか、困る面もある。
  • C#のシリアライズは・・・iOSで使えない • セーブデータはやはりバイナリで作りたいところだが・・・ • C#のクラスを自動でバイナリ化してくれるBinaryFormatter を使う? 参考ソース http://forum.unity3d.com/threads/140606-iOS-Basic-BinaryFormatter public static void SaveToBinaryFile(object obj, string path) { FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write); BinaryFormatter bf = new BinaryFormatter(); //シリアル化して書き込む bf.Serialize(fs, obj); fs.Close(); } iOSでは使えない (使える場合もあるようだが、 非常に限定的なので非対応 と思ったほうがいい)
  • バイナリデータを作成 • BinaryReader& BinaryWriterで手作業でバイナリ化処理を • コードを書くのは面倒だけど、バージョンアップ対応など細かい融通は利く 参考ソース Utage¥Scripts¥ADV¥Save¥AdvSystemSaveData.cs
  • WebPlayer対応を考慮したセーブデータのIO 参考ソース Utage¥Scripts¥GameLib¥File¥FileIOManagerBase.cs バイナリ化できてしまえば、 ファイルとしてIO(入出力)す ればいい。 WebPlayerなど、ファイルIOに対 応してない場合は、PlayerPref でセーブロードする バイナリ配列をStringに変換し、 ファイルパスをキーにしてIO
  • WebPlayer対応を考慮したファイルIO バイナリ化を駆使すれば、 スクリーンショット(テクスチャ) つきのセーブデータを WebPlayerで保存できる! WebPlayerのPlayerPrefにはサイズ 制限があるのでそこだけ注意
  • その他のプラグラム紹介
  • テキストの禁則処理 参考ソース Utage¥Scripts¥GameLib¥2D¥2D¥TextArea2D.cs そういう場合に適度な改行 をして読みやすくするのを 「禁則処理」という 日本語を表示する場合に 句読点や括弧等が行頭や行末に くると読みづらくなる 禁則処理 こういう日本独自の処理はおそらく Unity公式ではサポートされないと思 われる (日本人が書くしかない) 行頭に 。 かっこ悪い
  • 構文解析 • ADVを作るためには 「構文解析」が色々必要になる • <Color>タグつきのテキスト • “Point+=1”という「文字列」を数式として処理したり • その他いろいろ 詳しくはソースを確認してほしい
  • 「宴」のプログラム構成 ADVに特化した部分 ゲームプログラム 全般に汎用的に使う サンプルコード 主にUI処理を記 述
  • ADV特化部分 主に、テクスチャや サウンドなどの設定 ADVの多言語対応 ADV中の2D描画 ADVのシステム制御 ADVのセーブ処理 ADVのシナリオ制御 ほぼ中心となる処理 UI制御
  • ゲームプログラム汎用部分 2D処理 カメラ・タッチ入力処理 シリアライズ可能な Dictionary 文字列で書かれた数式の解析 ファイルマネージャー関係サウンド処理 (ボリューム調整や、 フェード処理つき) iTween カスタムプロジェクト設定 (一元管理用データ) エクセルやCSVなど、 文字列のグリッドの解析 タグつきテキスト解析 フォント制御 その他の便利処理
  • おしまい Unity用ADV制作ツール「宴」 http://madnesslabo.net/utage/ 2014/05/19 マッドネスラボ代表 時村良平 https://twitter.com/rodostw 「宴」実装時に得られた Unityプログラムのノウハウ