[Unity]自動的にGetComponentする

Unityで、変数を自動的にGetComponentする属性を紹介します。

 

毎回、スクリプト内でGetComponentを記述するのは面倒ではないですか?

変数の追加や削除をするたびにStart関数をチェックし、GetComponentの処理も追加して削除して…という作業に辟易している方が多いかと思います。

今回紹介するコードで、その煩わしさから解放されるかもしれません。

 

既に先駆者がいますが、この記事のコードでは、

にも適用できます。

 

 

 


 

使い方

①スクリプトをプロジェクトに保存

②BaseMonoBehaviourを継承

 

 

①スクリプトをプロジェクトに保存

https:// drive.google.com/file/d/1zc9ICMUm58X5X7WtOfPuX8ff5Selw314/view?usp=drive_link(旧版)

 

drive.google.com

上記のパッケージを、プロジェクトにインストールしてください。

 

②BaseMonoBehaviourを継承

利用したいクラスに、BaseMonoBehaviourを継承させます。

取得するフィールドに、[GetComponent]属性を付与してください。

 

サンプル

using UnityEngine;
using System.Linq;
using MantenseiLib; //忘れずに!

public class Test01 : BaseMonoBehaviour //継承を忘れずに!
{
    //デフォルトの記法
    [GetComponent] SpriteRenderer sr;

    //階層指定する場合
    [GetComponent(relation = HierarchyRelation.Self | HierarchyRelation.Parent)] CircleCollider2D col;

    //プロパティに使う
    [AddComponent] public Rigidbody2D Rb2d { get; private set; }

    //配列に使う
    [GetComponents(relation = HierarchyRelation.All)] 
    public Collider2D[] Colliders { get; private set; }

    private void Start()
    {
        Debug.Log(sr);
        Debug.Log(col);
        Debug.Log(Rb2d);
        Debug.Log(string.Join(", ", Colliders.Select(x => x.gameObject.name)));
    }
}

 

では、上記のスクリプトをアタッチしてみましょう。

↑取得したいコンポーネントはあらかじめアタッチしておきます。

 

実行します。

取得できました!

 

 

 

 

まとめ

BaseMonoBehaviourを継承する方法を紹介しましたが、パフォーマンスが気になる場合は、個別にGetOrAddComponent()を呼び出したり、FindObjectOfType()などで一括取得したりする方法も試すとよいかもしれません。

ただ、呼び出しが面倒だったり、ゲームの途中でInstantiateするオブジェクトには効かなかったりするため、最終的には継承に落ち着くような気がします。

(よりよい呼び出し方法や、バグ報告があればぜひ教えてください)

 

 

※注意

  • Parentã‚„Childrenを指定した場合、Selfを指定していなくても自分自身のコンポーネントも取得されます(GetComponentInParent、GetComponentInChildrenが自分自身を対象にするため)。 Parentã‚„Childrenを指定した際、自分自身のコンポーネントを含まないように変更しました!
  • [AddComponent]は親子オブジェクトには使用できません。

 

 

 


 

今回使用したコードはこちらです。

AttributeUtility

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using UnityEngine;

namespace MantenseiLib
{
    public static partial class AttributeUtility
    {
        ///
        /// 指定したプロパティと属性のペアを返します。
        /// 
        public static IEnumerable<(MemberInfo memberInfo, T attribute)> GetAttributedFields(object obj) where T : Attribute
        {
            Type type = obj.GetType();

            var fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
            var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);

            var fieldPairs = GetAttributePairs(fields);
            var propertyPairs = GetAttributePairs(properties);

            return fieldPairs.Concat(propertyPairs);
        }

        static IEnumerable<(MemberInfo, T)> GetAttributePairs(IEnumerable members) where T : Attribute
        {
            foreach (var member in members)
            {
                var attributes = GetAttributes(member);
                foreach (var attribute in attributes)
                    yield return (member, attribute);
            }
        }

        public static IEnumerable GetAttributes(MemberInfo info) where T : Attribute
        {
            var attributes = Attribute.GetCustomAttributes(info, typeof(T), true);
            if (attributes == null)
                yield break;

            foreach (var attribute in attributes)
                yield return (T)attribute;
        }
    }
}

 

GetComponentAttributes

using System;

namespace MantenseiLib
{
    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    public class GetComponentAttribute : Attribute
    {
        public HierarchyRelation relation { get; set; } = HierarchyRelation.Self;
        public virtual QuantityType quantity { get; } = QuantityType.Single;
        public virtual GetComponentType GetComponentType { get; } = GetComponentType.GetComponent;
        public bool HideErrorHandling { get; set; } = false;

        public GetComponentAttribute() { }
        public GetComponentAttribute(HierarchyRelation relation) { this.relation = relation; }
    }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    public class GetComponentsAttribute : GetComponentAttribute
    {
        public override QuantityType quantity => QuantityType.Multiple;
    }

    [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
    public class AddComponentAttribute : GetComponentAttribute
    {
        public override GetComponentType GetComponentType { get => GetComponentType.AddComponent; }
    }

    [Flags]
    public enum HierarchyRelation
    {
        Self = 1,    // 自分自身
        Parent = 2,  // 親
        Children = 4, // 子

        None = 0,
        All = Self | Parent | Children,
    }

    public enum GetComponentType
    {
        GetComponent,
        AddComponent,
    }

    public enum QuantityType
    {
        Single,
        Multiple
    }
}

 

GetComponentUtility

using System;
using System;
using System.Reflection;
using UnityEngine;
using System.Linq;
using System.Collections.Generic;

namespace MantenseiLib
{
    public static class GetComponentUtility
    {
        static bool _hideErrorHandling = false;

        public static void GetOrAddComponent(MonoBehaviour monoBehaviour)
        {
            var getComponentPairs = AttributeUtility.GetAttributedFields(monoBehaviour);

            foreach (var info in getComponentPairs)
            {
                object component = null;
                var memberInfo = info.memberInfo;
                var attribute = info.attribute;

                switch (attribute.GetComponentType)
                {
                    case GetComponentType.GetComponent:
                        component = GetComponentByRelations(monoBehaviour, memberInfo, attribute.relation, attribute.quantity);
                        break;

                    case GetComponentType.AddComponent:
                        component = AddComponentByRelation(monoBehaviour, memberInfo, attribute.relation);
                        break;
                }

                if (component == null && !attribute.HideErrorHandling && !_hideErrorHandling)
                {
                    Debug.LogWarning($"\"{memberInfo.GetMemberType()}\" の \"{memberInfo.Name}\" の \"{monoBehaviour.name}\" が見つかりません");
                }
                else
                {
                    SetComponent(monoBehaviour, memberInfo, component);
                }
            }
        }

        private static object GetComponentByRelations(MonoBehaviour obj, MemberInfo memberInfo, HierarchyRelation relations, QuantityType quantity)
        {
            Type componentType = memberInfo.GetMemberType();
            bool isArray = componentType.IsArray;
            Type elementType = isArray ? componentType.GetElementType() : componentType;

            object components = null;

            if (quantity == QuantityType.Single)
            {
                foreach (HierarchyRelation relation in Enum.GetValues(typeof(HierarchyRelation)))
                {
                    if (relation != HierarchyRelation.None && relations.HasFlag(relation))
                    {
                        components = GetComponentByRelation(obj, elementType, relation, quantity);
                        if (components as UnityEngine.Object != null) // Objectのnullは挙動が違うのでキャストしてnullチェック
                        {
                            break;
                        }
                    }
                }
            }
            else if (quantity == QuantityType.Multiple)
            {
                List componentList = new List();

                foreach (HierarchyRelation relation in Enum.GetValues(typeof(HierarchyRelation)))
                {
                    if (relation != HierarchyRelation.None && relations.HasFlag(relation))
                    {
                        object tempComponents = GetComponentByRelation(obj, elementType, relation, quantity);
                        if (tempComponents is Array tempArray)
                        {
                            foreach (var item in tempArray)
                            {
                                componentList.Add(item);
                            }
                        }
                    }
                }

                if (componentList.Count > 0)
                {
                    componentList = componentList.Distinct().ToList();
                    Array componentsArray = Array.CreateInstance(elementType, componentList.Count);
                    componentList.Distinct().ToArray().CopyTo(componentsArray, 0);

                    components = componentsArray;
                }
            }

            return components;
        }

        private static object GetComponentByRelation(MonoBehaviour obj, Type elementType, HierarchyRelation relation, QuantityType quantity)
        {
            switch (relation)
            {
                case HierarchyRelation.Parent:
                    return quantity == QuantityType.Single ? obj.transform.parent.GetComponentInParent(elementType)
                                                           : obj.transform.parent.GetComponentsInParent(elementType);

                case HierarchyRelation.Children:
                    if(quantity == QuantityType.Single)
                    {
                        foreach(var child in obj.transform)
                        {
                            var component = obj.GetComponentInChildren(elementType);
                            if (component != null) return component;
                        }
                        return null;
                    }
                    else
                    {
                        List components = new List();
                        foreach (Transform child in obj.transform)
                        {
                            components.AddRange(child.GetComponentsInChildren(elementType));
                        }
                        return components.ToArray();
                    }

                case HierarchyRelation.Self:
                default:
                    return quantity == QuantityType.Single ? obj.GetComponent(elementType)
                                                           : obj.GetComponents(elementType);
            }
        }

        private static object AddComponentByRelation(MonoBehaviour obj, MemberInfo memberInfo, HierarchyRelation relation)
        {
            if (relation == HierarchyRelation.Self)
            {
                return obj.gameObject.AddComponent(memberInfo.GetMemberType());
            }
            else
            {
                Debug.LogWarning("親や子にAddComponentすることはサポートされていません。");
                return null;
            }
        }

        private static void SetComponent(MonoBehaviour obj, MemberInfo memberInfo, object component)
        {
            if (memberInfo is FieldInfo fieldInfo)
            {
                Type fieldType = fieldInfo.FieldType;
                if (fieldType.IsArray && component is Component[] componentArray)
                {
                    // コンポーネント配列をフィールドに設定
                    Array array = Array.CreateInstance(fieldType.GetElementType(), componentArray.Length);
                    Array.Copy(componentArray, array, componentArray.Length);
                    fieldInfo.SetValue(obj, array);
                }
                else
                {
                    // 単一のコンポーネントをフィールドに設定
                    fieldInfo.SetValue(obj, component);
                }
            }
            else if (memberInfo is PropertyInfo propertyInfo)
            {
                var setMethod = propertyInfo.GetSetMethod(true);
                if (setMethod != null)
                {
                    Type propertyType = propertyInfo.PropertyType;
                    if (propertyType.IsArray && component is Component[] componentArray)
                    {
                        // コンポーネント配列をプロパティに設定
                        Array array = Array.CreateInstance(propertyType.GetElementType(), componentArray.Length);
                        Array.Copy(componentArray, array, componentArray.Length);
                        setMethod.Invoke(obj, new[] { array });
                    }
                    else
                    {
                        // 単一のコンポーネントをプロパティに設定
                        setMethod.Invoke(obj, new[] { component });
                    }
                }
                else
                {
                    Debug.LogWarning($"プロパティ \"{propertyInfo.Name}\" のセッターが見つかりません \"{obj.GetType()}\" の \"{obj.name}\"");
                }
            }
        }

        public static Type GetMemberType(this MemberInfo memberInfo)
        {
            if (memberInfo is FieldInfo fieldInfo)
            {
                return fieldInfo.FieldType;
            }
            else if (memberInfo is PropertyInfo propertyInfo)
            {
                return propertyInfo.PropertyType;
            }
            return null;
        }
    }
}

 

BaseMonoBehaviour

using System.Collections;
using System.Collections.Generic;
using MantenseiLib;
using UnityEngine;

namespace MantenseiLib
{
    public abstract partial class BaseMonoBehaviour : MonoBehaviour
    {
        protected virtual void Awake()
        {
            GetComponentUtility.GetOrAddComponent(this);
        }

    }
}