ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Unity开发笔记-Odin标签实现原理探究

2022-02-16 23:34:13  阅读:217  来源: 互联网

标签:标签 void CustomEditorAttributesType MonoEditorType Unity BindingFlags var Odin pu


一些废话

为避免不必要的篇幅,本文中指列出关键代码。完整代码工程地址:https://github.com/terrynoya/HowCustomEditorBindWork

Odin在Unity编辑器扩展中的地位不必多说。只需简单的标签,Odin就能自动为我们实现之前需要大量编码才能实现的扩展。下面来探究下其背后的原理,在实践中体会Odin基于标签的设计思路的精妙和易于实用性。
我们知道,扩展Inspector需要用到CustomEditor标签和实现Editor子类来完成。

下面是MyClass和MyClassInspector代码,我们再熟悉不过了。

MyClass类

public class MyClass : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

MyClassInspector类

[CustomEditor(typeof(MyClass))]
public class MyClassInspector :UnityEditor.Editor
{
    public override void OnInspectorGUI()
    {
        base.OnInspectorGUI();
        if (GUILayout.Button("btn"))
        {
            OnBtnClick();
        }
    }

    public void OnBtnClick()
    {
        Debug.Log("my class btn clicked");
    }
}

可以看到,Unity通过CustomEditor标签把Myclass和MyClassInspector绑定起来。
但我们使用Odin的时候,并不需要声明CustomEditor标签和实现Editor的子类,其中的奥秘在哪里呢。

UnityEditor.CustomEditorAttributes是谜底

git源码地址:https://github.com/Unity-Technologies/UnityCsReference/blob/e740821767d2290238ea7954457333f06e952bad/Editor/Mono/CustomEditorAttributes.cs
我们来看一下Rebuild函数,出现了关键的CustomEditor,大概可以理解为,遍历之后,在kSCustomEditors或者kSCustomMultiEditors内,存放了关于Myclass和MyClassInspector之间的映射。

 internal static void Rebuild()
        {
            kSCustomEditors.Clear();
            kSCustomMultiEditors.Clear();
            var types = TypeCache.GetTypesWithAttribute<CustomEditor>();
            foreach (var type in types)
            {
                object[] attrs = type.GetCustomAttributes(typeof(CustomEditor), false);

                foreach (CustomEditor inspectAttr in  attrs)
                {
                    var t = new MonoEditorType();

下面我们动手做个试验来进行验证。
由于CustomEditorAttributes可见性是internal,我们需要利用反射调用内部的静态函数。

CustomEditorAttributesType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.CustomEditorAttributes");

然后获得kSCustomEditors属性

CustomEditorAttributesType_CustomEditors = CustomEditorAttributesType.GetField("kSCustomEditors", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);

我们打印一下ksCustomEditors的数据

var datas = (IDictionary)CustomEditorAttributesType_CustomEditors.GetValue(null);
foreach (var item in datas.Keys)
{
    Debug.Log(item);
}

在log中我们发现了MyClass

下面我们写一个方法,来删除kSCustomEditors内的所有数据,然后看看会发生什么

public static void ClearCustomEditors()
{
        ((IDictionary)CustomEditorAttributesType_CustomEditors.GetValue(null)).Clear();
}

调用之后我们看到之前写的MyClass的Inspector已经没有按钮了。

事实上我们清除了所有CustomEditor的绑定关系,看一下RectTransform的Inspector也更原始了。

好在我们可以通过调用刚才的Rebuild方法重新建立绑定关系。

CustomEditorAttributesType_Rebuild = CustomEditorAttributesType.GetMethod("Rebuild",BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
if (UnityVersion.IsVersionOrGreater(2019, 1))
{
    CustomEditorAttributesType_Rebuild.Invoke(null, null);
    CustomEditorAttributesType_Initialized.SetValue(null, true);
    return;
}
CustomEditorAttributesType_Initialized.SetValue(null, false);

实现自己的标签功能

很自然的,我们可以在CustomEditorAttributes的ksCustomEditor属性中,加入我们想要的数据,实现绑定关系

MonoEditorType = CustomEditorAttributesType.GetNestedType("MonoEditorType", BindingFlags.Public | BindingFlags.NonPublic);
MonoEditorType_InspectedType = MonoEditorType.GetField("m_InspectedType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
MonoEditorType_InspectorType = MonoEditorType.GetField("m_InspectorType", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

public static void SetCustomEditor(Type inspectedType, Type editorType, bool isFallbackEditor,
        bool isEditorForChildClasses, bool isMultiEditor)
    {
        object obj = Activator.CreateInstance(MonoEditorType);
        MonoEditorType_InspectedType.SetValue(obj, inspectedType);
        MonoEditorType_InspectorType.SetValue(obj, editorType);
        AddEntryToDictList((IDictionary) CustomEditorAttributesType_CustomEditors.GetValue(null), obj, inspectedType);
    }

下面写一个Button标签类

public class ButtonAttribute:Attribute
    {
        public string Text;

        public ButtonAttribute(string text)
        {
            Text = text;
        }
    }

新建一个MonoBehaviour类,模拟一个需要Button标签的业务逻辑

public class NoCustomEditorAttributeClass : MonoBehaviour
{
    [Button("HowOdinAttributeWork")]
    public void MyBtnClick()
    {
        Debug.Log("my btn clicked!!");
    }
    
    [Button("btn2")]
    public void Btn2()
    {
        Debug.Log("btn2");
    }
}

下面实现NoClassInspector,在OnInspectorGUI中,我们通过反射调用GetMethods方法,查看target的函数中,是否有ButtonAttribute标签,如果有,则绘制GUILayout.Button,然后method.Invoke实现标签对应的函数调用

 public class NoClassInspector:UnityEditor.Editor
    {
        
        public override void OnInspectorGUI()
        {
            // Debug.Log($"target:{this.target}");
            base.OnInspectorGUI();

            var type = target.GetType();
            var methods =  type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance);
            foreach (var method in methods)
            {
                var attr = method.GetCustomAttribute<ButtonAttribute>();
                if (attr != null)
                {
                    if (GUILayout.Button(attr.Text))
                    {
                        method.Invoke(target,null);
                    }                    
                }
            }
        }
    }

最后通过之前写好的SetCustomEditor建立NoCustomEditorAttributeClass和NoClassInspector之间的映射关系

CustomInspectorUtility.SetCustomEditor(typeof(NoCustomEditorAttributeClass),typeof(NoClassInspector),false,false,false);

大功告成

标签:标签,void,CustomEditorAttributesType,MonoEditorType,Unity,BindingFlags,var,Odin,pu
来源: https://www.cnblogs.com/terrynoya/p/15902487.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有