拓冰建站拓冰建站
首页 / 资讯中心 / 正文

u3d IMGUI[二] 编辑器扩展

本文意在精简和整合IMGUI的官方文档的编辑器扩展部分抽出重点我的体会是编辑器扩展处处都是规范学习的过程就是掌握规范APIDEMO一.IMGUI 创建自定义Editor窗口可通过以下官方定义的固定步骤实现自定义编辑器窗口1.实现一个继承EditorWindow的脚本并放在Editor目录下2.通过特性实现一个菜单3.调用EditorWindow.GetWindow创建窗口4.通过OnGUI绘制窗口内UIusing UnityEngine; using UnityEditor; using System.Collections; public class IMGUIEdt : EditorWindow { string myString Hello World; bool groupEnabled; bool myBool true; float myFloat 1.23f; [MenuItem(Window/My Window)] public static void ShowWindow() { EditorWindow.GetWindow(typeof(IMGUIEdt)); } void OnGUI() { GUILayout.Label (Base Settings, EditorStyles.boldLabel); myString EditorGUILayout.TextField (Text Field, myString); groupEnabled EditorGUILayout.BeginToggleGroup (Optional Settings, groupEnabled); myBool EditorGUILayout.Toggle (Toggle, myBool); myFloat EditorGUILayout.Slider (Slider, myFloat, -3, 3); EditorGUILayout.EndToggleGroup (); } }参考使用 IMGUI 扩展 Editor二.在Inspector定制指定类型在所有 Inspector 中的显示下面DEMO对比一个类在Inspector中得默认显示和定制化显示默认显示的代码using System; using UnityEngine; public enum IngredientUnit { Spoon, Cup, Bowl, Piece } //用于让自定义类在Inspector中显示 [Serializable] public class Ingredient { public string name; public int amount 1; public IngredientUnit unit; } public class Recipe : MonoBehaviour { public Ingredient potionResult; public Ingredient[] potionIngredients; }2.1 特性[CustomPropertyDrawer]和继承PropertyDrawer通过特性CustomPropertyDrawer和继承PropertyDrawerunity显示Ingredient时将不再使用默认绘制方式而是调用IngredientDrawer的OnGUI方法(约定写法)注意IngredientDrawer的OnGUI 和MonoBehaviour的OnGUI完全不同MonoBehaviour的OnGUI无参是Unity的事件函数每帧调用参与打包构建IngredientDrawer的OnGUI有参(Rect, SerializedProperty, GUIContent)是编辑器扩展专用方法仅在Inspector面板绘制该序列化属性时触发不参与打包构建定制化显示代码(这段代码行数不多但是信息量较大)using UnityEditor; using UnityEngine; //特性CustomPropertyDrawer约定写法 [CustomPropertyDrawer(typeof(Ingredient))] public class IngredientDrawer : PropertyDrawer//继承PropertyDrawer:约定写法 { //OnGUI函数原型约定写法, 定义在父类PropertyDrawer中 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { var a property.type; if (Event.current.type EventType.Repaint) { Debug.Log(label.text, position.x , position.y, position.width, position.height); // EditorGUI.DrawRect(position, new Color(1, 0, 0, 0.3f)); } EditorGUI.BeginProperty(position, label, property); //前缀标签 position EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); //获取缩进 var indent EditorGUI.indentLevel; //设置缩进 EditorGUI.indentLevel 0; //获取矩形区域 var amountRect new Rect(position.x, position.y, 30, position.height); var unitRect new Rect(position.x 35, position.y, 50, position.height); var nameRect new Rect(position.x 90, position.y, position.width - 90, position.height); //绘制字段 EditorGUI.PropertyField(amountRect, property.FindPropertyRelative(amount), GUIContent.none); EditorGUI.PropertyField(unitRect, property.FindPropertyRelative(unit), GUIContent.none); EditorGUI.PropertyField(nameRect, property.FindPropertyRelative(name), GUIContent.none); //还原缩进 EditorGUI.indentLevel indent; EditorGUI.EndProperty(); } }默认显示定制化显示2.2OnGUI的Rect参数通过选中Ingredient类所在脚本所在的对象会执行OnGUI方法加上打印信息和绘制position代表的矩形区域来看效果public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { Debug.Log(label.text,Event.current.type, position.x , position.y, position.width, position.height); EditorGUI.DrawRect(position, new Color(1, 0, 0, 0.3f)); }这里给出结论OnGUI会多次执行其中position.x和position.y为0的时候是处于计算布局阶段(Layout)当处于repaint阶段时Rect所代表的矩形才是真正绘制区域2.3 通过EventType过滤阶段无论MonoBehaviour的无参OnGUI或 PropertyDrawer的有参OnGUI都可能因为各种事件执行。若需要将逻辑放在需要的事件中可用if (Event.current.type EventType.XX)判断这么做还可优化性能仅在MouseDown事件打印void OnGUI() { if (Event.current.type EventType.MouseDown) { Debug.Log(Mouse Down.); } }仅在Repaint事件执行代码public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { if (Event.current.type EventType.Repaint) { EditorGUI.DrawRect(position, new Color(1, 0, 0, 0.3f)); } }参考EventType2.4 EditorGUI.BeginProperty官方说明BeginProperty和EndProperty自动处理默认标签预制件覆盖的粗体字体恢复到预制件右键菜单如果多对象编辑时属性值不同则将showMixedValue设为true自己理解BeginProperty是让Inspector自定义控件获得Inspector原生控件能力(最主要是右键菜单)。实际开发中先无需加BeginProperty开发第一版开发完后哪些控件缺原生功能再调用BeginProperty往上加编辑器扩展创建的控件若支持右击菜单要反应过来其内部调用了BeginPropertyEditorGUI.BeginProperty(position, label, property); //... EditorGUI.EndProperty();对于判断什么情况下要调用BeginProperty至关重要首先看下源码BeginProperty内部调用BeginPropertyInternal在EditorGUI中搜索这两个函数有若干处调用也就是有的控件内部会调用BeginProperty无需重复调用下面列出两种需要加BeginProperty的情况1.创建控件的函数签名中没有SerializedProperty参数调用BeginProperty时需要传SerializedProperty类型参数因此这种情况函数内部由于没有获取SerializedProperty参数一定不会调用BeginProperty反之若函数签名中有 SerializedProperty参数其内部往往会调用BeginProperty。2.多个控件放一行共享一个主标签这时无需看控件的函数签名了因为需要BeginProperty来修饰主标签以及让其中控件被视作整体2.5 参数SerializedPropertyOnGUI的property的类型是SerializedProperty它是上下文对象。可将其理解为序列化句柄或序列化访问器如果翻译成序列化属性容易和C#的get/set属性概念混淆两者没有关联。类型名称type类型枚举propertyType字段名name获取子属性(耗性能)SerializedProperty levelProp property.FindPropertyRelative(level);修改值后生效property.serializedObject.ApplyModifiedProperties();2.6 GUIUtility.GetControlID获取unity分配的控件唯一id用来控制该控件是否接受某些事件如键盘tab切换控件不接受事件GUIUtility.GetControlID(FocusType.Passive)接受事件GUIUtility.GetControlID(FocusType.Keyboard)2.7 EditorGUI.PrefixLabel绘制一个前缀标签totalPosition整个控件的Rectid通过GUIUtility.GetControlID(FocusType.Passive)获取的控件idlabel:OnGUI的参数label返回值控件剩余可用区域public static Rect PrefixLabel(Rect totalPosition, int id, GUIContent label)2.7 EditorGUI.indentLevel用来控制缩进常见写法EditorGUI.indentLevel; EditorGUI.indentLevel--; //EditorGUI.indentLevel是全局静态属性所以在修改它时必须保存和恢复 var indent EditorGUI.indentLevel; EditorGUI.indentLevel 0; //code here EditorGUI.indentLevel indent;2.8 EditorGUI.PropertyField用于在编辑器中为SerializedProperty创建一个字段。var amountRect new Rect(position.x, position.y, 30, position.height); EditorGUI.PropertyField(amountRect, property.FindPropertyRelative(amount), GUIContent.none);2.9 PropertyAttribute类自定义特性继承PropertyAttribute后可以与ProperyDrawer的子类关联起来用于控制脚本变量在Inspector中如何显示using UnityEditor; using UnityEngine; public class MyRangeAttribute : PropertyAttribute { public float min; public float max; public MyRangeAttribute(float min, float max) { this.min min; this.max max; } } [CustomPropertyDrawer(typeof(MyRangeAttribute))] public class RangeDrawer : PropertyDrawer { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { MyRangeAttribute range (MyRangeAttribute)attribute; if (property.propertyType SerializedPropertyType.Float) EditorGUI.Slider(position, property, range.min, range.max, label); else if (property.propertyType SerializedPropertyType.Integer) EditorGUI.IntSlider(position, property, (int) range.min, (int) range.max, label); else EditorGUI.LabelField(position, label.text, Use MyRange with float or int.); } }using System; using UnityEngine; public class Recipe : MonoBehaviour { [Range(0f,10f)] public float speed 0f; [MyRange(0f,10f)] public int age 0; [MyRange(0f,10f)] public bool isNew false; }三.在Inspector中自定义整个组件3.1 定制组件基础下面demo演示脚本LookAtPoint定制化前后的显示对比using System; using UnityEngine; [ExecuteInEditMode] public class LookAtPoint : MonoBehaviour { public Vector3 lookAtPoint Vector3.zero; public void Update() { transform.LookAt(lookAtPoint); } }LookAtPoint对应的编辑器脚本LookAtPointEditorusing UnityEngine; using UnityEditor; [CustomEditor(typeof(LookAtPoint))] public class LookAtPointEditor : Editor { public override void OnInspectorGUI() { } }特性CustomEditor用于绑定编辑器类和MonoBehaviour从Editor类继承表示这个类用于定制inspectorOnInspectorGUI:固定写法注意要加ovveride:通常[CustomEditor]:EditorOnInspectorGUI一起使用public override void OnInspectorGUI()前后3.2 OnEnableOnDisable接下来看一个更复杂的demo选中LookAtPoint所在的gameObject时执行LookAtPointEditor的OnEnable取消选中该gameObject执行OnDisableusing UnityEngine; using UnityEditor; [CustomEditor(typeof(LookAtPoint))] [CanEditMultipleObjects] public class LookAtPointEditor : Editor { SerializedProperty lookAtPoint; void OnEnable() { lookAtPoint serializedObject.FindProperty(lookAtPoint); Debug.Log(#OnEnable); } void OnDisable() { Debug.Log(#OnDisable); } public override void OnInspectorGUI() { serializedObject.Update(); EditorGUILayout.PropertyField(lookAtPoint); if (lookAtPoint.vector3Value.y (target as LookAtPoint).transform.position.y) { EditorGUILayout.LabelField((Above this object)); } if (lookAtPoint.vector3Value.y (target as LookAtPoint).transform.position.y) { EditorGUILayout.LabelField((Below this object)); } serializedObject.ApplyModifiedProperties(); } public void OnSceneGUI() { var t (target as LookAtPoint); EditorGUI.BeginChangeCheck(); Vector3 pos Handles.PositionHandle(t.lookAtPoint, Quaternion.identity); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(target, Move point); t.lookAtPoint pos; t.Update(); } } }3.3 Editor.targettarget是[CustomEditor(typeof(A))]中A的对象引用使用时需要进行类型转换之后可以访问类A中的public 成员var t (target as LookAtPoint);3.4 Editor.serializedObjectSerializedObject是用来读写对象(MonoBehaviour)可序列化字段的工具类通过SerializedObject修改字段会自动支持撤销操作、显示出Scene的*表示出现改动、发生预制体覆盖时Inspector中会正确显示。3.4.1 serializedObject.FindProperty通过属性名称获取SerializedProperty类型的序列化属性public SerializedProperty FindProperty(string propertyPath);3.4.2 serializedObject.Update为了同步SerializedProperty的数据来看下面这个demo(将官方demo简化)public class SerializeObjectUpdateMB : MonoBehaviour { public int m_Field 1; [MenuItem(Example/SerializedObject Update (MonoBehaviour))] static void UpdateExample() { var monoBehaviour FindObjectOfTypeSerializeObjectUpdateMB(); if (monoBehaviour null) { Debug.LogError(No SerializeObjectUpdateMB component found in the scene!); return; } using (var serializedObject new SerializedObject(monoBehaviour)) { SerializedProperty sp serializedObject.FindProperty(m_Field); monoBehaviour.m_Field 5; Debug.Log(before Update:sp.intValue); serializedObject.Update(); Debug.Log(after Update:sp.intValue); } } }常见做法是在OnInspectorGUI下第一行加上serializedObject.Update();但是实测不加未能出现不同步的情况(在inspector中改值和通过代码改值)3.4.3 serializedObject.ApplyModifiedProperties通过代码或inspector改的序列化属性需要调用ApplyModifiedProperties才会反馈在inspector上3.5 OnSceneGUI负责在Scene视图中绘制交互元素调用机制和OnInspectorGUI一样选中关联的gameObject时调用选中其他gameObject时停止调用3.6 EditorGUI.BeginChangeCheck和EditorGUI.EndChangeCheckBeginChangeCheck与EndChangeCheck成对使用用来检测他们之间的GUI的状态变化若发生变化EndChangeCheck返回true常见写法EditorGUI.BeginChangeCheck(); //GUI code here if (EditorGUI.EndChangeCheck()) { Debug.Log(** EndChangeCheck true); }3.7 Handles.PositionHandle在一个点绘制一个位置控制控件Handles类用于在Scene视图绘制3D GUI控件3.8 Undo.RecordObject记录 RecordObject 函数之后对对象所做的任何更改以便撤销。注意必须把Undo.RecordObject放在修改对象代码的前面3.9 特性[ExecuteInEditMode]加了特性[ExecuteInEditMode]的MonoBehaviour在编辑模式下也会执行3.10 特性[CanEditMultipleObjects][CanEditMultipleObjects]可以选定多个挂了相同脚本的对象并编译放在继承Editor脚本上面四.创建树形UIunity提供了编辑器中用于创建树形控件的类TreeView、TreeViewState、TreeViewIte4.1TreeView规范创建一个继承TreeView的类SimpleTreeView构造函数中要调用父类的1参构造函数:base(treeViewState)不加的化会报错因为TreeView没有无参构造函数构造函数中调用Reload固定写法不然BuildRoot不会调用每次调用Reload都会调用BuildRoot一次BuildRootTreeView中的抽象函数SimpleTreeView必须实现在此创建树形结构SetupParentsAndChildrenFromDepths用已设置的顺序和深度值来初始化所有行的通用方法OnGUITreeView创建完成后需要调用OnGUI(Rect rect)在rect区域显示TreeView4.2 TreeViewItemTreeViewItem包含有关单个项的数据代表一行TreeView 有一个隐藏的根TreeViewItemTreeViewItem必须以唯一的整数 ID(用于查找项、选择状态、展开状态)进行构造。depth属性表示视觉缩进4.3 TreeViewStateTreeViewState 包含 TreeView 的可序列化状态信息可在EditorWindow中持有并当作参数传给TreeView的构造函数demo:SimpleTreeView.csusing UnityEditor.IMGUI.Controls; using System.Collections.Generic; public class SimpleTreeView : TreeView { public SimpleTreeView(TreeViewState treeViewState): base(treeViewState) { Reload(); } protected override TreeViewItem BuildRoot () { var root new TreeViewItem {id 0, depth -1, displayName Root}; var allItems new ListTreeViewItem { new TreeViewItem {id 1, depth 0, displayName Animals}, new TreeViewItem {id 2, depth 1, displayName Mammals}, new TreeViewItem {id 3, depth 2, displayName Tiger}, new TreeViewItem {id 4, depth 2, displayName Elephant}, new TreeViewItem {id 5, depth 2, displayName Okapi}, new TreeViewItem {id 6, depth 2, displayName Armadillo}, new TreeViewItem {id 7, depth 1, displayName Reptiles}, new TreeViewItem {id 8, depth 2, displayName Crocodile}, new TreeViewItem {id 9, depth 2, displayName Lizard}, }; SetupParentsAndChildrenFromDepths (root, allItems); return root; } }SimpleTreeViewWindow.csusing System.Collections.Generic; using UnityEngine; using UnityEditor.IMGUI.Controls; using UnityEditor; class SimpleTreeViewWindow : EditorWindow { [SerializeField] TreeViewState m_TreeViewState; SimpleTreeView m_SimpleTreeView; void OnEnable() { if (m_TreeViewState null) m_TreeViewState new TreeViewState (); m_SimpleTreeView new SimpleTreeView(m_TreeViewState); } void OnGUI() { m_SimpleTreeView.OnGUI(new Rect(0, 0, position.width, position.height)); } [MenuItem (TreeView Examples/Simple Tree Window)] static void ShowWindow () { var window GetWindowSimpleTreeViewWindow (); window.titleContent new GUIContent (My Window); window.Show (); } }五. IMGUI用于编辑器扩展的类EditorGUIEditorGUILayoutEditorStylesSerializedObjectSerializedPropertyGUIContentPropertyAttributePropertyDrawerEditorEditorWindowUndoHandlesTreeViewTreeViewStateTreeViewItem
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门