はじめに
こんにちは。
株式会社Synamonでエンジニアをしております、渡辺(@mochi_neko_7)と申します。
本記事のテーマは、UnityのEditor拡張のひとつである、「Reorderable List」になります。
通常の配列やリストのUnityEditor上でのGUIは、以下のように要素の順番が固定されています。
一方でReorderableListでは、GUIでリストの要素の順番を自由に入れ替えられるのが大きな特徴です。
Unityには以前からある機能で、とても便利なものなのですが、あまり知られていないのではないでしょうか?
本記事では、
- ReorderableList
- Editor拡張による開発環境の向上
を知ってもらうことを目的としています。
概要は以下のとおりです。
- ReorderableListとは
- Simple Reorderable List
- Editor拡張の知見
- やり残したこと
初心者向けの説明から、具体的な使い方の説明まで行います。
ソースコードおよびUnityPackageは以下のGitHubで公開しています。
かいつまんだ説明はこちらのイベントのLTでも発表させていただきました。
LTのスライド資料も併せて参考にしていただければと思います。
本記事ではこの内容に加えて、
- もう少し細かな説明
- 実際の使い方
- カスタマイズ例
- やり残したこと
などを補足したいと思います。
1、ReorderableListとは
ReorderableListは、UnityのEditor拡張の一つです。
あまりご存じない方のために、Editor拡張とはなにか、ということから、ReorderableListとはなにか、ということまで簡単に紹介したいと思います。
Editor拡張とは
Unityのプログラムは、大きく分けると次の2つに分類することができます。
- Unity Engine = Unityで作成したアプリケーションおよびEditor上で動作するもの
- Unity Editor = UnityのEditor上のみで動作するもの
今回お話するのは、後者のUnityEditorの話になります。
Unityでアプリケーションを作る際には、GUIによるEditor上での操作が基本になります。
このGUIは利用者がカスタマイズすることができ、Editor拡張と呼ばれています。
たとえば、
- コンポーネントのInspectorでの表示を調整
- 専用ウィンドウを自作
- Projectの表示を改造
などといったこともできます。
配列やリストのEditor表示
配列やリストをInspectorに表示して、Editor上で編集したい場合、表示したいフィールドに[UnityEngine.SerializeField]
のAttributeを付けることで可能です。
特徴は以下の3つです。
- 要素数をSizeで指定する
- 要素間の挿入・削除はできる(右クリック)
- 順序の入れ替えはできない
この最後が厄介で、順番が大事になるリストの使い方をした時には、パラメータをそれぞれセットしなおさなければなりません。
パラメータの中身を保持したまま要素の順番を入れ替えたいような時に、もどかしい思いをしたことはありませんか?
そこで役に立つのがReorderable Listです。
Reorderable List
実はUntiyEditorInternal
のNameSpaceの中にReorderable Listなるものがあります。
「Reorderable」の意味は、
- re : 繰り返し
- order : 順番を変える
- able : 可能
で、つまり「入れ替え可能な」ということです。
リストの要素の操作が直感的にできる、結構便利な機能です。
全部のList表示が最初からこのReorderableListでもいいのでは、と思うほどです。
ただし、InternalのNameSpace内ですので、正式にリリースされているものではないことに注意する必要はあります。*1
このAPIは便利な反面、少し扱いづらいものとなっています。
UnityEditor.EditorGUILayout
などの他のEditor便利クラスたちとは違って、各要素の描画方法や高さを自分で指定する必要があります。*2
毎回個別に描画方法を指定する方式では、使いまわしが利きません。
それに描画方法を書かせるのは、GUI初心者にはややハードルが高いですよね。
それではせっかく便利な機能も使われなくなってしまいます。
そこで、こんなReorderableListが欲しいと思うわけです。
- できるだけ扱いやすいものがいい
- 汎用的に使えるものがいい
- 機能はシンプルなものでいい
シンプルで使いやすいものが欲しかったので、自分で作成することにしました。
それが「Simple Reorderable List」です。
本記事の目的は、あくまでReorderableListの布教です。
なので、もし他にいいものがあれば「Simple Reorderable List」を使わなくても構いません。
AssetStoreやGitHubにいつくか見つかると思います。
シンプルな機能があれば十分、むしろ余計なものはいらない、というのであればぜひこちらを使ってもらえればと思います。
2、Simple Reorderable List
自分の作成したReorderableListの使い方を紹介します。
以下の順番で説明していきたいと思います。
- Editor拡張の準備
- SimpleReorderableListの使い方
- 特徴
- オプションの説明
- カスタマイズ例
- 思想
Editor拡張の準備
Editor拡張の準備をしましょう。
ReorderableListを使用したいコンポーネントをSinglePropertySample
とします。
このコンポーネントのInspectorでの表示を自分で調整したい場合には、Editor拡張用のクラスを作成する必要があります。
ここではSinglePropertySampleEditor
とし、これをEditorという名前のフォルダに置きます。*3
SinglePropertySample
には、ここではとりあえず文字列のリストtexts
を持たせておきましょう。
このtexts
をEditor上で編集できるようにするために、[UnityEngine.SerializeField]
を付けるのを忘れないでください。
次にEditorのスクリプトの準備をしましょう。
SinglePropertySampleEditor
を作る上でのお約束は、
UnityEditor.Editor
クラスを継承するUnityEditor.CustomEditorAttribute
をつける- AttributeのConstructorに、調整したいコンポーネント(
SinglePropertySample
)のTypeを入れる UnityEditor.Editor.OnInspectorGUI()
をオーバーライドして、描画を上書きする
になります。
ひとまずこれでEditor拡張の基本的な準備は完了です。
Editor拡張自体に関する入門的な話を知りたい方は、以下のサイトなどをご覧いただくのがおすすめです。
SimpleReorderableListの使い方
「Simple Reorderable List」を導入しましょう。
のReleaseにある最新版の.unitypackageをダウンロードし、UnityEditorにDrag&Dropしてインポートします。
texts
をReorderableList化する手順は以下になります。
Mochineko.ReorderableList.ReorderableListLayouter
のフィールドを用意するOnEnable()
で、texts
のSerializedProperty
を取得し、1. のフィールドを初期化するOnInspectorGUI()
で、1. のフィールドのLayout()
を呼び出すOnInspectorGUI()
の最後に、UnityEditor.Editor.serializedObject.ApplyModifiedProperties()
を呼び出す
これにより、texts
のEditor上での表示が次のような感じになります。
特徴
SimpleReorderableListの機能の特徴は、
- 要素の入れ替え、追加・削除をGUIで直感的に操作できる
- 複数プロパティを持つ要素や、自作したクラスにもそのまま使える
- 要素の背景に交互にコントラストを付けて見やすく
- カスタマイズでDropDownで要素を追加することもできる
になります。
それぞれGIF画像で簡単に紹介していきます。
SimpleReorderableListには、以降で挙げる例をはじめとして、簡単なサンプルを用意しています。
よろしければ実装のコードも併せてご参照ください。
要素をドラッグして、順番を入れ替えることができます。
右下の「+・-」ボタンで要素の追加・削除をすることができます。
複数プロパティを持つ場合でも、フォールドアウト付きで表示できます。
上の例が既にそうですが、自作したクラスでも表示できます。
内部にリストなどを含んでいても問題ありません。
あらかじめ用意した文字列を使って、ドロップダウンで要素を追加することもできます。
オプション
SimpleReorderableListには大きく分けて2つのオプションを用意しています。*4
NativeFunctionOptions
ReadyMadeDrawerOptions
デフォルトのままで問題ない場合には、何も指定せずに使用できます。
少しカスタマイズしたい場合などには変更することができます。
ここではこれらのオプションの説明をしたいと思います。
NativeFunctionOptions
本来のReorderableListにあるオプションです。
Draggable
= 要素をドラッグして順番を入れ替えられるかDisplayHeader
= ヘッダー部分に文字を表示するかDisplayAddButton
= 要素を追加するボタン(+)を表示するかDisplayRemoveButton
= 要素を削除するボタン(-)を表示するか
デフォルトではすべてtrue
にしています。
必要のない機能がある場合には、Constructorでオプションを放り込んで指定してください。
ReadyMadeDrawerOptions
自分が作成した描画方法を使用するかのオプションです。
UseReadyMadeHeader
= リストのプロパティ名をヘッダーに表示しますUseReadyMadeElement
= 自動的に要素の描画を行いますUseReadyMadeBackground
= 見やすいように要素の背景色を交互に変えます
これもデフォルトではすべてtrueにしています。
これも必要のない機能がある場合には、Constructorでオプションを放り込んで指定してください。
もちろん、2種類のオプションをどちらも変更することも可能です。
カスタマイズ例
これまでにお見せしたのは、標準的な使い方をした場合のものです。
ここでは、オプションによって少し応用を利かせたユースケースを紹介します。
要素数の固定
通常のリストなどでは、Editor上のSizeのパラメータで要素数を変えることができました。
逆に言えば、いくらスクリプトで要素数を指定しても、Editorで数を変えることができてしまいます。
ところが、ReorderableListでは要素数は右下のボタン(+、-)でのみ行い、これらは非表示にすることができます。
そのため、以下のようにして要素数を変更できないようにすることも可能です。
Editor側で要素数を指定して初期化します。
ReorderableListの初期化時に、要素の追加と削除のボタンを非表示にするようオプションを指定します。
すると、このように要素数を変更できないリストが出来上がります。
もし古いシリアライズデータが残っていて数が変な場合には、コンポーネント右上の歯車アイコンをクリックし、Resetを実行してください。*5
ドロップダウン
決められた要素しか追加したくない場合、追加する要素をあらかじめ用意した一覧から選ばせる方法があります。
ReorderableListの初期化時に、AddDrawDropDownCallback(...)を使用します。
第一引数には、選ばせたい候補strin[]を文字列で指定します。
第二引数には、選ばれた文字列に対するコールバックSystem.Action<string>を指定します。
すると、このようにドロップダウンで選択して要素を追加することができます。
このように、Editor拡張を少し工夫することで、Editorの使いやすさとある程度の制約を両立することもできます。
思想
まとめとして、簡単に設計思想的なものをお話しします。
- シンプルさ
- 汎用性
- 見やすさ
自作した主な動機は、Unity本来のReorderableListが扱いにくいことでした。
ですので、できるだけシンプルに使える、自分が使いたいと思えるものを作るよう心掛けました。
せっかく作ったものでも、汎用性がなければいろんなところで使うことができません。
配列やリストは様々な型の要素を扱いますので、ジェネリックな対応は必須です。
今回作ったものでは、UnityのSerializedProperty
の仕組みをそのまま流用する形で、これを実現しています。
機能的な部分だけではなく、GUI上での見やすさもこだわっています。
Projectウィンドウなど、要素の数が増えるととても見づらく、神経質になる必要が出てきます。
複数の要素を扱う場合を想定しているので、要素の区別がしやすいよう、かつ邪魔にならないよう調整しています。
3、Editor拡張の知見
Serialized Property
UnityEditor.SerializedProperty
には、
CountInProperty(...)
CountRemaingin()
という一見便利そうなメソッドが用意されていますが、少し注意が必要です。
これらのメソッドを叩くと、SerializedProperty
のIteratorが1つ進みます。
つまり、メソッド実行後のSerializedProperty
は1つ先のPropertyを指すものになります。
Debug.Log()
でSerializedProperty
の内部の状態を見ながら動作確認...なんてやっていると変な挙動をするので注意してください。
公式のスクリプトリファレンスにも(英語で)書かれています。
これを回避するためには、一度SerializedProperty
のコピーをSerializedProperty.Copy()
で取ればよいです。
コピーのほうを操作してもコピー元には影響しなくなります。
ユーザースキンの考慮
UnityのPro以上のライセンスでは、EditorSkin(カラーテーマ)をPreferencesで変更することができます。*6
それぞれの違いは以下です。
- Personal:無料版でも利用できる、白っぽいテーマ
- Professional:Pro以上で利用できる、黒っぽいテーマ
Personalではこんな感じ。
Professionalではこんな感じ。
どちらを利用するのかは好みの問題です。
しかし、Editor拡張で描画する際に自分で色を指定する際には、スキンで見かけが変わることに注意しなければなりません。
Professionalで見やすく調整しても、Personalでは見にくい、なんてこともあります。
EditorGUIUtility.isProSkin
でユーザーの使用しているスキンがどちらか判定できますので、必要に応じて色を使いわけるなどしましょう。*7
あまり多くはないかもしれませんが、スキンの違いで微妙にレイアウトが変わる場合もあります。*8
描画を自分で行う際には、念のためスキンを変えても問題ないか確認するのがいいと思います。
4、やり残したこと
今回、理由があって対応できなかったことを最後に述べておきます。
PropertyDrawer対応
今回作った「SimpleReorderableList」は、面倒な設定をすることがなく汎用的に使えるものにはなっています。
ですが、これを使用する際には、どうしてもEditor拡張を書く必要があります。*9
フィールドに特定のAttributeを付けるだけで使うことができ、Editor拡張を書かなくてもいいのが理想です。
これを実現できそうなEditor拡張の一つとして、PropertyDrawerがあります。
これらの詳細は先ほども挙げたこちら
を参照してください。
実際にこれを試してみたのですが、配列やリストに対してのPropertyDrawerは各要素にしか適用されず、リストそのものの描画を上書きすることはできないようです。
そのため、PropertyDrawerによる対応は泣く泣く諦めました...*10
IList対応
ReorderableListの初期化の方法には、実は大きく分けて2つあります。
SerializedProperty
IList
各要素の描画の指定と高さ計算の汎用性から、前者のSerializedPropertyによるもののみの実装をしました。
IListに対応する際には、これらの汎用的なものを自作するか、使用する部分で都度作成するかになります。
これはかなり重労働になるため、今回は対応を見送りました。
まとめ
今回もやや長くなってしまいましたが、要約すると、
- ReorderableList便利ですよ
- 「Simple Reorderable List」よかったら使ってください
- いくつかEditor拡張の知見を共有します
という話でした。
初心者向けの説明から、具体的な使い方の手順まで紹介しました。
ソースコードもGitHubで公開していますので、独自でReorderableListを実装されるかたなどに参考になれば幸いです。
おわりに
Editor拡張は、基本的には実際に動作するアプリケーションに何かするわけではないので、割と後回しにされがちなのではないでしょうか?
ですが実際に触ってみると、いろいろなことができて結構楽しいですし、開発がはかどる便利機能を作ることができたりします。
ReorderableListはそんな便利な機能の一つに過ぎません。
どんなものを作るにせよ、開発環境って結構大切ですよね。
本記事をきっかけに、Editor拡張を触ってみようと思う人が少しでも増えてくれると嬉しいです。
*1:とはいえUnity自体で各所で使われていますので、すぐに使えなくなるというようなこともないと思われます
*2:実際には、特定のdelegateに描画メソッドなどを += で差しにいきます
*3:Editorという名前のフォルダの中にあればいいので、場所はどこでもいいです。個人的には近くに置いたほうが分かりやすくて好きです
*4:パラメータが多くなったので、カテゴライズしてstructを用意しました
*5:ただし、ほかの要素のシリアライズデータもリセットされてしまうので注意してください
*6:商用で利用している方は当然利用できますよね?
*7:とはいえPro以上の人は実際に切り替えて色を確認できますが、無料版ではできないのでなかなか大変です...
*8:実際、ReorderableListの背景の描画では、要素の左側のマージンが微妙に違いました...
*9:Editor拡張のコードを自動生成する機能も一度作ったのですが...
*10:もし方法を知っている方がいらっしゃいましたら教えてほしいです