[Unity][VFW] フリーなのに高機能なエディタ拡張

# 始めに

最近仕事でUnityさわり始めたんですが、元々WindowsでC#使ってた身としてはUnity独自の制約に時々戸惑うことも。
その一つがインスペクタ上で表示できないものが意外と多いこと。普通にジェネリック型とか使うと表示できなくて困った。

そんな時対応策を探していて見つけたのが **VFW** (Vexe Framework)。
ジェネリック型だけでなく、**プロパティやメソッド(!)まで簡単にインスペクター上に表示できる**カスタム属性(CustomAttribute)群を提供してくれます。
https://github.com/vexe/VFW

現在公開されてるバージョン1.3は二年ほど前のものですが、現時点で最新の Unity2017.2 でも問題なく動いてます。

ただ問題は、情報が少ないこと。本家のGitHubサイトでも細かい使い方はほとんど説明されてなくて、サンプルコードやソースコメントで読み解くしか無い。
日本語だと唯一見つけたのはこの記事ぐらい。
http://unitymemonote.blogspot.jp/2015/04/unitycvfw.html

なので、英語はそこまで得意というわけでもないですが（予防線）、上記記事にもあるように、もう少し詳細な紹介を書いてみようと思います。なお、導入や簡単な使用法などについては[上記リンク](http://unitymemonote.blogspot.jp/2015/04/unitycvfw.html)を観てください。

# VFW（README.md の大雑把な翻訳）

## VFWとは
* VFWはエディタの拡張機能であり、Unityで提供されているものよりもはるかに進んだエディタ拡張機能を提供します。より優れた描画API、より高速なエディタ用GUIレイアウトシステム、多数のカスタム属性/描画機能、ヘルパータイプなどなど。

## 使い方
* githubから.Zipをダウンロードして解凍してください。Plugins、VFW Examples (サンプル)、およびVFW Deprecated (非推奨)の3つのフォルダが表示されます。必要なのはPluginsフォルダだけです。
* MonoBehaviourの代わりにBaseBehaviourを継承するか、ScriptableObjectの代わりにBaseScriptableObjectを継承し、usingステートメントを追加することを忘れないでください。詳細については、パッケージに付属のサンプルを参照してください。

```csharp
using Vexe.Runtime.Types;

public class TestBehaviour : BaseBehaviour
{
    [PerItem, Tags, OnChanged("Log")]
		public string[] enemyTags;
		
		[Inline]
		public GameObject go;
		
		[Display(Seq.LineNumbers | Seq.Filter), PerItem("Whitespace"), Whitespace(Left = 5f)]
    public ItemsLookup[] ComplexArray;
}
```

## 機能概要

### **1- 以下の機能を持つ新しい描画API群**

1. 極めて多くの **任意の** System.Type のためのカスタム描画を書くことができます！
1. 一つカスタム描画を書くだけで、任意の **メンバ**（メソッド、プロパティ、フィールド）に適用可能です。DRY(訳註 Don't Repeat Yourself ?)！
1. APIは（ジェネリックを使って）強く型付けされています。あなたが実装したカスタム描画に強く関連づけします。
1. フィールド/プロパティは同じように扱われます。これは、 'EditorMember' ラッパーによって実現しています。このラッパーは強く型付けされていますが、弱い型のバージョンもあります。もう SerializedProperties 扱わなくていい！
1. シリアライズ可能かどうかにかかわらず、**あらゆる**メンバーを公開することができます！（インスペクタへの表示はシリアライゼーションに依存しません）。
1. フィールド、プロパティ、メソッド、ジェネリック、インターフェイス、抽象オブジェクト、Dictionary、配列、List などすべてが公開できるでしょう。
1. 適切に使えばカスタム属性の組み合わせ利用で効果を発揮します（Unityの 'order'属性のように、複合属性をソートするために使用できる 'id'が取得できます）。
1. あらゆる箇所でカスタム描画を再利用できます！描画クラスは、Unityが扱うUnityEngine.Objectに限らず、System.Objectを対象としています。たとえば、EditorWindow でカスタム描画を簡単に使うことができます。
1. 描画APIは非常に柔軟で、カスタム属性を付与していないメンバーにも適用することができます。```MemberField(someMember, someAttributes);``` のようなもの。
1. コレクションを扱うときは、コレクション自体に属性を適用するか、個々の要素に属性を適用するかを選択できます。
1. GUILayout、GUI、そして独自の高速レイアウトシステム **RabbitGUI**! を使用することができます。
1. 対象フィールドの特定の隣接領域を対象にしたカスタム描画を実装することができます。OnLeftGUI、OnRightGUI、OnBottomGUI、OnUpperGUI（フィールドの左、右、下、上の各辺） - 構成レベル：上級！
1. 項目を表示/非表示にする簡易カスタム属性：[Show], [Hide]
1. カスタム属性が適切なフィールド/プロパティの型に適用されていることを確認するために型チェック/validationを書かく必要はなく、描画ロジックを書くことに集中できます。例えば、GameObjectフィールドにPopup属性を適用しても無視されます。

### **2-** <欠番>
### **3- ヘルパーの種類**

1. SelectionMemorizer：オブジェクト選択を記憶します。Ctrl.Shift.- で戻る、Ctrl.Shift.+ で進む。
2. BetterUndo：コマンドパターンを使用して実装された単純なアンドゥシステムで、実行する内容と実行を元に戻す方法を直接制御できます
3. RabbitGUI：Unityの GUILayout / EditorGUILayout よりもはるかに高速でクリーンなGUIレイアウトシステムで、同等のAPIといくつかの追加機能があります。

### **4- 大量の property 属性**

1. **制約**：Min, Max, Regex, IP, NumericClamp, StringClamp
	*最小、最大、正規表現、IP、数値範囲、文字数*
1. **修飾**：Comment, WhiteSpace
	*コメント、余白*
1. **Enum**：EnumMask（動くやつ！ Bunny83作）, SelectEnum（選択ウィンドウ内でを右クリックすると数値を表示することができます - 大きな値を持つenumに便利です）
	*ビットフラグenum複数選択、単一enum選択*
1. **ポップアップ**：AnimVar（アタッチしたAnimatorコンポーネントの利用可能なすべての変数を表示）、Popup（よくあるポップアップ-実行時に値を生成するメソッド名を取ることができる）、Tags（使用可能なすべてのタグを表示）、InputAxis（利用可能なすべての入力軸(Horizontalとか)を表示）
1. **ランダム**：Rand（randomizeボタンを押すとfloat / intにランダム値を設定）
1. **Vector**：BetterVector（ベクタ値のゼロ化、正規化
 Normalize、コピー/ペースト）
1. **その他**：Draggable（フィールド自体をドロップ/ドラッグできます）、Path（文字列フィールドにオブジェクトをドロップして文字列値を取得することができます-フォルダ/ファイルのパスを手入力しなくとも、そのオブジェクトをドラッグアンドドロップするだけでパスを設定でき、GameObjectでも動作します）、OnChanged（作者のお気に入り！プロパティ/フィールドの値が変更された時に呼ばれるコールバックメソッドを指定できます！）、Assignable（指定したソースオブジェクトのフィールド/プロパティを割り当てることができます）
1. **選択**：SelectEnum、SelectScene：オブジェクト（子、親など）、列挙型、シーン をその順番で選択するためのウィンドウを提供します。
1. **フィルタ**：FilterEnum、FilterTags：列挙型やタグをフィルタリングして素早く選択できる小さなテキストフィールドを提供します
1. **カテゴリ**：DefineCategory：メンバーカテゴリを定義し、カスタムルールとフィルタに従って行動メンバーを分類できます。Category：メンバーに注釈を付けて、特定のカテゴリにそれを含める。IgnoreCategories：以前に定義されたカテゴリを無視（非表示）します。
1. **コレクション**：Seq：シーケンス（配列/リスト）の見た目をカスタマイズできます。PerItem：配列/リストの各要素に属性を適用することを示します。PerKey / PerValue：Dictionaryの各キー/値にカスタム属性を適用することを示します。

## スクリーンショット

- **UnityTypes**
	- 訳註：Unity独自型の専用描画もできる
![](http://i.imgur.com/O5klA26.png)

- **ShowType**
	- 訳註：Type型フィールド用に選択ポップアップに出すベース型を指定できる
	- straregyTypeが変わった時、新しい値を引数にしてSetStrategyがコールバックされる
	- インスタンスを生成するときは Activator.CreateInstance の代わりに、より高速な FasterFlect delegate API であり、強く型付けできる Instance<T> を使おう
![](http://i.imgur.com/WlLhnXn.png)

- **Sequences**
	- 訳註：配列やList型を表示するGUI
		- **※現在SeqOptionsはDisplayという名前になってる模様**
	- Comment:「やあ、僕はreadonlyな配列。編集できないよ:smiley:」
	- もし GuiBox が不要なら単に [Readonly] だけでいい
	- この例では属性に`id`があるのを忘れないで、Whitespace=0, Comment=1 でWhitespaceがCommentより先に描画されます
	- Comment:「私は下の方に居る」
	- SeqOptions(SeqOpt.Advanced) と同じ
	- PerItem は配列自体ではなく各要素に属性を適用することを示します
	- ここでは PerItem を使ってないことに注意。つまり属性はリストの要素ではなくリスト自体に適用されることになります。ただし BetterVector はVector2/3描画専用なので、この場合は無視されてUnity標準の方法で描画されます
![](http://i.imgur.com/vtPe4SN.png)

- **SelectEnum**
	- 訳註：Enum選択ポップアップ
![](http://i.imgur.com/srCJepM.png)

- **Popups**
	- 訳註：選択子を指定して選択ポップアップを表示
	- GetFactorsメソッドから挿入される - 戻り値はstring[]型であること（アクセス修飾子ははなんでもよい）
	- PerItem は配列自体ではなく、各要素に適用されることを示す。ここでは　Tagsも　OnChangedも各要素ごとに適用され、要素のどれが変更されても、新しい値を引数にしてLogFactorがコールバックされます
![](http://i.imgur.com/HNeuUSB.png)

- **InputAxis**
	- 訳註：利用可能な`Axis`の選択ポップアップ
![](http://i.imgur.com/UN7qf2d.png)

- **ShowMethod**
	- 訳註：メソッドを晒す
![](http://i.imgur.com/O5n2gCv.png)

- **Inline**
	- 訳註：同じウィンドウ内にあるGameObjectの詳細を埋め込み表示
	- これはよく使う組み合わせ。必要なコンポーネント（ここではGameObject）がなければ付けて、インライン表示し、GameObjectから隠す（訳註：？）
	- 注意：これらは複合（適用可能な）属性であり、RequiredFromThisがInlineより先に描画されるように描画順序を指定してます。こうしないと inline はオブジェクトが存在しないときに描画しません。
![](http://i.imgur.com/vHZORHn.png)

- **AnimVar**
	- 訳註：Animatorの変数を晒す（勉強不足でいまいち解ってない；；）
	- このメソッドはこのままでは、メソッドを指定することなく、このgameObjectからAnimatorを取得するので、無意味です。実際は他のところからAnimatorを取得していると仮定してください...
![](http://i.imgur.com/q0lINLZ.png)

- **Filters**
	- 訳註：右のテキストボックスで選択肢が絞り込みできる
![](http://i.imgur.com/J0F5Msb.png)

- **Attributes in EditorWindow**
	- 訳註：EditorWindowでも各種カスタム属性が使える

![](http://i.imgur.com/2b8YOJu.png)

- **Assignable**
	- 訳註：別のオブジェクトから値を参照設定できる
![](http://i.imgur.com/MzIPxVD.png)

## [旧]チュートリアル/動画（ビデオの説明を読んで内容をご覧ください）

1. [紹介/ APIの概要](https://www.youtube.com/watch?v=9AnTNGKjAK0)
1. [属性1](https://www.youtube.com/watch?v=jsNXBY2VJ6g)
1. [属性2](https://www.youtube.com/watch?v=y3XJKnOCrcU)
1. [メンバーカテゴリー](https://www.youtube.com/watch?v=c9PDvJOZXGU)
1. [その他の機能、およびいくつかの軽微な問題](https://www.youtube.com/watch?v=rlGqavcPNuE)

## 既知の問題点

1. アンドゥは完全に100％完璧ではありません（ただし、1.3では改善されています）
1. まだ十分にドキュメント化されていません
1. マルチオブジェクト編集のサポートはまだできません

## 謝辞

1. これは一個人の仕事であることを理解してください。私はすべてがうまくいくように努力しますが、すべてを完璧にすることはできません。私は自分のコードを分離して単体テストを書いて、すべてが正常に動作するように努めてますが、バグが1つや2つ簡単に入り込む可能性があります。バグのないプログラムをお望みなら、私は書きません。
1. 残念ながら、このフレームワークを使用することで厄介なバグや不都合が生じる場合があることを、あらかじめご了承ください。
1. ヘルプ、改善/提案、バグレポート：あなたは[ここ](https://github.com/vexe/VFW/issues)に投稿することができます。askvexe@gmail.comまでPMかメールを送ってください。早急に返信し、できる限り力になります。
1. 私は常に開発のためにUnityの最新バージョンを使用します。問題を報告する前に、最新のバージョンを使用していることを確認してください。それでもまだ問題が起きる場合は報告してください。

## よくある質問

1. なぜあなたはカスタムシリアル化機能を非推奨にしましたか？
	- [ポストNo.441](http://forum.unity3d.com/threads/open-source-vfw-134-drawers-save-system-serialize-interfaces-generics-autoprops-delegates.266165/page-9#post-2478086)を読んでください。

1. ライセンスは？
	- [MIT](http://choosealicense.com/licenses/mit/)。
1. これにはUnity Proが必要ですか？
	- いいえ
1. これはAOTプラットフォームをサポートしていますか？
	- はい。iOSで動作することが確認されています。Androidについては自分の手で確認はしていませんが、動作するはずです。
1. なぜ無料なの？
	1. 誰でも自分の個人的なプロジェクトで使って恩恵を受けられる基板となる「標準」のフレームワークを私が構築できると信じているからです。現状では、ほぼ全てのアセット開発者は、同じ問題に対して何度も何度も「自身の解決策を再発見している」ということです。これは、異なるパブリッシャーのアセットが、バックボーンが異なるため、上手く共存することを困難にします。たとえば、Node Canvasで使用したいプロパティー・ドロワーアセットを購入したとしても、上手くいきません。なぜなら、NCとドロワーアッセットがコードに対して異なる基盤を使用していて、両者に互換性がないからです。
	1. このような革新的なものは、私たちは無料で利用できるべきです out-of-the-box...私にuRanting(わめき散らす?)させないでください。
	1. オープンソースは美しい。誰もがソフトウェアの改善、バグ修正などに貢献することができます。
1. 描画システムはどのように機能しますか？カスタム描画を作成するにはどうすればいいですか？
	0. カスタム描画には、ObjectDrawer、AttributeDrawer <T、A>、およびCompositeDrawer <T、A>の3種類があります。
	1. ObjectDrawersは、インスペクタでメンバーがどのように表示されるかを定義します。どこにあっても特定のタイプにドロワーが適用されるようにするには、ObjectDrawerを使用します。例えばIntDrawer、StringDrawer、ListDrawer、ArrayDrawerはそれぞれ、int、string、List、T[]に対応するドロワーとして適用されます。ObjectDrawerでは、OnGUIをオーバーライドして、すべてのGUIを処理します。ドロワーが対象としているメンバーの値を変更するために使用できる、厳密に型付けされた 'memberValue'が得られます。あらゆる描画を行うのに、 'gui'プロパティを使用します。同様に、対象に付与されている全てのカスタム属性を参照するために強く型付けされた属性 'attribute'が使えます。
	1. AttributeDrawer<T,A> で、AはDrawnAttributeです。ObjectDrawersと同様に、AttributeDrawersはメンバーの見た目を定義します。メンバーに特定のカスタム属性が付与されてるときにのみドロワーを使用する場合は、AttributeDrawer<T,A> を使用します。例えば、PopupDrawer、AnimVarDrawer、およびShowTypeDrawerがあります。PoupDrawerは、PopupAttributeが付与されているstring型にのみ適用されます。したがって、PopupDrawerはAttributeDrawer <string,PopupAttribute>です。ここでも、OnGUIとmemberValueプロパティが使えます。複数のAttributeDrawersを付与するのは意味がありません。例えば、AnimVarとPopupの両方をstring型に付与すると、競合して、どちらか1つだけが使用されるためです。
	1. CompsoiteDrawer<T,A> で、AはCompsoiteAttributeです。CompositeDrawersは少し他と異なります。メンバーの外観を定義するのに使用されるのではなく、メンバーの周りに「装飾」されます。OnGUIを取得するのではなく、OnLeftGUI、OnRightGUI、OnUpperGUI、OnLowerGUI、およびOnMemberDrawn(Rect) コールバックを取得します。最後のコールバックは、メンバが描画されるときに呼び出され、メンバが描画される矩形が渡されます。残りのコールバックは、OnUpperGUI、OnLeftGUI、OnRightGUI、OnBottomGUIの順で呼び出されます。それらのコールバックをオーバーライドして、メンバーの特定の領域を飾ることができます。例としてWhiteSpaceDrawerを参照してください。これらのコールバックを使用して、メンバーの左/右/上/下にスペースを追加します。OnMemberDrawnコールバックの使用例はPathDrawerを参照してください。AttributeDrawerと同様に、 'memberValue'、'attribute'、 'gui'が利用できます。CompositeDrawerの例は、CommentDrawer、WhiteSpaceDrawer、InlineDrawer、PathDrawerなどです。AttributeDrawerとは異なり、メンバーに複数のCompositeDrawerを適用できます。それらのドロワーが適用/描画される順序は、CompositeAttributeの 'id'プロパティを使って定義できます。小さい方から順です。
	1. 処理できないメンバーに属性を適用すると、エラーが出力され、そのメンバーの適切なドロワーに置き換えられます。たとえば、PopupをTransformに適用すると、これは意味をなさないので、PopupはTrasnformを処理できず、メンバはUnityObjectDrawerを使用して描画されるというエラーが表示されます。
	1. カスタム描画を作成したら、TypeDrawerMapperを使用してドロワーを登録/マップする必要があります。どのように処理されているかは、RegisterCustomDrawerExample.csを参照してください。
1. 私のクラスのメンバーはインスペクタでどのような順番で並びますか？
	- 順序は、フィールド、プロパティ、メソッドです。フィールドは宣言の順番でソートされ、プロパティとメソッドでも同じです。例：f1、f2、p1、p2、m1のようにf1、p1、f2、m1、p2を定義したクラスがある場合（f：field、p：property、m：methodの順）。[DisplayOrder(#)]を使用して、インスペクタでのメンバーの表示順を明示的に指定することができます。メンバーを宣言順に表示する方法は非常にトリッキーであることに注意してください（[こちらを参照](https://www.assetstore.unity3d.com/en/#!/content/31442)）（もっと簡単な方法を知っていれば、私に知らせてください）。

*訳註：ここから下は非推奨となったカスタムシリアライゼーションシステムについてですので、BaseBehaviour, BaseScriptableObject を使う限り関係ありません。Unityのシリアライズシステムに準拠します。*

1. シリアライゼーションシステムで何かサポート**してない**ものはありますか？
	- シリアライゼーションシステムは、Unityがサポートしていない型をシリアライズするためのものです。Unityオブジェクトのシリアライゼーションは、Unity自身によって処理されます。シリアライザがUnityEngine.Objectの参照を渡すと、シリアライザはUnityオブジェクトのシリアル化されたリストに格納し、そのストレージがリスト内でどこで行われたかのインデックスをシリアライズします（基本的にハックです）。つまり、UnityオブジェクトをStreamに適切にシリアル化するためには、コンバーター/サロゲートを作成する必要があります（私が間もなく公開するFastSaveを楽しみにしていてください。）
1. 正確なシリアル化ルールは何ですか？
	1. publicフィールドはデフォルトでシリアル化されます。
	1. 少なくともpublicなゲッターまたはセッターを含む自動プロパティは、デフォルトでシリアル化されます
	1. 副作用を持つプロパティ（ボディ{}を持つgetter / setter）は決してシリアル化されません
	1. [SerializeField]または[Serialize]でマークされた非公開フィールドまたは自動プロパティはすべてシリアル化されます
	1. [DontSerialize]または[NonSerialized]でマークされている場合、パブリックフィールド/自動プロパティはシリアル化されません。 [DontSerialize]は、[NonSerialized]と違ってプロパティにも使用できるので、お勧めです。
	1. 以上すべてが読み取り専用フィールドに適用されます
	1. 静的フィールド/自動プロパティの場合、BetterBehaviourにあるすべてのstaticなフィールドおよび自動プロパティーには以上の全てが適用されます。使用されるシリアライザによっては、System.Objectsでシリアル化されないことがあります。たとえば、FullSerializerはstaticフィールド＆自動プロパティのシリアル化をサポートしていませんが、FastSerializerは機能します（間もなくリリース）
	1. 型に[Serializable]を付ける必要はありません。実のところ、型に[Serializable]を付けないことをお勧めします（下記の注意書き(次の項目)を参照してください）
1. 結局のところ[Serialize]と[SerializeField]のどちらを使うべきですか？また、その理由は何ですか？
	- 短い答え：常に[Serialize]を使用してください。
	- 長い答え：BetterBehaviourはMonoBehaviourなので、Unityのすべてのシリアライゼーションルールがそのまま適用されます。つまり、パブリックフィールド（serializableな型を持つ）はデフォルトでシリアル化され、非パブリックフィールド（serializable な型を持つ）は[SerializeField]で注釈が付けられたときにのみシリアライズされます。つまり、[SerializeField]でマークされた公開フィールドまたは非公開フィールドを持っている場合は、2回シリアライズされます。1つはUnityによって、もう1つはVfwによって。だから、（できるなら）Vfwだけがシリアライズようにするようにして、Unityのシリアライゼーションシステムとの競合を避けるほうが良いです。非パブリックフィールドの対処方法は簡単です。[SerializeField]を使わず、代わりに[Serialize]を使います。パブリックフィールドの場合は、代わりにpublicオートプロパティを使用するか、readonlyにします（インスペクターに表示したいが、コードからは変更しない値には最適で、作者のお勧めです）。**ただし、VFWのシリアライズだけにした場合は、あらゆるフィールド初期化子の値が無視されます！** したがって、Behaviour上で「Reset」をクリックすると、メンバーは初期化子で初期化された値に戻されるのではなく、default<T>の値に戻ります。回避策としてDefaultAttributeを使うことができます。
1. シリアル化をカスタマイズするコンバーター/サロゲートを作成する方法/特定の種類のサポートを追加する方法
	- FullSerializerについては、FSのレポを 参照してください。https://github.com/jacobdufault/fullserializer# あるいは```Vexe/Runtime/Serialization/Serializers/FullSerializer```のMethodInfoConverter.csも参照してください。コンバータを作成したら、必ずFullSerializerBackend.csに追加して初期化されるようにしてください。
1. BetterBehaviourのシリアライズ状態を表示するには？
	- スクリプトヘッダーの折り畳みを展開するだけで、ランタイムのシリアル化データが表示されます
1. BetterBehaviourをファイルにシリアライズするにはどうしたらいいですか？
	- 短い答え：直接それを行うことはサポートしてないので、しないでください。
	- 長い答え：これまで説明したように、Unityオブジェクトのシリアライズは、serializable なリストに（Unityによって）オブジェクトを格納し、格納場所のインデックスをシリアライズすることによって行われます。Stream（File など）に保存するとうまくいきません。なぜなら、deserialize が機能するためには、受信側で格納時と同じリストが必要だからです。これはあまり実用的ではありません。このため、シリアライゼーションシステムは主に、アセンブリのリロード間でデータを永続化するためのものです。つまり、Behaviourのシリアライゼーションデータをファイルに書き込もうとすると、DictionalyやリストなどのUnityObjectではないメンバでうまく動作しますが、UnityObjectではうまく動作しません。

# 余談
以上の翻訳は一旦Google先生にお願いして、適当に更正しました。しかし最近は翻訳精度高いですね。ほぼそのままで通じる。時々括弧の前後で文が行方不明になってるけどｗ

カスタムシリアライザーが非推奨の理由もざっくり読んだけど、「シリアライゼーションシステムがクソ、OOPなんて辞めちゃえ」みたいな不満をぶちまけてるだけであんまり具体的な問題点が分からなかった。
非推奨だけど、かなり優れた機能なので追々別記事で紹介してみたい。

件の投稿を読むと、作者はなかなかアグレッシブな人な気がする。Unity関連の質問への返信で結構あちこちVFW売り込みしてるし。
一方で謝辞にわざわざあんなこと書いてるのは、よほど要望やバグのクレームが多かったのだろうかと勘繰ってしまいますね。自分は要望出すだけでなくコントリビューターになれたらと思うけど、Unity一年生でまだそんな実力はないので、とりあえずVFWの布教活動にでもなればと書いてみました。