今天博主想和大家分享的是Unity3d场景编辑器的扩展开发,相关的话题我们在Unity3d游戏开发之编辑器扩展程序开发实例这篇文章中我们已经有所涉及,今天博主想特别针对场景编辑器的扩展开发来进行下深入研究。对于一个场景编辑器来说,它主要的作用是3D场景视图中实时显示、输入反馈和相关信息的更新。在Unity3D中提供了Editor、EditorWindow、GUILayout、EditorGUILayout、GUIUtility、EditorGUIUtility、Handles、Event等来完成这些工作。其中基于EditorWindow的这种扩展方式我们已经研究过了,这种扩展方式拥有自己的独立窗口使用OnGUI方法进行界面的绘制。今天我们想说的是基于Editor的这种扩展方式,这种扩展方式只能针对脚本,从脚本内容在Inspector里的显示布局到变量在Scene视图的可视化编辑,它都可以完全胜任。这里特别想说的是Handles和Event这两个类,这两个类分别提供了3D显示和输入反馈的功能,我们下面就来学习如何使用这些类来扩展Unity3D的场景编辑器。
创建一个扩展的Transform组件
Transform是Unity3D中一个基本的组件,下面我们来创建一个扩展的Transform组件,该组件可以对游戏体的坐标、旋转、缩放进行重置。首先,我们创建一个ExtendTransform的类,该类继承自Editor类:
using UnityEngine; using System.Collections; using UnityEditor; [CustomEditor(typeof(Transform),true)] public class ExtendTransform : Editor { /// <summary> /// Position属性 /// </summary> private SerializedProperty mPos; /// <summary> /// Scale属性 /// </summary> private SerializedProperty mScale; void OnEnable() { mPos = serializedObject.FindProperty("m_LocalPosition"); mScale = serializedObject.FindProperty("m_LocalScale") ; } /// <summary> /// Inspector相关GUI函数 /// </summary> public override void OnInspectorGUI() { EditorGUIUtility.labelWidth = 15; //获取最新的可序列化对象 serializedObject.Update(); //绘制物体的坐标、旋转和缩放 DrawPosition(); DrawRotate(); DrawScale(); //更新可序列化对象的属性 serializedObject.ApplyModifiedProperties(); } /// <summary> /// 绘制位置 /// </summary> private void DrawPosition() { GUILayout.BeginHorizontal(); { bool Reset = GUILayout.Button("P", GUILayout.Width(20f)); EditorGUILayout.LabelField("Position"); EditorGUILayout.PropertyField(mPos.FindPropertyRelative("x")); EditorGUILayout.PropertyField(mPos.FindPropertyRelative("y")); EditorGUILayout.PropertyField(mPos.FindPropertyRelative("z")); if(Reset) mPos.vector3Value = Vector3.zero; } GUILayout.EndHorizontal(); } /// <summary> /// 绘制旋转 /// </summary> private void DrawRotate() { Vector3 eulerAngles = ((Transform)target).eulerAngles; GUILayout.BeginHorizontal(); { bool Reset = GUILayout.Button("R", GUILayout.Width(20f)); EditorGUILayout.LabelField("Rotation", GUILayout.Width(70f)); EditorGUILayout.LabelField("X", GUILayout.Width(13f)); float angleX=EditorGUILayout.FloatField(eulerAngles.x, GUILayout.Width(56f)); EditorGUILayout.LabelField("Y", GUILayout.Width(13f)); float angleY = EditorGUILayout.FloatField(eulerAngles.y, GUILayout.Width(56f)); EditorGUILayout.LabelField("Z", GUILayout.Width(13f)); float angleZ = EditorGUILayout.FloatField(eulerAngles.z, GUILayout.Width(56f)); ((Transform)target).eulerAngles = new Vector3(angleX, angleY, angleZ); if(Reset) { eulerAngles = Vector3.zero; ((Transform)target).eulerAngles = Vector3.zero; } } GUILayout.EndHorizontal(); } /// <summary> /// 绘制缩放 /// </summary> private void DrawScale() { GUILayout.BeginHorizontal(); { bool Reset = GUILayout.Button("S", GUILayout.Width(20f)); EditorGUILayout.LabelField("Scale"); EditorGUILayout.PropertyField(mScale.FindPropertyRelative("x")); EditorGUILayout.PropertyField(mScale.FindPropertyRelative("y")); EditorGUILayout.PropertyField(mScale.FindPropertyRelative("z")); if (Reset) mScale.vector3Value = Vector3.one; } GUILayout.EndHorizontal(); } }
首先我们注意到ExtendTransform继承自Editor,这是我们开发这类编辑器扩展的第一个前提。其次我们注意到在该类的声明位置有这样一个标记:
[CustomEditor(typeof(Transform),true)]
该标记表明我们这个编辑器扩展是针对Transform组件进行扩展的,即当物体存在Tranform组件时会在编辑器中响应这个编辑器扩展程序。我们在这个编辑器扩展程序中都做了哪些事情呢?第一,我们实现了OnEnable()方法,该方法相当于一个初始化的方法;第二,我们重写了OnOnInspectorGUI()方法,该方法将覆盖默认的Inspector窗口外观。
/// <summary> /// 定义一个可序列化类 /// </summary> [System.Serializable] public class ExampleClass { [SerializeField] public int ID; [SerializeField] public string Name; [SerializeField] public Vector3[] Points; private bool editable = false; } /// <summary> /// 定义一个简单的脚本 /// </summary> public class ExampleScript : MonoBehaviour { public ExampleClass Example; }
- OnInspectorGUI():该方法可对Inspector窗口面板进行扩展或者重写,比如我们可以通过DrawDefaultInspector()方法来绘制默认Inspector窗口面板然后在此基础上使用GUILayout或者EditorGUILayout等辅助类进行自定义的绘制。在这个示例中我们对整个面板进行了重写,值得注意的是为了让Inspector窗口面板正常工作,如果要重绘该窗口请确保对该方法进行覆盖。
-
OnSceneGUI():该方法可对场景视图进行绘制,在实际的使用中可以配合Handles类和Event类来进行网格编辑、地形绘制或高级Gizmos等方面的工作。在本文的第二个示例中,我们将利用这一特性来编写一个用于NPC寻路的路径节点编辑工具。
对第一个示例的总结
using UnityEngine; using System.Collections; using UnityEditor; [CustomEditor(typeof(PatrolNPC))] public class PatrolPathEditor : Editor { /// <summary> /// 寻路节点 /// </summary> private Vector3[] paths; /// <summary> /// 显示寻路信息的GUI /// </summary> private GUIStyle style=new GUIStyle(); /// <summary> /// 初始化 /// </summary> void OnEnable() { //获取当前NPC的寻路路径 paths = ((PatrolNPC)target).Paths; //初始化GUIStyle style.fontStyle = FontStyle.Normal; style.fontSize = 15; } void OnSceneGUI() { //获取当前NPC的寻路路径 paths = ((PatrolNPC)serializedObject.targetObject).Paths; //设置节点的颜色为红色 Handles.color = Color.red; if(paths.Length <= 0 || paths.Length<2) return; //在场景中绘制每一个寻路节点 //可以在场景中编辑节点并将更新至对应的NPC for (int i = 0; i < paths.Length; i++) { paths = Handles.PositionHandle(paths, Quaternion.identity); Handles.SphereCap(i, paths, Quaternion.identity, 0.25f); Handles.Label(paths, "PathPoint" + i, style); if (i < paths.Length && i + 1 < paths.Length) { Handles.DrawLine(paths, paths[i + 1]); } } } }