fc2ブログ
ヽ|∵|ゝ(Fantom) の 開発blog? ホーム »エディタ拡張
このページの記事一覧

【Unity】【エディタ拡張】インスペクタの UnityEvent を並べ替え可能にする  


 前回「インスペクタの配列やリストを並べ替え」を紹介したが、UnityEvent も並べ替えたいときもよくあるだろう。これもとても導入・利用が簡単なものがあるので、ついでに紹介しておこう。

EasyEventEditorMIT License

 また、あまりオープンソースに馴染みの無い人のためにも、導入方法も詳しく書いておく。慣れている人なら自分の方法でも構わない。といっても導入しただけで使えるスグレモノなので、簡単な使い方だけ見ておけば、すぐに使えると思う。


(※) Unity 2019.2.21f1 / Windows10(x64) で確認



■ライブラリのインポート

 まずは github でライブラリをダウンロードしよう。「Clone or download」から zip をダウンロードしても良いが、Releases ページにある .unitypackage を使う方が簡単なので、ここではそのやり方で導入してみよう。


Releases ページ(.unitypackage がダウンロードできる)

 .unitypackage をダウンロードしたら、プロジェクトビューにドラッグドロップしよう。ファイルは1つしかないので(※掲載時点:v1.0.1)、そのままインポートすれば良い。これだけで導入は完了だ。





 尚、zip の方には asmdef(Assembly Definition Files)やパッケージマネージャーでの導入するための package.json などが入っている。こちらの方法はまた別の方法となるので(あと、Unity のバージョンによるので)、興味があったら参考資料だけ載せておこう。

【Unity】Assembly Definition Filesという神機能
プロジェクト管理は進化しています – Unity Package Manager の概要
UnityのPackage Managerを使って機能をインストールする



■インスペクタで並べ替えてみるためのサンプルコード

 ここからは簡単なコードを書いて試してみよう。内容はかなり適当なので、好きなように書き換えて欲しい。

 もしあまり UnityEvent を使ったことないから、色々なイベントコールバックをするサンプルコードが以下の記事にまとめてあるので、参照して欲しい。

【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録

●インスペクタで並べ替えてみるためのサンプルコード(※.NET 4.x)
using UnityEngine;
using UnityEngine.Events;

public class EasyEventEditorTest : MonoBehaviour
{
//これがインスペクタに表示される
public UnityEvent OnCallback;

//UnityEvent のコールバック用1
public void Hoge()
{
Debug.Log("Hoge");
}

//UnityEvent のコールバック用2
public void Fuga()
{
Debug.Log("Fuga");
}

//UnityEvent のコールバック用3
public void Piyo()
{
Debug.Log("Piyo");
}

//UI-Button などの OnClick などに登録する等
public void Click()
{
OnCallback?.Invoke(); //コールバック発火
}
}

 このスクリプトをヒエラルキーで何らかの GameObject にアタッチしよう。

 後はインスペクタで実際に確認して欲しい(表示が更新されてなかったら、一旦フォーカスを外して、再度スクリプトをアタッチした GameObject を見てみると反映されてたりする ← Unity はコンパイルが走ると表示がデフォに戻ることがある)。

●左端にハンドルが付く


●ドラッグで移動できる


●移動後


 Click() を UI の Button に登録して試してみればわかるが(面倒なら Start() から呼んでも良い)、コールバックの発火はインスペクタで登録した上→下の順になる。試してみよう。

●並べ替え前
Hoge
Fuga
Piyo

●並べ替え後(※並び替えた順)
Piyo
Hoge
Fuga


 また、機能をオフにしたり、表示方法を変更したりする設定は「Edit>Preference」で「Easy Event Editor」タブ(Unity2019以降)にある。自分の見やすい設定にしておくと良いだろう。







(関連記事)
【Unity】【C#】インスペクタの配列やリストを並べ替え可能にする
【Unity】【C#】インスペクタで入力不可(Disable)な属性を作る
【Unity】【C#】インスペクタの値を保持したまま変数をリネームする
【Unity】【C#】インスペクタの表示項目を動的に変更する
【Unity】【C#】インスペクタでの UnityEvent のコールバック登録の有無を調べる
【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録


関連記事

category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityオープンソースライブラリ  Unityプラグイン  インスペクタ  エディタ拡張 
tb: 0   cm: --

【Unity】【エディタ拡張】インスペクタの配列やリストを並べ替え可能にする  


 この手のエディタ拡張はググればいくつか出てくるが、今回は特に導入・利用が簡単な「Reorderable Inspector」というオープンソースライブラリを紹介しよう。また、コード上で属性を付けるだけで、既に作ってあるものに後から機能追加できることも、このライブラリの魅力だ。

Reorderable InspectorMIT License

※Unity2020LTS では標準で利用できるようになりました(導入不要)

 あまりオープンソースに馴染みの無い人のためにも、導入方法も詳しく書いておく。慣れている人なら自分の方法でも構わない。それではやってみよう。


(※) Unity 2019.2.21f1 / Windows10(x64) で確認



■ライブラリのインポート

 まずは github でライブラリをダウンロードしよう。「Clone or download」から zip をダウンロードできるので、落としたら zip を解凍する。


Reorderable InspectorMIT License

 解凍したらいくつかファイルが出てくるので、プロジェクトビューで「Scripts」(名前は任意)フォルダを用意し、「ReorderableAttribute.cs」「EditScriptableAttribute.cs」をプロジェクトビューにドラッグ&ドロップしてインポートする

 また「Editor」以下のファイルも必要なので、フォルダごとインポートするか、自分で「Editor」(場所はどこでも構わないが、名前は "Editor" 固定)フォルダを作り、「ReorderableArrayInspector.cs」「SerializedPropExtension.cs」をインポートする。これだけで導入は完了だ。





■インスペクタで並べ替え可能な配列やリストを表示する

 ここからは簡単な使用例となる。配列やリストの変数名などは自分の任意で構わない。既に何らかのプロジェクトがあるのなら、それを使っても良い。

●ReorderableInspector の使用例
using System.Collections.Generic;
using UnityEngine;
using SubjectNerd.Utilities;

public class ReorderableInspectorTest : MonoBehaviour
{
//配列の例(public フィールド例)
[Reorderable]
public string[] strArray = { "Apple", "Banana", "Candy" };

//リストの例(private フィールド例)
[SerializeField, Reorderable]
List<string> strList = new List<string>() { "xxx", "yyy", "zzz" };
}

 要素の内容は適当だが、このスクリプトをヒエラルキーで何らかの GameObject にアタッチしよう。

 コードの要点としては「using SubjectNerd.Utilities;」を入れることと、フィールドに "[Reorderable]" を追加するということだ。逆に元に戻したければ "[Reorderable]" をコメントアウトするか、削除すれば良い。

 後はインスペクタで実際に確認して欲しい(表示が更新されてなかったら、一旦フォーカスを外して、再度スクリプトをアタッチした GameObject を見てみると反映されてたりする ← Unity はコンパイルが走ると表示がデフォに戻ることがある)。



●各項目をドラッグして並べ替えられるようになる


 もし、並び替え機能を自作したいなら、以下の資料を参考にすると良いだろう。ただ、自作となると Reflection の知識が必要となるので、結構大変だと思う(実は私も独自に作ってたのがあるが、結局このライブラリの方が楽なので、最近はこっちしか使ってない(笑))。興味があったら調べてみるのも良いだろう。

エディタ拡張で配列の入れ替えが簡単に出来るReorderableListの使い方と全コールバック【Unity】【エディタ拡張】
.NET のリフレクション
System.Reflection





(関連記事)
【Unity】【C#】インスペクタの UnityEvent を並べ替え可能にする
【Unity】【C#】インスペクタで入力不可(Disable)な属性を作る
【Unity】【C#】インスペクタの値を保持したまま変数をリネームする
【Unity】【C#】インスペクタの表示項目を動的に変更する
【Unity】【C#】インスペクタでの UnityEvent のコールバック登録の有無を調べる
【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録


関連記事

category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityオープンソースライブラリ  Unityプラグイン  インスペクタ  エディタ拡張 
tb: 0   cm: --

【Unity】【エディタ拡張】インスペクタで入力不可(Disable)な属性を作る  


 インスペクタに表示するだけで、インスペクタからは入力はできないフィールドを作りたいときは良くある。デバッグ用に表示だけしたいときとかね。

 サードパーティのインスペクタの拡張アセットなどにはよく付いているのだが、インポートするまでもなく、簡単に済ませたいときなどに便利なので掲載しておこう。何でもかんでもアセット導入すると、ファイルが増えすぎてビルド時間も長くなるしね。自作なら色々応用効くのも魅力だ(笑)。

(※) Unity 2018.4.8f1 / Windows10(x64) で確認



 必要なスクリプトは PropertyAttribute を継承した属性用スクリプトと、PropertyDrawer を継承したエディタ(インスペクタ)用スクリプトになる。エディタ表示用スクリプトは「Editor」フォルダを作って、そこに置いておく必要がある。

PropertyAttribute
PropertyDrawer



●属性用スクリプト:DisableAttribute.cs
using UnityEngine;

public class DisableAttribute : PropertyAttribute
{
//※中身は空で良い
}

●エディタ表示用スクリプト:DisableDrawer.cs
using UnityEngine;
using UnityEditor;

[CustomPropertyDrawer(typeof(DisableAttribute))]
public class DisableDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUI.GetPropertyHeight(property, label, true);
}

public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
GUI.enabled = false; //ここで入力不可にしている
EditorGUI.PropertyField(position, property, label, true);
GUI.enabled = true; //元に戻しておく
}
}


 例として適当な GameObject にテスト用スクリプトをアタッチして、インスペクタの表示を見てみよう。



●テスト用スクリプト:DisableAttributeTest.cs(※名前は任意)
using UnityEngine;

public class DisableAttributeTest : MonoBehaviour
{

[Disable] public string str = "hoge"; //インスペクタからは入力できない
[SerializeField, Disable] int num = 1; //インスペクタからは入力できない

[HideInInspector] public float f = Mathf.PI; //※これは単にインスペクタに表示しない例
}

 今回作ったのは "[Disable]" という属性だ。以前のバージョンでは "[DisableAttribute]" と書く必要があったらしいが、現在の Unity バージョンでは "Attribute" は省略できる。

 "[HideInInspector]" は単に表示したくないときの属性で、元々 Unity にビルトインされている。他にも色々な属性があるので、少し古いが、以下に資料を載せておこう。

UnityのAttribute(属性)についてまとめてメモる。

 アセットによっては "[ReadOnly]" などになってるかもだが、C# Job System でも使われる属性なので、今後はコンフリクトするかも知れない。その場合は namespace を付けておくのも一つの手だ。もっと色々自作したいなら、以下も少し古いが、資料を載せておこう。

自分だけのPropertyDrawerを作ろう!






(関連記事)
【Unity】インスペクタの値を保持したまま変数をリネームする
【Unity】【C#】インスペクタの配列やリストを並べ替え可能にする
【Unity】【C#】インスペクタの UnityEvent を並べ替え可能にする
【Unity】【C#】インスペクタでの UnityEvent のコールバック登録の有無を調べる
【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録
【Unity】【C#】インスペクタの表示項目を動的に変更する
【Unity】【C#】独自のギズモ(Gizmo)を表示する


関連記事

category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityリファレンス  エディタ拡張  インスペクタ 
tb: 0   cm: --

【Unity】【C#】インスペクタの値を保持したまま変数をリネームする  


 最近は色々なオープンソースを利用しているのだが、時折アップデートしたらプロジェクトが破壊されることもある。その1つがインスペクタの参照だろうか?

 残念ながら、インスペクタに設定されていたフィールド名を変えると、参照が外れることはよくあることだ。これが大量にあったら・・・まぁ、まともに動かなくなることは想像できるだろう(笑)。

 これは FormerlySerializedAs というものを使えば、回避できるのだが、微妙に凡例が長かったり、必要なものが欠けてたりと、ちょっとわかりずらいと思ったので、もっと簡単な例をメモ代わりに書いておこうと思った。

FormerlySerializedAsAttribute(公式マニュアル)

(※) Unity 2018.4.6f1 / Windows10(x64) で確認



■簡単な手順

 例として、ボタンの参照しているフィールドを

[SerializeField] private Button hogeButton;
 ↓
[SerializeField] private Button _hogeButton;


のように変更したいとする。インスペクタの表示名は変わらないが(hogeButton, _hogeButton, m_hogeButton などはコード内では別物だが、インスペクタでの表示は同じになる)、普通にリネームすると、これまで設定していたボタンへの参照が外れる。これをそのまま保持してリネームしたいという感じだ。

1.「using UnityEngine.Serialization;」をファイルの先頭に追加する。

2.[FormerlySerializedAs] の属性を加え、現在のフィールド名(変更前の名前)を入れる。

using UnityEngine.Serialization;

public class Example : MonoBehaviour
{
[FormerlySerializedAs("hogeButton")]
[SerializeField] private Button hogeButton;
}


3.フィールド名をリネーム(リファクタリング)して、一旦、シーンを保存する。また、プレファブ化してるものは個々に保存[Override]しておかないと参照が外れることがある(※要エディタでプレイ前に保存)。

using UnityEngine.Serialization;

public class Example : MonoBehaviour
{
[FormerlySerializedAs("hogeButton")]
[SerializeField] private Button _hogeButton;
}


4.インスペクタで値が保持されているのを確認できたら、FormerlySerializedAs 属性や using は削除しても良い(※複数から参照しているときは注意)。

using UnityEngine.Serialization;

public class Example : MonoBehaviour
{
[SerializeField] private Button _hogeButton;
}


 まぁ、それでも大量にあったら大変だけどね。できればリリース後は、public なものや過去のリソースに影響を与える変更は、極力控えて貰いたいなぁ・・・(バグとかクリティカルなものならともかく・・・ただの命名規則の変更で無駄に時間とられるのは勘弁・・・)。|||orz


(参考)
FormerlySerializedAsAttribute(公式マニュアル)
PrefabやSceneのSerializeされた変数の値を保持したままリネームする
FormerlySerializedAs(テラシュールブログ)






(関連記事)
【Unity】【C#】インスペクタで入力不可(Disable)な属性を作る
【Unity】【C#】インスペクタの配列やリストを並べ替え可能にする
【Unity】【C#】インスペクタの UnityEvent を並べ替え可能にする
【Unity】【C#】インスペクタの表示項目を動的に変更する
【Unity】【C#】インスペクタでの UnityEvent のコールバック登録の有無を調べる
【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録
【C#】【Unity】enum 型と string, int 型の相互変換など


関連記事

category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityトラブルシューティング  Unityリファレンス  エディタ拡張  インスペクタ 
tb: 0   cm: --

【Unity】【C#】インスペクタでの UnityEvent のコールバック登録の有無を調べる  


 利用頻度はあまり高くはないかもだが、インスペクタでの UnityEvent のコールバック登録があるか否かを調べたいときがある。実際に利用するには調べたい条件によってコードを変えるしかないかも知れないが、簡単な例を2つばかり挙げておこう。また、UnityEvent にはジェネリックパラメタの数違いのパターンがあるので、それらをカバーするためのオーバーロードも簡単に作っておく。

 ここでは汎用的に使えるように、拡張メソッドとして定義したものを書いておこう。ライブラリとして保存しておけば使い回しもできると思う。拡張メソッドにしたくない場合は、メソッドの引数の this を取り除けば良い。


(※) Unity 5.6.3p1 - 2018.1.8f1 / Windows10(x64) で確認



■インスペクタの UnityEvent にコールバックが1つでも登録されてるか否かを返す

 このメソッドたちは1つでもコールバック名が有るか無いかという判別をするものである。UnityEvent が null のときは常に false になる。ジェネリックパラメタの数違いはオーバーロードとして定義している。

●インスペクタの UnityEvent にコールバックメソッドが存在するか否かを返す(関数定義)
using System;
using UnityEngine;
using UnityEngine.Events;

public static class Extensions //※クラス名は任意
{
//インスペクタの UnityEvent にコールバックが1つでも登録されてるか否かを返す。
//※AddListener() で登録したものは無視されるので注意。
public static bool HasRegistered(this UnityEventBase obj)
{
if (obj != null)
{
int count = obj.GetPersistentEventCount(); //※AddListener() には無効
for (int i = 0; i < count; i++)
{
if (!string.IsNullOrEmpty(obj.GetPersistentMethodName(i))) //※AddListener() には無効
return true;
}
}
return false;
}

//※以下、パラメタ違いオーバーロード
public static bool HasRegistered(this UnityEvent obj)
{
return HasRegistered((UnityEventBase)obj);
}

public static bool HasRegistered<T0>(this UnityEvent<T0> obj)
{
return HasRegistered((UnityEventBase)obj);
}

public static bool HasRegistered<T0, T1>(this UnityEvent<T0, T1> obj)
{
return HasRegistered((UnityEventBase)obj);
}

public static bool HasRegistered<T0, T1, T2>(this UnityEvent<T0, T1, T2> obj)
{
return HasRegistered((UnityEventBase)obj);
}

public static bool HasRegistered<T0, T1, T2, T3>(this UnityEvent<T0, T1, T2, T3> obj)
{
return HasRegistered((UnityEventBase)obj);
}
}

●使用例(メインでのコードなど)
using UnityEngine;

Debug.Log("OnNothing.HasRegistered = " + OnNothing.HasRegistered());
Debug.Log("OnSingle.HasRegistered = " + OnSingle.HasRegistered());
Debug.Log("OnDouble.HasRegistered = " + OnDouble.HasRegistered());
Debug.Log("OnTriple.HasRegistered = " + OnTriple.HasRegistered());
Debug.Log("OnQuadruple.HasRegistered = " + OnQuadruple.HasRegistered());


//コールバックハンドラなど
public void ReceiveNothing()
{
Debug.Log("ReceiveNothing called.");
}

public void ReceiveSingle(string s)
{
Debug.Log("ReceiveSingle called.");
}

public void ReceiveDouble(string s, int i)
{
Debug.Log("ReceiveDouble called.");
}

public void ReceiveTriple(string s, int i, float f)
{
Debug.Log("ReceiveTriple called.");
}

public void ReceiveQuadruple(string s, int i, float f, bool b)
{
Debug.Log("ReceiveQuadruple called.");
}

● False になる例

OnNothing.HasRegistered = False
OnSingle.HasRegistered = False
OnDouble.HasRegistered = False
OnTriple.HasRegistered = False
OnQuadruple.HasRegistered = False

● True になる例

OnNothing.HasRegistered = True
OnSingle.HasRegistered = True
OnDouble.HasRegistered = True
OnTriple.HasRegistered = True
OnQuadruple.HasRegistered = True

 注意して欲しいのは「GetPersistentEventCount()」や「GetPersistentMethodName()」というメソッドを使っているが、これらは「AddListener()」でランタイム時に追加したメソッドには無効ということだ。なので、基本的にはインスペクタでの登録のみと考えて欲しい(タイトルが「インスペクタでの~」となってるのはそのため)。

 また、メソッドの存在は「GetPersistentMethodName()」で空か否かで判断しているだけなので、何らかミスなどで "<Missing ~>"(※ここではわざと無効な Dummy() メソッドを登録している)となっているメソッドも存在していると判断されてしまうので注意しよう。しかし通常、インスペクタでの登録は正常であるという前提なら問題はない。



■インスペクタの UnityEvent にシグニチャが合致しているコールバックが1つでも登録されてるか否かを返す

 次のメソッドたちは前述のコールバック名の存在有無より厳密に、コールバックの引数のシグニチャと同じものだけを検出するものである。実際インスペクタでの登録は違うシグニチャも登録できるわけで(例:コールバック引数が(string s, int i)であっても、引数が(string s)[※インスペクタで文字列を入力] のメソッドを登録することができる)、便利ではあるが前述の方法では正しくコールバックできるメソッドが存在するかは判定できないことにもなる。以下の方法では完全に正しいシグニチャを持つメソッドしか検出しないので、"<Missing ~>"(※ここではわざと無効な Dummy() メソッドを登録している)となっているものも排除できる。

●インスペクタの UnityEvent に引数が正しいコールバックメソッドが存在するか否かを返す(関数定義)
using System;
using System.Reflection;
using UnityEngine;
using UnityEngine.Events;

public static class Extensions //※クラス名は任意
{
//インスペクタの UnityEvent にシグニチャが合致しているコールバックが1つでも登録されてるか否かを返す。
public static bool HasValidMethod(this UnityEventBase obj, Type[] argumentTypes)
{
if (obj != null)
{
int count = obj.GetPersistentEventCount(); //※AddListener() には無効
for (int i = 0; i < count; i++)
{
string method = obj.GetPersistentMethodName(i); //※AddListener() には無効
if (!string.IsNullOrEmpty(method))
{
object target = obj.GetPersistentTarget(i); //※AddListener() には無効
MethodInfo info = UnityEventBase.GetValidMethodInfo(target, method, argumentTypes);
if (info != null)
return true;
}
}
}
return false;
}

//※以下、パラメタ違いオーバーロード
public static bool HasValidMethod(this UnityEvent obj)
{
return HasValidMethod(obj, new Type[]{});
}

public static bool HasValidMethod<T0>(this UnityEvent<T0> obj)
{
return HasValidMethod(obj, new Type[]{typeof(T0)});
}

public static bool HasValidMethod<T0, T1>(this UnityEvent<T0, T1> obj)
{
return HasValidMethod(obj, new Type[]{typeof(T0), typeof(T1)});
}

public static bool HasValidMethod<T0, T1, T2>(this UnityEvent<T0, T1, T2> obj)
{
return HasValidMethod(obj, new Type[]{typeof(T0), typeof(T1), typeof(T2)});
}

public static bool HasValidMethod<T0, T1, T2, T3>(this UnityEvent<T0, T1, T2, T3> obj)
{
return HasValidMethod(obj, new Type[]{typeof(T0), typeof(T1), typeof(T2), typeof(T3)});
}
}

●使用例(メインでのコードなど)
using UnityEngine;

Debug.Log("OnNothing.HasValidMethod = " + OnNothing.HasValidMethod());
Debug.Log("OnSingle.HasValidMethod = " + OnSingle.HasValidMethod());
Debug.Log("OnDouble.HasValidMethod = " + OnDouble.HasValidMethod());
Debug.Log("OnTriple.HasValidMethod = " + OnTriple.HasValidMethod());
Debug.Log("OnQuadruple.HasValidMethod = " + OnQuadruple.HasValidMethod());

//・・・コールバックハンドラなどは前述と同じで良いので省略・・・

● False になる例

OnNothing.HasValidMethod = False
OnSingle.HasValidMethod = False
OnDouble.HasValidMethod = False
OnTriple.HasValidMethod = False
OnQuadruple.HasValidMethod = False

● True になる例

OnNothing.HasValidMethod = True
OnSingle.HasValidMethod = True
OnDouble.HasValidMethod = True
OnTriple.HasValidMethod = True
OnQuadruple.HasValidMethod = True

 使い分けは大まかに「何でも良いからメソッドが登録されてる」ときと「正確に値を返せるメソッドが登録されてる」となるだろう。どちらを使うかはケースバイケースだと思う。


(関連記事)
【Unity】【C#】UnityEvent, Action, delegate, interface でのコールバック実装方法とインスペクタでの登録
【Unity】【C#】長押し(ロングタップ)を取得してコールバックする
【Unity】【C#】スワイプ(フリック)を判定、方向を取得してコールバックする
【Unity】【C#】ピンチ操作を取得してコールバックする
【Unity】【C#】VideoPlayer で動画の終了判定をする


関連記事

category: Unity

thread: ゲーム開発

janre: コンピュータ

tag: Unityライブラリ  Unityリファレンス  エディタ拡張  インスペクタ 
tb: 0   cm: --


プロフィール

Social

検索フォーム

全記事一覧

カテゴリ

ユーザータグ

最新記事

リンク

PR

▲ Pagetop