LoginSignup
1
0

More than 3 years have passed since last update.

Unityで自作コンポーネントアタッチ後、子要素にあるコンポーネントをドラッグ&ドロップでバインドするのが面倒なので楽したい

Last updated at Posted at 2021-03-27

やりたいこと

Unityで自作コンポーネントアタッチ後、子要素にあるコンポーネントをドラッグ&ドロップでバインドするのが面倒なので楽したい

やったこと

バインドのための命名ルールを決めた上でエディタ拡張を実装した

動作例

バインドしたいコンポーネント上で右クリックして BindComponensFromChildren メニューを選択する

image.png

バインドされる。嬉しい。

image.png

実装概要

  • リフレクションを使ってバインドできそうなそれっぽいフィールドを取得する
    • 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;
                }
            }
        }
    }
}

気になってることとか

  • HideInInspectorNonSerialized なフィールドに対して値設定したらどうなるのか未検証
  • [field: SerializeField] 属性つけたプロパティに対しても同じことできると便利そう
  • Resetメソッドから呼び出してもいいかも
  • 似たような機能公式機能あってもおかしくなさそうだがググり方がわからない
  • インスペクターにドラッグ&ドロップでコンポーネントの参照設定する操作ってなんていうの???バインド?セット?アタッチ?
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0