うにてぃブログ

主にUnityとC#に関する記事を書いていきます

【Unity】シリアライズしたオブジェクトの参照を制限する Attribute

以下のように記述している場合 Hierarchy にあるオブジェクトや Project にあるオブジェクト等
制限なく自由にオブジェクトの設定ができてしまう

[SerializeField]
private GameObject _obj;

そのため、オブジェクトに制限をかける Attribute を作成しました

制限は以下の3種類です

/// <summary>
/// 子供のオブジェクトのみ
/// </summary>
[SerializeField, ObjectRestrict(TargetType.ChildOnly)]
private GameObject _obj;
 
/// <summary>
/// Hierarchyのオブジェクトのみ
/// </summary>
[SerializeField, ObjectRestrict(TargetType.InHierarchy)]
private GameObject _obj;
 
/// <summary>
/// Project リソースのみ
/// </summary>
[SerializeField, ObjectRestrict(TargetType.InProject)]
private GameObject _obj;

コード

public enum TargetType
{
    /// <summary>
    /// 子供のオブジェクトのみ
    /// </summary>
    ChildOnly,
    /// <summary>
    /// Hierarchyのオブジェクトのみ
    /// </summary>
    InHierarchy,
    /// <summary>
    /// Project リソースのみ
    /// </summary>
    InProject,
}
 
[AttributeUsage(AttributeTargets.Field)]
public class ObjectRestrictAttribute : PropertyAttribute
{
    public TargetType target { get; }
 
    public ObjectRestrictAttribute(TargetType type)
    {
        target = type;
    }
}
 
[CustomPropertyDrawer(typeof(ObjectRestrictAttribute))]
public class NotNullDrawer : PropertyDrawer
{
    private ObjectRestrictAttribute _attribute => attribute as ObjectRestrictAttribute;
 
    public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
    {
        position.height = base.GetPropertyHeight(property, label);
        using (var check = new EditorGUI.ChangeCheckScope())
        {
            EditorGUI.PropertyField(position, property, label);
            if (check.changed && property.propertyType == SerializedPropertyType.ObjectReference)
            {
                if (!CanUpdateObject(property))
                {
                    property.objectReferenceValue = null;
                }
            }
        }
    }
 
    private bool CanUpdateObject(SerializedProperty property)
    {
        switch (_attribute.target)
        {
            case TargetType.ChildOnly:
            {
                var obj = property.serializedObject.targetObject as Component;
                var target = property.objectReferenceValue as GameObject;

                if (target == null || !target.activeInHierarchy)
                    return false;

                return HasChildRecursive(obj.transform, target.transform);
            }
 
            case TargetType.InHierarchy:
            {
                var target = property.objectReferenceValue as GameObject;
                return target != null && target.activeInHierarchy;
            }
 
            case TargetType.InProject:
            {
                return !string.IsNullOrEmpty(AssetDatabase.GetAssetPath(property.objectReferenceValue));
            }
        }
 
        return true;
    }
 
    private bool HasChildRecursive(Transform parent, Transform target)
    {
        foreach (Transform child in parent)
        {
            if (child.Equals(target))
                return true;
 
            if (HasChildRecursive(child, target))
                return true;
        }
 
        return false;
    }
}