ActiveではないシーンにGameObjectを生成する【Unity】
概要
マルチシーン機能を使ってシーンをAdditiveでロードしたときに、遷移先のシーンではなく遷移前のシーンにGameObjectが生成されてしまうことがありました。これは遷移中はまだ遷移前のシーンがActiveであることが原因のようでした。そこでActiveではないシーンにGameObjectを生成する方法を調べてみました。
方法1: MoveGameObjectToSceneを使う
以下のようにMoveGameObjectToSceneを使えば生成したGameObjectを別のシーンに移動させることができます。
var obj = new GameObject(); // InstantiateされたGameObjectも同様 var scene = UnityEngine.SceneManagement.SceneManager.GetSceneByName("SampleScene"); UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(obj, scene);
ただし、この関数はシーンのルートにあるオブジェクトにしか使うことができません。
使うと以下のエラーが出ます。
ArgumentException: Gameobject is not a root in a scene
ルート以外のGameObjectも一旦ルートに変えれば別のシーンに移動させることができるため以下のような関数を用意すると便利かもしれません。
void MoveGameObjectToScene(GameObject obj, UnityEngine.SceneManagement.Scene scene) { obj.transform.SetParent(null); UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(obj, scene); }
方法2: 遷移先のシーンにあるGameObjectの子にする
MoveGameObjectToSceneを使わなくても遷移先のシーンにあるGameObject(Transform)の子にすれば別のシーンに移動させることができます。
var obj = new GameObject();
obj.transform.SetParent(targetSceneTransform);
その後にSetParent(null)をしたらルートオブジェクトにすることもできます。
var obj = new GameObject(); obj.transform.SetParent(targetSceneTransform); obj.transform.SetParent(null);
参考
環境
- Unity 2018.4.0f1
- VisualStudio 2019
シングルトンを継承する【C#】
概要
汎用的なクラスをシングルトンとして実装して使おうと考えましたが、状況によっては機能を追加したくなりそうだったためシングルトンの継承をすることを考えてみました。
シングルトンの継承を活用するパターン
シングルトンの継承を活用するパターンは大きく分けて2パターン考えられました。
実装
どちらのパターンでも使えるようにSingletonクラスを定義します。
public abstract class Singleton<SingletonType> : System.IDisposable where SingletonType : Singleton<SingletonType>, new() { private static SingletonType m_Instance; public static SingletonType Instance { get { return GetOrCreateInstance<SingletonType>(); } } protected static InheritSingletonType GetOrCreateInstance<InheritSingletonType>() where InheritSingletonType : class, SingletonType, new() { if (IsCreated) { // 基底クラスから呼ばれた後に継承先から呼ばれるとエラーになる。先に継承先から呼ぶ if (!typeof(InheritSingletonType).IsAssignableFrom(m_Instance.GetType())) { UnityEngine.Debug.LogErrorFormat( "{1}が{0}を継承していません", typeof(InheritSingletonType), m_Instance.GetType() ); } } else { m_Instance = new InheritSingletonType(); } return m_Instance as InheritSingletonType; } public static bool IsCreated { get { return m_Instance != null; } } public virtual void Dispose() { m_Instance = default; } /// <summary> /// コンストラクタ(外部からの呼び出し禁止) /// </summary> protected Singleton() { } }
1.基底も継承先も同じインスタンスとして利用するケース
パターン1のように使う場合は以下のように使います。
private class BaseClass1 : Singleton<BaseClass1> { /* 基本機能 */ } private class InheritClass1 : BaseClass1 { // 基底クラスのInstanceでは型が基底クラスのものになるので再定義 public static new InheritClass1 Instance { get { return GetOrCreateInstance<InheritClass1>(); } } /* 機能拡張 */ }
このように使う場合、BaseClass1.Instanceよりも先にInheritClass1.Instanceにアクセスして継承先の型でシングルトンを生成する必要があることに注意して下さい。
2.継承先ごとに別インスタンスとして利用するケース
パターン2のように使う場合は以下のように使います。
private abstract class BaseClass2<SingletonType> : Singleton<SingletonType> where SingletonType : Singleton<SingletonType>, new() { /* 共通機能 */ } private class InheritClass2A : BaseClass2<InheritClass2A> { /* 機能拡張A */ } private class InheritClass2B : BaseClass2<InheritClass2B> { /* 機能拡張B */ }
環境
- Unity 2018.4.0f1
- VisualStudio 2019
CameraFilterPack用のデモシーンを作る【Unity】
概要
少し前にCamera Filter Packという300種類以上のカメラエフェクトを集めたAssetを買いました。使い方はシンプルでカメラにスクリプトをAdd Componentしてパラメーターを設定するだけです。簡単なのですがこのAssetにはデモシーンが含まれておらず一つ一つ表示を確認するのがめんどうだったのでデモシーンを作ってみました。
デモシーン用スクリプト
フィルターをかけたいシーンが既にある場合は以下のスクリプトを適当なGameObjectにAdd Componentするだけです。シーンをまるごと作る話は後述します。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class CameraFilterPackDemo : MonoBehaviour { [SerializeField] private Camera m_Camera; // nullならMainCameraが使われる [SerializeField] private Camera m_SecondCamera; // Blend2Camera系フィルター用の2つ目のカメラ [SerializeField] private int m_FontSize = 40; [SerializeField] private Color m_FontColor = Color.red; [SerializeField] private KeyCode m_NextKey = KeyCode.L; [SerializeField] private KeyCode m_PrevKey = KeyCode.K; private string m_NamePrefix = "CameraFilterPack_"; private int m_CurrentIndex; private System.Type[] m_ComponentTypes; private Component m_LastComponent; protected new Camera camera { get { if(m_Camera == null) { return Camera.main; } return m_Camera; } } #if UNITY_EDITOR [UnityEditor.CustomEditor(typeof(CameraFilterPackDemo))] private class Editor : UnityEditor.Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); var self = target as CameraFilterPackDemo; // 再生中のみフィルターのリストをInspectorに表示する if (UnityEditor.EditorApplication.isPlaying) { m_Foldout = UnityEditor.EditorGUILayout.Foldout( m_Foldout, "Filter Name List" ); if (m_Foldout) { ++UnityEditor.EditorGUI.indentLevel; foreach (var type in self.m_ComponentTypes) { // Prefixを除いた名前を使う var name = type.Name.Substring(self.m_NamePrefix.Length); // 選択中のフィルター名には目印を付ける if (type == self.m_ComponentTypes[self.m_CurrentIndex]) { name = "→ " + name; } UnityEditor.EditorGUILayout.LabelField(name); } --UnityEditor.EditorGUI.indentLevel; } } } private bool m_Foldout; } #endif protected void Start() { m_ComponentTypes = GetFilterComponentTypes(); UpdateComponentType(); } protected void Update() { // 前にフィルターに切り替える if(Input.GetKeyDown(m_PrevKey)) { m_CurrentIndex = Mathf.Max(m_CurrentIndex - 1, 0); UpdateComponentType(); } // 次のフィルターに切り替える else if(Input.GetKeyDown(m_NextKey)) { m_CurrentIndex = Mathf.Min(m_CurrentIndex + 1, m_ComponentTypes.Length-1); UpdateComponentType(); } } protected void UpdateComponentType() { // 最後に使ったフィルターを削除する if(m_LastComponent != null) { DestroyImmediate(m_LastComponent); } // 次に使うフィルターをカメラに付ける var type = m_ComponentTypes[m_CurrentIndex]; m_LastComponent = camera.gameObject.AddComponent(type); // Blend2Camera系のフィルターであれば2つ目のカメラを設定する var camera2Field = m_LastComponent.GetType().GetField("Camera2"); if(camera2Field != null) { camera2Field.SetValue(m_LastComponent, m_SecondCamera); } } protected void OnGUI() { // 現在のフィルター名を表示する GUI.skin.label.fontSize = m_FontSize; GUI.color = m_FontColor; var type = m_ComponentTypes[m_CurrentIndex]; GUILayout.Label(type.Name.Substring(m_NamePrefix.Length)); } public System.Type[] GetFilterComponentTypes() { // フィルターをリストアップする var baseType = typeof(MonoBehaviour); var result = new List<System.Type>(); foreach (var assembly in System.AppDomain.CurrentDomain.GetAssemblies()) { foreach(var type in assembly.GetTypes()) { if( baseType.IsAssignableFrom(type) && type.Name.StartsWith(m_NamePrefix) ) { result.Add(type); } } } return result.ToArray(); } }
画面左上に現在のフィルター名が表示されます。Lキーを押すと次のフィルターに変わり、Kキーを押すと前のフィルターに変わります。各フィルターのパラメーターはMainCameraのInspectorから変更して下さい。
デモ用スクリプトのInspectorからキー設定や左上の文字サイズや色などを変更することができます。またSecond Cameraに2つ目のカメラを設定するとBlend2Camera系のフィルターで二つ目のカメラを自動的に設定してくれます。
このデモ用スクリプトは"CameraFilterPack_"で始まる名前のコンポーネントをCamera Filter Packのフィルターとして認識します。そのため、今後Camera Filter Packがバージョンアップしても同じ命名規則ならば追加されたスクリプトも表示確認することができます。逆に、命名規則が異なるフィルターが追加されたり、全く無関係のスクリプトが同じ命名規則になっていると正しく動作しない可能性があります。
デモシーンを作る
CameraFilterPackには3D用のエフェクトも含まれているので3D地形上でUnityちゃんが動き回れるシーンを作ります。
- Medieval Town Exteriors という無料のAssetをインポートする
- このAssetにDemoシーンが含まれているので開く(このシーンをベースに改造する)
- 『Unityちゃん(Unity-Chan)をノンプログラミングで動かす!』を参考にUnityちゃんを動かせるようにする
- Unityちゃんをインポート
- Unityちゃんを地形の家付近に配置
- Unityちゃん以下にカメラを配置してUnityちゃんに追従させる
- 適当なGameObjectにCameraFilterPackDemo(上記スクリプト)を付ける
- Blend2Camera系のフィルターの表示確認もしたいならもう一つカメラを配置してデモ用スクリプトにセットする(2つ目のカメラにはMainCameraタグを付けないこと)
完成品を撮影した動画がこちら(圧縮した時点で画質が結構劣化しています)。Unityちゃんを操作しながらでも簡単にフィルターを切り替えられています。
上記動画では各フィルターのパラメーターが未調整であることにご注意下さい。各フィルターについての詳細はこちらに書かれています。
環境
- Unity 2018.4.01f
- Camera Filter Pack 4.0.0
スクリプトを書かずに無限に広がるマップが作れる『MapMagic』の基本的な使い方【Unity】
概要
少し前にMapMagic World GeneratorというAssetを買いました。
簡単に言えば、スクリプトを書かずにプロシージャルに地形(Terrain)を作ったりその地形の上に木や岩などのオブジェクトを配置できるツールです。あらかじめ決められたルールに従ってランダムに生成されるので無限に広がるマップを作ることができます。
このAssetに含まれているデモではこのようなマップが作られています。
(その他にもAssetStoreなどで作成例のスクリーンショットや動画が見られます)
このMapMagicの使い方を数日間調べて試してみたのですが、日本語のチュートリアルがなく英語のチュートリアルもわかりにくく苦労したので基本的な使い方の解説を書いてみることにしました。
使い始めたばかりなので間違っている内容も含まれているかもしれません。もし間違いに気付いたらご連絡下さい。
MapMagicの基本的な使い方
MapMagicの機能は大きく分けて「地形の生成」と「オブジェクトの配置」の2つがあります。それぞれを順番に解説しますが まず最初に基本操作だけ説明しておきます。
1.基本操作
新規作成と編集画面の開き方
- 新規シーンを作成して開きます
- Hierarchyを右クリックして3D Object → Map Magicをクリック
- 生成されたGameObject(MapMagic)のInspectorから「Show Editor」を押す
- 見やすくなるようにSceneビューのカメラを調整(必要であればMainカメラも)
編集画面の操作方法
ノードの移動 | ノードの上からマウスの左ボタンのドラッグ |
全体の移動 | ホイールボタンのドラッグ。またはAlt+マウスの左ボタンのドラッグ |
ノードの削除 | ノードの上で右クリックしてRemoveを押す |
ノードの追加 | 右クリックしてCreateから種類を選択 |
ノードを繋ぐ | 青い〇から青い〇にドラッグ (後述しますが緑の〇もあります。色の違う〇同士は接続できません) |
リンクの切断 | ノードの左にある青い〇から何もない場所へドラッグ |
Terrainを複数表示する
これ以降の解説ではTerrainを1つだけ表示していますが複数表示することもできます。それぞれのマスで地形が微妙に異なり、隣合うTerrainが綺麗に繋がっていることも確認できます。
Terrainを表示中の場所を再度クリックすると非表示に戻ります。
変更が反映されないときの対処
グラフを変更してもTerrainやオブジェクトに変更が反映されないことがときどきあります。そういうときはEditor上部の「Force Generate All」を押すと反映されることがあります。
それでもまだおかしい場合は以下の対処で解決できるかもしれません。
現象 | 対処方法 |
---|---|
地形やオブジェクトのy座標がおかしい | Heightノードを追加する |
Terrainの色がおかしい | Texturesノードを追加する |
2.地形の作成
シンプルな地形を作る
新規作成すると既にNoise、Curve、Heightのノードがあり凸凹した地形が生成されていると思います。Curveの説明は後述するので今はCurveを削除してNoiseとHeightを繋ぎましょう。
Noiseノードは乱数を生成するためのノードで、HeightはTerrainに高さを設定するノードです。Noiseノードのパラメーターを変更すると形が変化します。試しにいろいろ変えて挙動を観察してみましょう。ノード内のテキストボックスに表示されている矢印をドラッグすると変更が簡単です。
ちなみに、普通のTerrainと同じく高さは一定の範囲に限定されます。Noiseの値が0~1を超えるとClampされます。
乱数(Noise)だけでなくボロノイ分割を使って高さを設定することもできます。
- 右クリックして Create → Map → Voronoi を選択
- VoronoiノードとHeightノードを繋ぐ
地形にテクスチャを設定する
次に地形にテクスチャを設定してみます。
- 右クリックしてCreate → Output → Texturesを選択
- ノードにTerrainLayerを設定
TerrainLayerはAssets/MapMagic/Demo/TerrainLayersにあるGreenGrassを使っています。
Texturesノードの[+]ボタンを押すともう一つTerrainLayerを設定したり、Noiseなどの出力することができます。
- Texturesノードの[+]ボタンを押す
- Noiseノードから Texturesノードに追加された青い〇に繋ぐ
- (凸凹してるとわかりにくいので)VoronoiノードとHeightノードのリンクを切断
このとき白く表示される部分がHeightノードでは高く表示され、緑で表示される部分がHeightノードでは低く表示されます。
NoiseノードだけでなくVoronoiノードからTexturesノードに繋ぐこともできます。
地形を変形する
単純にNoiseやVoronoiを出力するだけでなく間にノードを挟むことで形状を変化させることができます。最初に見たCurveノードもその一つです。
- 新規シーンでHierarchyを右クリックして 3D Object → Map Magic を押す(新規作成)
- Curveノードのグラフをクリックして編集する
さらに、Invertノードを使うと高さを反転することができます。
InvertではなくBlurノードを使うとぼけた見た目に(高さの変化がなだらかに)なります。
Terraceノードを使うと一定間隔ごとに平らな地面を作ることができます。こういう形状の方が使いやすいゲームもありそうです。
地形のマスク
マスクの説明をする前にまずSimple Formノードの説明をします。以下の図のようにSimple FormノードをHeightノードと繋ぐとコーン型の地形が生成されます。
このSimple FormをNoiseと一緒に使うと以下のようにコーン型にマスクすることができます。
Simple Formはコーン型以外の形状にもできるので別の形でマスクすることもできますし、Simple Form以外でも使えるのでNoiseをマスクとしてNoiseを生成することも可能です。
ちなみに、ノードの途中の出力結果を知りたい場合は 青い〇を右クリックして Preview → On Terrain を選択するとTerrain上に色で表示されます。色が赤いほど値が小さく緑に近いほど値が大きくなります。Texturesで出力するより簡単なので便利です。
この表示を元に戻したいときは Preview → Clear を押します。
その他の機能
その他にも地形の生成に関する機能があるので簡単に紹介します。
ノード名 | 機能 |
---|---|
Blend | 複数の出力をAddやOverlayなどの合成方法で混ぜる |
Normalize | 複数の出力を正規化して出力する |
Constant | 高さが一定の地形を作る。他のノードと組み合わせて使うことが多い |
Intensity/Bias | 高低差を強調する |
RAW Input | 指定された画像で高さを設定する |
Shore | 指定した高さに境界線を作る。島の海岸を作るときに使う |
Erosion | 地形が侵食されたような変形をする |
Slope | どう説明していいのかわからないのでリンク先を参照 |
Cavity | どう説明していいのかわからないのでリンク先を参照 |
3.オブジェクトの配置
オブジェクトをランダムに配置する
まずは配置するオブジェクト(prefab)を作ります。木や岩のモデルを使ってもいいのですがシンプルな形状の方がわかりやすいと思うのでCylinderを使います。RootのTransformはMapMagicが書き換えることが多いのでEmptyGameObjectにしておいてその子をCylinderにします。また今回は説明用にScaleをかなり大きくします。
オブジェクトの配置はランダムに座標リストを生成するScatterノードと、指定された座標にオブジェクトを生成するObjectsノードを使います。
地形が凸凹していてもオブジェクトは地形に沿って生成されます。
(※ ObjectsのRelative Heightのチェックが外れていると地形に沿わないので注意)
生成するオブジェクトがTree(Terrainで使う木オブジェクト)である場合はObjectsノードではなくTreesノードを使うことができます。Objectsノードとは設定項目が異なりますがその他に何が違うのかはまだよくわかっていません。Treesを試してみたい場合はAssets/MapMagic/Demo/Trees/Pine/PrefabsやAssets/MapMagic/Demo/Trees/Birch/Prefabsにあるprefabが使えます。
生成されるオブジェクトのTransformを変更する
上記の例では全てのオブジェクトが同じサイズでしたがランダムにサイズや姿勢を変えることもできます。
特定のエリアのみにオブジェクトを生成する
地形の作成でSimple Formをマスクとして使ったようにオブジェクトの生成範囲をマスクすることもできます。
ここまでの知識を応用してランダムに生成された地形の低い場所ほどオブジェクトが生成されやすくすることもできます。
森や群れのようなものを生成する
PropagateノードやForrestノードを使うと森や群れのようなものを生成することができます。
Propagateは単純に増やすだけですが、Forestではサイズ違いのオブジェクトが生成されます。森が広がるように中心ほど大きく、周囲はまばらで小さくなります。最初に作ったprefab(GreenObject)では大きくなりすぎるのでスケールを小さくしたprefabを作って使います。
生成位置が重ならないようにする
ScatterとObjectsを2つずつ使えば2種類のオブジェクトを生成することもできますが生成位置が重なってしまうことがあります。重なっても違和感がないものであればいいですが岩と木のような組み合わせだとバグに見えてしまいます。
この問題はSubtractノードを使って解決できます。Subtractは引き算のような処理で Inputに指定された座標からSubtrahendに指定された座標付近のオブジェクトを消します。
オブジェクト生成位置の土地を平らにする
以下のように凸凹した地形の上にオブジェクトを生成できることは説明済みですが、このオブジェクトが木ではなく宝箱だった場合斜面を転がり落ちてしまうかもしれません。
この問題はFlattenを使って地面を平らにすれば解決することができます。
草を生やす
草を生やす場合はScatterやObjectsではなく、GrassノードとNoiseノードなどを組み合わせて実現します。Assets/MapMagic/Demo/Grass以下に草のテクスチャがいくつかあるのでこれで試すことができます。
4.その他
グラフを見やすくする
MapMagicにはグラフを見やすくするための機能もいくつかあります。例えば、Portalはリンクが複雑になってしまったのを整理するのに使えます。
また、Groupはノードのグループ化をすることができます。グループ単位で移動させたり削除したりできます。
グラフの分割
複雑なマップを作るときにはBiomeを使ってグラフを分割すると見やすくなるかもしれません。まずは1つ目のグラフを作ります。
「Exit Biome」を押して元のグラフに戻り、2つ目のグラフを作ります。
元のグラフに戻り、以下のように繋ぐと1つ目と2つ目のBiomeを合成することができます。
1つのグラフの中で全部作るよりだいぶスッキリすると思います。
最後に
もっと複雑なマップを作るには
ここまで解説した内容は基礎だけなので実際にはこれらを組み合わせてもっと複雑なマップを作ることになります。例えばこのAssetに含まれているDemoSceneはこれくらいの複雑さになります。
ここまでの解説を理解した人ならばそこそこ理解できるのではないかと思いますが、詳しく解説しなかったノードも使われているのでそれらを理解する必要があります。一通りのノードを理解したらDemoSceneのグラフを読み解いてみたり、複雑なマップの解説動画などを見たりして慣れていくことになるかと思います。
また、MapMagicはVoxelandやCTSなどと組み合わせて使うこともできるそうなのでこれらと組み合わせることでもっと複雑なマップや綺麗なマップを作れるようになるかもしれません。
MapMagicを使うのに向いてそうなこと、向いてなさそうなこと
MapMagicはとても便利なツールだとは思いますが向き不向きを考えないと逆に苦労することになるかもしれないとは思いました。
例えば、ランダムに生成される迷路を作ろうと思うとどうやってゴールができることを保証するかが問題になると思います。独自のノードを実装すれば不可能ではないと思いますが、そういうことをやり始めると作業コストが増えていきMapMagicの利点が損なわれるかもしれません。
ランダムな宝探しゲームならプレイヤーの移動性能と地形の相性が悪いと全ての宝を取れない可能性があります。プレイヤーがどこへでも移動できるほど性能を高くするか、宝を全て集めなくても問題が発生しない仕様にする必要があるかもしれません。
また、手作業で丁寧に細かく調整したマップと同じレベルのものをMapMagicで作ろうとすると手作業で作ったものほど面白く作れなかったり、面白くできたとしても手作業以上の作業コストがかかるかもしれません。手作業で調整しきれないほど広いマップを作るのであればMapMagicの方が向いていると思います。地形が作りこまれていなくても成立するゲームを考えるというのもアリかもしれません。
MapMagicを有効活用できそうな使い方
MapMagicの有効活用パターンは以下の3つだと思いました。
- 非常に広いマップを使うゲームを作る
- オープンワールド、無限に走れるランゲームなど
- マップがランダム生成であることを生かしたゲームを作る
- 不思議なダンジョンのようなゲームや、ランダムマップでの対戦ゲームなど
- 試作用にのみ使う
- (細かいことを気にしなければ)いろんなパターンのマップを大量に素早く作れる
- デザイナーでなくてもそこそこいい感じのマップが簡単に作れる
- MapMagicで生成されたマップをベースに手作業で調整するような使い方もできる
自分は3番を主目的として買いました。まだ勉強中であまり活用はできていませんが予想していたよりも汎用性が高く便利そうです。
環境
- Unity 2018.4.0f1
- MapMagic 1.10.3
インデクサーのPropertyInfoを取得する【C#】
概要
Reflectionを使ってプロパティやフィールドにアクセスする実装をするときにインデクサー(添え字演算子)のPropertyInfo(MemberInfo)を取得する方法がわからなかったので調べてみました。
取得方法
大抵のケースでは以下のように"Item"という名前のプロパティを取得したらそれで済みます。
var property = typeof(TypeName).GetProperty("Item");
ただし、以下のようなケースでは単純に取得できない可能性があります。
- IndexerNameAttributeが使われ"Item"以外の名前になっている可能性がある
- 一つの型に複数のインデクサーが定義されている可能性がある
- public以外で定義されている可能性がある
- 基底クラスにも定義されている可能性がある
これらのケースにも対応するなら以下のような関数を用意して、複数見つかったらGetIndexParametersの型をチェックして目的のPropertyを探すのが良さそうです。
public static IList<PropertyInfo> GetIndexerPropertyInfoList( System.Type type, bool selfTypeOnly = false, BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly ) { var result = new List<PropertyInfo>(); while (type != null) { foreach (var property in type.GetProperties(flags)) { // 引数があるプロパティをインデクサーとみなす if (property.GetIndexParameters().Length > 0) { result.Add(property); } } // 基底クラスまでチェックしないならループを抜ける if(selfTypeOnly) { break; } type = type.BaseType; } return result; }
余談
- インデクサーの定義とは別に"Item"という名前のプロパティが既にあると名前が衝突してエラー(CS0102)になる。こういうケースでIndexerNameAttributeが使われる可能性がある
- インデクサーのプロパティもその他のプロパティと同様にget_Item、set_Itemのようなメソッドが暗黙的に定義される
- インデクサーにより暗黙的に定義されたメソッドと普通に定義されたメソッドの名前が衝突した場合もエラー(CS0082)になる
- 複数のインデクサーがあるときに単純にGetPropertyすると「System.Reflection.AmbiguousMatchException : Ambiguous match found.」というエラーが発生する可能性がある
参考
環境
- VisualStudio 2019
- Unity 2018.3.14f1 (.NET4.x)
リストの中から文字列で絞り込んで要素を選択するEditor用GUIを作る【Unity】
概要
リストの中から要素を選択させるGUIを作る場合、EditorGUILayout.Popupを使う方法がありますが要素が大量になってくると/で区切って分類しても探すのに苦労することがあります。そこで他にいい機能がないか探してみたところOdinの中にOdinSelectorというものを見つけたので試してみました。
使用例
まずは選択ウィンドウの実装をします。
- OdinSelectorのGeneric引数に要素の型を指定して継承
- 必要ならコンストラクタでパラメーターを受け取る
- BuildSelectionTreeをoverrideして選択可能な要素をAddしていく
今回は指定された型が持つメソッドを選択するものを作ってみます。
using Sirenix.OdinInspector.Editor; using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; /// <summary> /// 指定されたTypeのメソッドを選択するOdinSelector /// </summary> public class MethodInfoSelector : OdinSelector<MethodInfo> { private System.Type m_Type; public MethodInfoSelector(System.Type type) { m_Type = type; } protected override void BuildSelectionTree(OdinMenuTree tree) { tree.Selection.SupportsMultiSelect = false; // 選択可能な要素をリストアップ var flags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public; foreach (var member in m_Type.GetMethods(flags)) { var path = string.Format("{0}.{1}", m_Type.FullName, member.Name); tree.Add(path, member); } } }
このSelectorを使うときは以下のように使います。
using Sirenix.OdinInspector.Editor; using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; public class NewBehaviourScript : MonoBehaviour { public string m_Value; [UnityEditor.CustomEditor(typeof(NewBehaviourScript))] private class Editor : UnityEditor.Editor { public override void OnInspectorGUI() { base.OnInspectorGUI(); // 選択ボタンを押したらウィンドウを開く if (GUILayout.Button("選択")) { var behaviour = target as NewBehaviourScript; // Vector3のメソッドを選択する var selector = new MethodInfoSelector(typeof(Vector3)); selector.SelectionChanged += (e) => { var selections = e.ToArray(); if (selections.Length > 0) { // 選択されたメソッドの名前を格納する behaviour.m_Value = selections[0].Name; } }; selector.ShowInPopup(); } } } }
このコンポーネントをGameObjectに付けて選択ボタンを押すと以下のようにウィンドウが表示されます。
「Search」と書かれたところに文字列を入力すると絞り込むことができます。
おまけ
- System.Typeを選択したい場合はTypeSelectorというものが既にある
- Selectorに現在の値をセットしたい場合はSetSelectionを呼ぶ
環境
- Unity 2018.3.14f1
- Odin Inspector 2.0.19
- VisualStudio 2019