经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 游戏设计 » 查看文章
Unity检视面板的继承方法研究
来源:cnblogs  作者:tiancaiKG  时间:2019/5/20 8:38:43  对本文有异议

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

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

非常浪费人力物力.

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

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

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

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

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

基类 :

  1. public class TestBaseClass : MonoBehaviour
  2. {
  3. public float abc;
  4. }
  5.  
  6. // 检视面板
  7. [CustomEditor(typeof(TestBaseClass))]
  8. public class TestBaseClassInspector : Editor
  9. {
  10. TestBaseClass _target;
  11.  
  12. private void OnEnable()
  13. {
  14. _target = target as TestBaseClass;
  15. }
  16.  
  17. public override void OnInspectorGUI()
  18. {
  19. _target.abc = EditorGUILayout.FloatField("基类变量 : ", _target.abc);
  20. }
  21. }

子类1 :

  1. public class TestCamera : TestBaseClass
  2. {
  3. public Camera cam;
  4. }
  5.  
  6. // 检视面板
  7. [CustomEditor(typeof(TestCamera))]
  8. public class TestCameraInspector : Editor
  9. {
  10. TestCamera _target = null;
  11.  
  12. private void OnEnable()
  13. {
  14. _target = target as TestCamera;
  15. }
  16.  
  17. public override void OnInspectorGUI()
  18. {
  19. base.OnInspectorGUI();
  20. _target.cam = EditorGUILayout.ObjectField("一次继承变量 : ", _target.cam, typeof(Camera), true) as Camera;
  21. }
  22. }

子类2 : 

  1. public class TestUntiy : TestCamera
  2. {
  3. public int hahah;
  4. }
  5.  
  6. // 检视面板
  7. [CustomEditor(typeof(TestUntiy))]
  8. public class TestUntiyInspector : Editor
  9. {
  10. TestUntiy _target = null;
  11.  
  12. private void OnEnable()
  13. {
  14. _target = target as TestUntiy;
  15. }
  16.  
  17. public override void OnInspectorGUI()
  18. {
  19. base.OnInspectorGUI();
  20.  
  21. _target.hahah = EditorGUILayout.IntField("二次继承变量 : ", _target.hahah);
  22. }
  23. }

  

  1. TestBaseClass
  1. TestCamera : TestBaseClass
  1. TestUntiy : TestCamera

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

 

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

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

  1. [CustomEditor(typeof(TestCamera))]
  2. public class TestCameraInspector : TestBaseClassInspector
  3. {
  4. TestCamera _target = null;
  5.  
  6. private void OnEnable()
  7. {
  8. _target = target as TestCamera;
  9. }
  10.  
  11. public override void OnInspectorGUI()
  12. {
  13. base.OnInspectorGUI();
  14.  
  15. _target.cam = EditorGUILayout.ObjectField("一次继承变量 : ", _target.cam, typeof(Camera), true) as Camera;
  16. }
  17. }

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

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

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

  1. public class InspectorDecoratorEditor : Editor
  2. {
  3. public static readonly System.Type EndType = typeof(UnityEngine.MonoBehaviour); // end type dont need show in inspector
  4. public static readonly System.Type BaseEditorType = typeof(UnityEditor.Editor); // CustomEditor must inherit from it, filter
  5. public static readonly BindingFlags CustomEditorFieldFlags = BindingFlags.NonPublic | BindingFlags.Instance; // flag
  6.  
  7. // type cache[Assembly, [scriptType, customEditorType]]
  8. protected static Dictionary<Assembly, Dictionary<System.Type, System.Type>> ms_editorReferenceScript
  9. = new Dictionary<Assembly, Dictionary<System.Type, System.Type>>();
  10.  
  11. protected List<Editor> m_inheritEditors = null; // cached editors
  12.  
  13. // ctor, use ctor instead Mono life circle, more user friendly
  14. public InspectorDecoratorEditor()
  15. {
  16. CacheEditorReferenceScript();
  17. }
  18.  
  19. #region Main Funcs
  20. /// <summary>
  21. /// Cache all CustomEditor in current Assembly
  22. /// </summary>
  23. protected void CacheEditorReferenceScript()
  24. {
  25. var editorAssembly = Assembly.GetAssembly(this.GetType()); // editor may in diferent assemblies
  26. if(ms_editorReferenceScript.ContainsKey(editorAssembly) == false)
  27. {
  28. Dictionary<System.Type, System.Type> cachedData = new Dictionary<System.Type, System.Type>();
  29. var types = editorAssembly.GetExportedTypes();
  30. foreach(var editorType in types)
  31. {
  32. if(editorType.IsSubclassOf(BaseEditorType))
  33. {
  34. var scriptType = GetTypeFormCustomEditor(editorType);
  35. if(scriptType != null)
  36. {
  37. cachedData[scriptType] = editorType;
  38. }
  39. }
  40. }
  41. ms_editorReferenceScript[editorAssembly] = cachedData;
  42. }
  43. }
  44.  
  45. /// <summary>
  46. /// Draw a Target Type Inspector, call OnInspectorGUI
  47. /// </summary>
  48. /// <typeparam name="T"></typeparam>
  49. protected virtual void DrawBaseInspectorGUI<T>() where T : InspectorDecoratorEditor
  50. {
  51. if(m_inheritEditors == null)
  52. {
  53. m_inheritEditors = new List<Editor>();
  54. Dictionary<System.Type, System.Type> scriptEditorCache = null;
  55. if(ms_editorReferenceScript.TryGetValue(Assembly.GetAssembly(this.GetType()), out scriptEditorCache) && scriptEditorCache != null)
  56. {
  57. var baseType = target.GetType().BaseType;
  58. while(baseType != null && baseType != EndType)
  59. {
  60. System.Type editorType = null;
  61. if(scriptEditorCache.TryGetValue(baseType, out editorType) && editorType != null)
  62. {
  63. m_inheritEditors.Add(Editor.CreateEditor(targets, editorType));
  64. }
  65. baseType = baseType.BaseType;
  66. }
  67. }
  68. }
  69. if(m_inheritEditors.Count > 0)
  70. {
  71. for(int i = m_inheritEditors.Count - 1; i >= 0; i--)
  72. {
  73. var drawTarget = m_inheritEditors[i];
  74. if(drawTarget && drawTarget.GetType() == typeof(T))
  75. {
  76. drawTarget.OnInspectorGUI(); // draw target type only, avoid endless loop
  77. break;
  78. }
  79. }
  80. }
  81. }
  82. #endregion
  83.  
  84. #region Help Funcs
  85. public static System.Type GetTypeFormCustomEditor(System.Type editorType)
  86. {
  87. var attributes = editorType.GetCustomAttributes(typeof(CustomEditor), false) as CustomEditor[];
  88. if(attributes != null && attributes.Length > 0)
  89. {
  90. var attribute = attributes[0];
  91. var type = attribute.GetType().GetField("m_InspectedType", CustomEditorFieldFlags).GetValue(attribute) as System.Type;
  92. return type;
  93. }
  94. return null;
  95. }
  96. #endregion
  97. }

  

  

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

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

  1. [CustomEditor(typeof(TestBaseClass))]
  2. public class TestBaseClassInspector : InspectorDecoratorEditor
  3. {
  4. TestBaseClass _target;
  5.  
  6. private void OnEnable()
  7. {
  8. _target = target as TestBaseClass;
  9. }
  10.  
  11. public override void OnInspectorGUI()
  12. {
  13. _target.abc = EditorGUILayout.FloatField("基类变量 : ", _target.abc);
  14. }
  15. }
  16.  
  17. [CustomEditor(typeof(TestCamera))]
  18. public class TestCameraInspector : InspectorDecoratorEditor
  19. {
  20. TestCamera _target = null;
  21.  
  22. private void OnEnable()
  23. {
  24. _target = target as TestCamera;
  25. }
  26.  
  27. public override void OnInspectorGUI()
  28. {
  29. DrawBaseInspectorGUI<TestBaseClassInspector>();
  30.  
  31. _target.cam = EditorGUILayout.ObjectField("一次继承变量 : ", _target.cam, typeof(Camera), true) as Camera;
  32. }
  33. }
  34.  
  35. [CustomEditor(typeof(TestUntiy))]
  36. public class TestUntiyInspector : InspectorDecoratorEditor
  37. {
  38. TestUntiy _target = null;
  39.  
  40. private void OnEnable()
  41. {
  42. _target = target as TestUntiy;
  43. }
  44.  
  45. public override void OnInspectorGUI()
  46. {
  47. DrawBaseInspectorGUI<TestCameraInspector>();
  48.  
  49. _target.hahah = EditorGUILayout.IntField("二次继承变量 : ", _target.hahah);
  50. }
  51. }

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

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

 

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

原文链接:http://www.cnblogs.com/tiancaiwrk/p/10881981.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号