Unity 编辑器开发实战【Custom Editor】- FSM Editor
本文介绍如何为FSM有限状态机模块实现一个自定义编辑器面板,FSM的详细代码在上一篇文章中有介绍,链接地址: 在Unity中构建FSM有限状态机 下面是最终效果:
首先,自定义一个编辑器面板,需要用到Attribute:CustomEditor,参数传入目标类的类型,代码如下:
-
using UnityEngine;
-
using UnityEditor;
-
-
namespace SK.Framework
-
{
-
[CustomEditor(typeof(FSMMaster))]
-
public class FSMEditor : Editor
-
{
-
-
}
-
}
自定义编辑器类继承Editor类后,重写OnInspectorGUI函数来自定义Inspector面板,例如添加一个Label文本:
-
using UnityEngine;
-
using UnityEditor;
-
-
namespace SK.Framework
-
{
-
[CustomEditor(typeof(FSMMaster))]
-
public class FSMEditor : Editor
-
{
-
public override void OnInspectorGUI()
-
{
-
GUILayout.Label("有限状态机");
-
}
-
}
-
}
绘制该面板我们需要FSM Master中的状态机列表的信息,是一个私有的StateMachine类型的列表,因此需要通过反射去获取:
-
using System.Reflection;
-
using System.Collections.Generic;
-
-
using UnityEngine;
-
using UnityEditor;
-
-
namespace SK.Framework
-
{
-
[CustomEditor(typeof(FSMMaster))]
-
public class FSMEditor : Editor
-
{
-
private List<StateMachine> machines;
-
-
public override void OnInspectorGUI()
-
{
-
//程序未在运行状态则退出
-
if (!Application.isPlaying) return;
-
-
if (machines == null)
-
{
-
//通过反射获取状态机列表
-
machines = typeof(FSMMaster).GetField("machines", BindingFlags.Instance | BindingFlags.NonPublic)
-
.GetValue(FSMMaster.Instance) as List<StateMachine>;
-
}
-
}
-
}
-
}
有了状态机的信息后,通过EditorGUILayout类中的Popup去列举所有的状态机,其中需要传入一个string类型数组,即列举的内容,我们声明一个string类型数组来存储所有状态机的名称,使用一个int类型字段来表示当前选中的状态机的索引:
-
using System.Linq;
-
using System.Reflection;
-
using System.Collections.Generic;
-
-
using UnityEngine;
-
using UnityEditor;
-
-
namespace SK.Framework
-
{
-
[CustomEditor(typeof(FSMMaster))]
-
public class FSMEditor : Editor
-
{
-
private List<StateMachine> machines;
-
private int currentMachineIndex;
-
private string[] machinesName;
-
-
public override void OnInspectorGUI()
-
{
-
//程序未在运行状态则退出
-
if (!Application.isPlaying) return;
-
-
if (machines == null)
-
{
-
//通过反射获取状态机列表
-
machines = typeof(FSMMaster).GetField("machines", BindingFlags.Instance | BindingFlags.NonPublic)
-
.GetValue(FSMMaster.Instance) as List<StateMachine>;
-
}
-
-
//当状态机名称数组为空(初始化) 或数量与状态机数量不等时(状态机列表发生变化)
-
if (machinesName == null || machines.Count != machinesName.Length)
-
{
-
//重置当前状态机索引数值
-
currentMachineIndex = 0;
-
//重新获取状态机名称数组
-
machinesName = machines.Select(m => m.Name).ToArray();
-
}
-
-
if (machines.Count > 0)
-
{
-
currentMachineIndex = EditorGUILayout.Popup("状态机:", currentMachineIndex, machinesName);
-
}
-
}
-
}
-
}
接下来获取状态机中的所有状态信息, 状态使用一个IState类型的列表存储,修饰符为protected,因此也通过反射去获取:
-
using System.Linq;
-
using System.Reflection;
-
using System.Collections.Generic;
-
-
using UnityEngine;
-
using UnityEditor;
-
-
namespace SK.Framework
-
{
-
[CustomEditor(typeof(FSMMaster))]
-
public class FSMEditor : Editor
-
{
-
private List<StateMachine> machines;
-
private FieldInfo statesFieldInfo;
-
private int currentMachineIndex;
-
private string[] machinesName;
-
-
public override void OnInspectorGUI()
-
{
-
//程序未在运行状态则退出
-
if (!Application.isPlaying) return;
-
-
if (machines == null)
-
{
-
//通过反射获取状态机列表
-
machines = typeof(FSMMaster).GetField("machines", BindingFlags.Instance | BindingFlags.NonPublic)
-
.GetValue(FSMMaster.Instance) as List<StateMachine>;
-
//获取状态列表字段
-
statesFieldInfo = typeof(StateMachine).GetField("states", BindingFlags.Instance | BindingFlags.NonPublic);
-
}
-
-
//当状态机名称数组为空(初始化) 或数量与状态机数量不等时(状态机列表发生变化)
-
if (machinesName == null || machines.Count != machinesName.Length)
-
{
-
//重置当前状态机索引数值
-
currentMachineIndex = 0;
-
//重新获取状态机名称数组
-
machinesName = machines.Select(m => m.Name).ToArray();
-
}
-
-
if (machines.Count > 0)
-
{
-
currentMachineIndex = EditorGUILayout.Popup("状态机:", currentMachineIndex, machinesName);
-
var currentMachine = machines[currentMachineIndex];
-
//获取当前状态机的状态列表
-
var states = statesFieldInfo.GetValue(currentMachine) as List<IState>;
-
}
-
}
-
}
-
}
有了状态的列表信息后,for循环遍历列表,绘制每一个状态的名称,使用不同的GUIStyle来区分该状态是否为状态机的当前状态,如果不是,则提供一个切换到该状态的Button按钮:
-
using System.Linq;
-
using System.Reflection;
-
using System.Collections.Generic;
-
-
using UnityEngine;
-
using UnityEditor;
-
-
namespace SK.Framework
-
{
-
[CustomEditor(typeof(FSMMaster))]
-
public class FSMEditor : Editor
-
{
-
private List<StateMachine> machines;
-
private FieldInfo statesFieldInfo;
-
private int currentMachineIndex;
-
private string[] machinesName;
-
-
public override void OnInspectorGUI()
-
{
-
//程序未在运行状态则退出
-
if (!Application.isPlaying) return;
-
-
if (machines == null)
-
{
-
//通过反射获取状态机列表
-
machines = typeof(FSMMaster).GetField("machines", BindingFlags.Instance | BindingFlags.NonPublic)
-
.GetValue(FSMMaster.Instance) as List<StateMachine>;
-
//获取状态列表字段
-
statesFieldInfo = typeof(StateMachine).GetField("states", BindingFlags.Instance | BindingFlags.NonPublic);
-
}
-
-
//当状态机名称数组为空(初始化) 或数量与状态机数量不等时(状态机列表发生变化)
-
if (machinesName == null || machines.Count != machinesName.Length)
-
{
-
//重置当前状态机索引数值
-
currentMachineIndex = 0;
-
//重新获取状态机名称数组
-
machinesName = machines.Select(m => m.Name).ToArray();
-
}
-
-
if (machines.Count > 0)
-
{
-
currentMachineIndex = EditorGUILayout.Popup("状态机:", currentMachineIndex, machinesName);
-
var currentMachine = machines[currentMachineIndex];
-
//获取当前状态机的状态列表
-
var states = statesFieldInfo.GetValue(currentMachine) as List<IState>;
-
-
GUILayout.BeginVertical("Box");
-
for (int i = 0; i < states.Count; i++)
-
{
-
var state = states[i];
-
//如果状态为当前状态 使用SelectionRect Style 否则使用IN Title Style进行区分
-
GUILayout.BeginHorizontal(currentMachine.CurrentState == state ? "SelectionRect" : "IN Title");
-
GUILayout.Label(state.Name);
-
-
//如果状态不是当前状态 提供切换到该状态的Button按钮
-
if(currentMachine.CurrentState != state)
-
{
-
if (GUILayout.Button("Switch", GUILayout.Width(50f)))
-
{
-
currentMachine.Switch(state);
-
}
-
}
-
GUILayout.EndHorizontal();
-
}
-
GUILayout.EndVertical();
-
}
-
}
-
}
-
}
除此之外,我们还希望在状态机下面添加一排菜单,绘制三个按钮,分别实现状态机中的切换到下一状态、切换到上一状态、切换到空状态的功能,通过GUILayout类中的BeginHorizontal和EndHorizontal将这三个按钮绘制到一排:
-
private class GUIContents
-
{
-
public static GUIContent switch2Next = new GUIContent("Next", "切换到下一状态");
-
public static GUIContent switch2Last = new GUIContent("Last", "切换到上一状态");
-
public static GUIContent switch2Null = new GUIContent("Null", "切换到空状态 (退出当前状态)");
-
}
-
GUILayout.BeginHorizontal();
-
//提供切换到上一状态的Button按钮
-
if (GUILayout.Button(GUIContents.switch2Last, "ButtonLeft"))
-
{
-
currentMachine.Switch2Last();
-
}
-
//提供切换到下一状态的Button按钮
-
if (GUILayout.Button(GUIContents.switch2Next, "ButtonMid"))
-
{
-
currentMachine.Switch2Next();
-
}
-
//提供切换到空状态的Button按钮
-
if (GUILayout.Button(GUIContents.switch2Null, "ButtonRight"))
-
{
-
currentMachine.Switch2Null();
-
}
-
GUILayout.EndHorizontal();
最终完整代码:
-
using System.Linq;
-
using System.Reflection;
-
using System.Collections.Generic;
-
-
using UnityEngine;
-
using UnityEditor;
-
-
namespace SK.Framework
-
{
-
[CustomEditor(typeof(FSMMaster))]
-
public class FSMEditor : Editor
-
{
-
private class GUIContents
-
{
-
public static GUIContent switch2Next = new GUIContent("Next", "切换到下一状态");
-
public static GUIContent switch2Last = new GUIContent("Last", "切换到上一状态");
-
public static GUIContent switch2Null = new GUIContent("Null", "切换到空状态 (退出当前状态)");
-
}
-
private List<StateMachine> machines;
-
private FieldInfo statesFieldInfo;
-
private int currentMachineIndex;
-
private string[] machinesName;
-
-
public override void OnInspectorGUI()
-
{
-
//程序未在运行状态则退出
-
if (!Application.isPlaying) return;
-
-
if (machines == null)
-
{
-
//通过反射获取状态机列表
-
machines = typeof(FSMMaster).GetField("machines", BindingFlags.Instance | BindingFlags.NonPublic)
-
.GetValue(FSMMaster.Instance) as List<StateMachine>;
-
//获取状态列表字段
-
statesFieldInfo = typeof(StateMachine).GetField("states", BindingFlags.Instance | BindingFlags.NonPublic);
-
}
-
-
//当状态机名称数组为空(初始化) 或数量与状态机数量不等时(状态机列表发生变化)
-
if (machinesName == null || machines.Count != machinesName.Length)
-
{
-
//重置当前状态机索引数值
-
currentMachineIndex = 0;
-
//重新获取状态机名称数组
-
machinesName = machines.Select(m => m.Name).ToArray();
-
}
-
-
if (machines.Count > 0)
-
{
-
currentMachineIndex = EditorGUILayout.Popup("状态机:", currentMachineIndex, machinesName);
-
var currentMachine = machines[currentMachineIndex];
-
//获取当前状态机的状态列表
-
var states = statesFieldInfo.GetValue(currentMachine) as List<IState>;
-
-
GUILayout.BeginHorizontal();
-
//提供切换到上一状态的Button按钮
-
if (GUILayout.Button(GUIContents.switch2Last, "ButtonLeft"))
-
{
-
currentMachine.Switch2Last();
-
}
-
//提供切换到下一状态的Button按钮
-
if (GUILayout.Button(GUIContents.switch2Next, "ButtonMid"))
-
{
-
currentMachine.Switch2Next();
-
}
-
//提供切换到空状态的Button按钮
-
if (GUILayout.Button(GUIContents.switch2Null, "ButtonRight"))
-
{
-
currentMachine.Switch2Null();
-
}
-
GUILayout.EndHorizontal();
-
-
GUILayout.BeginVertical("Box");
-
for (int i = 0; i < states.Count; i++)
-
{
-
var state = states[i];
-
//如果状态为当前状态 使用SelectionRect Style 否则使用IN Title Style进行区分
-
GUILayout.BeginHorizontal(currentMachine.CurrentState == state ? "SelectionRect" : "IN Title");
-
GUILayout.Label(state.Name);
-
-
//如果状态不是当前状态 提供切换到该状态的Button按钮
-
if(currentMachine.CurrentState != state)
-
{
-
if (GUILayout.Button("Switch", GUILayout.Width(50f)))
-
{
-
currentMachine.Switch(state);
-
}
-
}
-
GUILayout.EndHorizontal();
-
}
-
GUILayout.EndVertical();
-
}
-
}
-
}
-
}
文章来源: coderz.blog.csdn.net,作者:CoderZ1010,版权归原作者所有,如需转载,请联系作者。
原文链接:coderz.blog.csdn.net/article/details/122488190
- 点赞
- 收藏
- 关注作者
评论(0)