やりたいこと
Unityで自作コンポーネントアタッチ後、子要素にあるコンポーネントをドラッグ&ドロップでバインドするのが面倒なので楽したい
やったこと
バインドのための命名ルールを決めた上でエディタ拡張を実装した
動作例
バインドしたいコンポーネント上で右クリックして BindComponensFromChildren
メニューを選択する
バインドされる。嬉しい。
実装概要
- リフレクションを使ってバインドできそうなそれっぽいフィールドを取得する
-
Component
を継承した型のフィールド -
public
またはSerializeField
属性のついたフィールド
-
- GameObjectの名前とフィールドの名前が一致したらバインドを試みる(大文字小文字比較なし)
- バインド可能なコンポーネントが複数存在した場合、先に見つけたほうを使用する
- GameObjectの探索に LINQ to GameObject 使ってます
コード
using System;
using System.Collections.Generic;
using System.Reflection;
using Unity.Linq;
using UnityEditor;
using UnityEngine;
namespace Assets.Scripts.Editor
{
static class ContextMenuUtil
{
/// <summary>
/// 子GameObjectにバインド可能な同名のコンポーネントがあったらバインドする。
///
/// ・比較する名前は大文字小文字の区別をしない
/// ・public または SerializeField属性のついたフィールドが対象
/// ・バインド可能なGameObjectが複数存在した場合、先に見つけたものをセットする
/// </summary>
[MenuItem("CONTEXT/MonoBehaviour/BindComponensFromChildren")]
private static void BindComponentsFromChildren(MenuCommand menuCommand)
{
var context = menuCommand.context as MonoBehaviour;
Debug.Log($"context.name: {context.name}");
var contextType = context.GetType();
Debug.Log($"contextType.Name: {contextType.Name}");
var bindableFields = GetBindableFields(contextType);
foreach (var field in bindableFields)
{
foreach (var child in context.gameObject.Descendants())
{
// フィールドとオブジェクトの名前を大文字小文字無視して比較する
if (string.Compare(child.name, field.Name, true) != 0)
{
// 一致しなかったらスキップ
continue;
}
var childComponent = child.GetComponent(field.FieldType);
if (childComponent == null)
{
// 対象のコンポーネントが存在しないのでスキップ
continue;
}
Debug.Log($"component {field.FieldType.Name} {childComponent.name} found.", childComponent);
field.SetValue(context, childComponent);
break;
}
}
EditorUtility.SetDirty(context);
AssetDatabase.SaveAssets();
}
/// <summary>
/// バインド可能なComponentフィールドの一覧を取得する
/// </summary>
private static IEnumerable<FieldInfo> GetBindableFields(Type contextType)
{
var serializableFields = new List<FieldInfo>();
var publicFields = contextType.GetFields(BindingFlags.Instance | BindingFlags.Public);
serializableFields.AddRange(publicFields);
// NonPublicなフィールドのうち、SerializeField属性が設定されてるものを取得する
var nonPublicFields = contextType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic);
foreach (var field in nonPublicFields)
{
var serializeFieldAttribute = field.GetCustomAttribute<SerializeField>();
if (serializeFieldAttribute != null)
{
serializableFields.Add(field);
}
}
foreach (var field in serializableFields)
{
var isComponentField = field.FieldType.IsSubclassOf(typeof(Component));
if (isComponentField)
{
yield return field;
}
}
}
}
}
気になってることとか
-
HideInInspector
やNonSerialized
なフィールドに対して値設定したらどうなるのか未検証 -
[field: SerializeField]
属性つけたプロパティに対しても同じことできると便利そう -
Reset
メソッドから呼び出してもいいかも - 似たような機能公式機能あってもおかしくなさそうだがググり方がわからない
- インスペクターにドラッグ&ドロップでコンポーネントの参照設定する操作ってなんていうの???バインド?セット?アタッチ?