• Unity检视面板的继承方法研究 (一)


      对于检视面板 Inspector 的面板继承方式对项目来说是很有必要的, 比如一个基类, 写了一个很好看的检视面板[CustomEditor(typeof(XXX))],

    可是所有子类的面板无法直接继承这个CustomEditor, 有些人的解决方案是把子类写检视面板的代码独立出来, 然后子类面板直接去调用这些Layout,

    非常浪费人力物力.

      最近发现有个 DecoratorEditor 脚本, 它实现了对 Unity 自带检视面板的扩展, 看到它实现某个类型的面板Inspector的方法, 就是使用Editor.CreateEditor 这个API

    创建了一个相应的Editor, 然后调用它的OnInspectorGUI() 方法来绘制原有面板的, 于是可以从这个地方着手.

       从设计上来说 [CustomEditor(typeof(XXX))] 在耦合上并没有太多的耦合, Unity 开发组的想法应该就是一个Editor对应一个组件, 它们对应类型之间的继承关系不应该

    互相影响, 保证泛用性和独立性. 

      原始的Editor脚本和类型是这样的:

    基类 :

    public class TestBaseClass : MonoBehaviour
    {
        public float abc;
    }
    
    // 检视面板
    [CustomEditor(typeof(TestBaseClass))]
    public class TestBaseClassInspector : Editor
    {
        TestBaseClass _target;
    
        private void OnEnable()
        {
            _target = target as TestBaseClass;
        }
    
        public override void OnInspectorGUI()
        {
            _target.abc = EditorGUILayout.FloatField("基类变量 : ", _target.abc);
        }
    }
    

    子类1 :

    public class TestCamera : TestBaseClass
    {
        public Camera cam;
    }
    
    // 检视面板
    [CustomEditor(typeof(TestCamera))]
    public class TestCameraInspector : Editor
    {
        TestCamera _target = null;
    
        private void OnEnable()
        {
            _target = target as TestCamera;
        }
    
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            
            _target.cam = EditorGUILayout.ObjectField("一次继承变量 : ", _target.cam, typeof(Camera), true) as Camera;
        }
    }
    

    子类2 : 

    public class TestUntiy : TestCamera
    {
        public int hahah;
    }
    
    // 检视面板
    [CustomEditor(typeof(TestUntiy))]
    public class TestUntiyInspector : Editor
    {
        TestUntiy _target = null;
    
        private void OnEnable()
        {
            _target = target as TestUntiy;
        }
    
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
    
            _target.hahah = EditorGUILayout.IntField("二次继承变量 : ", _target.hahah);
        }
    }
    

      

    TestBaseClass
    TestCamera : TestBaseClass
    TestUntiy : TestCamera

    非常简单的继承关系, TestUnity的检视面板如下, 没有继承关系

      那么在继承的设计上, 也应该遵循Unity的设计原则, 在继承类型 : Editor 以及 base.OnInspectorGUI(); 绘制基类方法上做文章.

    如果使用简单的继承比如:

    [CustomEditor(typeof(TestCamera))]
    public class TestCameraInspector : TestBaseClassInspector
    {
        TestCamera _target = null;
    
        private void OnEnable()
        {
            _target = target as TestCamera;
        }
    
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
    
            _target.cam = EditorGUILayout.ObjectField("一次继承变量 : ", _target.cam, typeof(Camera), true) as Camera;
        }
    }
    

      会出现很多问题, 如基类的OnEnable方法没有被触发, 基类面板报错等, 所有生命周期都要写虚方法, 每个重写方法都要注意, 很麻烦.

      而Editor.CreateEditor创建的Editor是有生命周期的. 创建一个中间类型 InspectorDecoratorEditor, 大家都继承它就可以了, 而绘制基类对象的方法就改为

    DrawBaseInspectorGUI<T>(), 这样就能方便地自己定义需要绘制的基类了.

    public class InspectorDecoratorEditor : Editor
    {
    	public static readonly System.Type EndType = typeof(UnityEngine.MonoBehaviour);     // end type dont need show in inspector
    	public static readonly System.Type BaseEditorType = typeof(UnityEditor.Editor);     // CustomEditor must inherit from it, filter
    	public static readonly BindingFlags CustomEditorFieldFlags = BindingFlags.NonPublic | BindingFlags.Instance;    // flag
    
    	// type cache[Assembly, [scriptType, customEditorType]]
    	protected static Dictionary<Assembly, Dictionary<System.Type, System.Type>> ms_editorReferenceScript
    		= new Dictionary<Assembly, Dictionary<System.Type, System.Type>>();
    
    	protected List<Editor> m_inheritEditors = null;     // cached editors
    
    	// ctor, use ctor instead Mono life circle, more user friendly 
    	public InspectorDecoratorEditor()
    	{
    		CacheEditorReferenceScript();
    	}
    
    	#region Main Funcs
    	/// <summary>
    	/// Cache all CustomEditor in current Assembly
    	/// </summary>
    	protected void CacheEditorReferenceScript()
    	{
    		var editorAssembly = Assembly.GetAssembly(this.GetType());      // editor may in diferent assemblies
    		if(ms_editorReferenceScript.ContainsKey(editorAssembly) == false)
    		{
    			Dictionary<System.Type, System.Type> cachedData = new Dictionary<System.Type, System.Type>();
    			var types = editorAssembly.GetExportedTypes();
    			foreach(var editorType in types)
    			{
    				if(editorType.IsSubclassOf(BaseEditorType))
    				{
    					var scriptType = GetTypeFormCustomEditor(editorType);
    					if(scriptType != null)
    					{
    						cachedData[scriptType] = editorType;
    					}
    				}
    			}
    			ms_editorReferenceScript[editorAssembly] = cachedData;
    		}
    	}
    
    	/// <summary>
    	/// Draw a Target Type Inspector, call OnInspectorGUI
    	/// </summary>
    	/// <typeparam name="T"></typeparam>
    	protected virtual void DrawBaseInspectorGUI<T>() where T : InspectorDecoratorEditor
    	{
    		if(m_inheritEditors == null)
    		{
    			m_inheritEditors = new List<Editor>();
    			Dictionary<System.Type, System.Type> scriptEditorCache = null;
    			if(ms_editorReferenceScript.TryGetValue(Assembly.GetAssembly(this.GetType()), out scriptEditorCache) && scriptEditorCache != null)
    			{
    				var baseType = target.GetType().BaseType;
    				while(baseType != null && baseType != EndType)
    				{
    					System.Type editorType = null;
    					if(scriptEditorCache.TryGetValue(baseType, out editorType) && editorType != null)
    					{
    						m_inheritEditors.Add(Editor.CreateEditor(targets, editorType));
    					}
    					baseType = baseType.BaseType;
    				}
    			}
    		}
    		if(m_inheritEditors.Count > 0)
    		{
    			for(int i = m_inheritEditors.Count - 1; i >= 0; i--)
    			{
    				var drawTarget = m_inheritEditors[i];
    				if(drawTarget && drawTarget.GetType() == typeof(T))
    				{
    					drawTarget.OnInspectorGUI();   // draw target type only, avoid endless loop
    					break;
    				}
    			}
    		}
    	}
    	#endregion
    
    	#region Help Funcs
    	public static System.Type GetTypeFormCustomEditor(System.Type editorType)
    	{
    		var attributes = editorType.GetCustomAttributes(typeof(CustomEditor), false) as CustomEditor[];
    		if(attributes != null && attributes.Length > 0)
    		{
    			var attribute = attributes[0];
    			var type = attribute.GetType().GetField("m_InspectedType", CustomEditorFieldFlags).GetValue(attribute) as System.Type;
    			return type;
    		}
    		return null;
    	}
    	#endregion
    }
    

      

      

      修改后的Editor代码如下, 修改的只有继承类以及DrawBaseInspectorGUI<T>函数, 注意这里对于T来说是没有类型检查的, 可是在函数中是有类型匹配的,

    就算传入错误类型也是安全的 :

    [CustomEditor(typeof(TestBaseClass))]
    public class TestBaseClassInspector : InspectorDecoratorEditor
    {
        TestBaseClass _target;
    
        private void OnEnable()
        {
            _target = target as TestBaseClass;
        }
    
        public override void OnInspectorGUI()
        {
            _target.abc = EditorGUILayout.FloatField("基类变量 : ", _target.abc);
        }
    }
    
    [CustomEditor(typeof(TestCamera))]
    public class TestCameraInspector : InspectorDecoratorEditor
    {
        TestCamera _target = null;
    
        private void OnEnable()
        {
            _target = target as TestCamera;
        }
    
        public override void OnInspectorGUI()
        {
            DrawBaseInspectorGUI<TestBaseClassInspector>();
    
            _target.cam = EditorGUILayout.ObjectField("一次继承变量 : ", _target.cam, typeof(Camera), true) as Camera;
        }
    }
    
    [CustomEditor(typeof(TestUntiy))]
    public class TestUntiyInspector : InspectorDecoratorEditor
    {
        TestUntiy _target = null;
    
        private void OnEnable()
        {
            _target = target as TestUntiy;
        }
    
        public override void OnInspectorGUI()
        {
            DrawBaseInspectorGUI<TestCameraInspector>();
    
            _target.hahah = EditorGUILayout.IntField("二次继承变量 : ", _target.hahah);
        }
    }
    

      然后看看检视面板现在的样子, 完美绘制了基类面板:

    DrawBaseInspectorGUI<T>() 这个绘制基类的请求强大的地方就是可以选择从哪个类型开始绘制, 比如
    DrawBaseInspectorGUI<TestCameraInspector>();
    换成
    DrawBaseInspectorGUI<TestBaseClassInspector>();
    那么 TestCameraInspector 这个检视面板就被跳过去了 :

     

      虽然有很多方式能够绘制或者继承子类检视面板, 不过这个应该是个泛用度很高的方案. Over.

  • 相关阅读:
    Java多线程问题
    pattern-matching as an expression without a prior match -scala
    从Zero到Hero,OpenAI重磅发布深度强化学习资源
    What-does-git-remote-and-origin-mean
    flink source code
    如何生成ExecutionGraph及物理执行图
    rocketmq 源码
    Flink source task 源码分析
    flink 获取上传的Jar源码
    fileupload
  • 原文地址:https://www.cnblogs.com/tiancaiwrk/p/10881981.html
Copyright © 2020-2023  润新知