Unity 编辑器开发实战【Custom Editor】- FSM Editor

简介: Unity 编辑器开发实战【Custom Editor】- FSM Editor

本文介绍如何为FSM有限状态机模块实现一个自定义编辑器面板

image.gif

首先,自定义一个编辑器面板,需要用到Attribute:CustomEditor,参数传入目标类的类型,代码如下:

usingUnityEngine;
usingUnityEditor;
namespaceSK.Framework{
    [CustomEditor(typeof(FSMMaster))]
publicclassFSMEditor : Editor    {
    }
}

image.gif

自定义编辑器类继承Editor类后,重写OnInspectorGUI函数来自定义Inspector面板,例如添加一个Label文本:

usingUnityEngine;
usingUnityEditor;
namespaceSK.Framework{
    [CustomEditor(typeof(FSMMaster))]
publicclassFSMEditor : Editor    {
publicoverridevoidOnInspectorGUI()
        {
GUILayout.Label("有限状态机");
        }
    }
}

image.gif

image.gif

绘制该面板我们需要FSM Master中的状态机列表的信息,是一个私有的StateMachine类型的列表,因此需要通过反射去获取:

image.gif

usingSystem.Reflection;
usingSystem.Collections.Generic;
usingUnityEngine;
usingUnityEditor;
namespaceSK.Framework{
    [CustomEditor(typeof(FSMMaster))]
publicclassFSMEditor : Editor    {
privateList<StateMachine>machines;
publicoverridevoidOnInspectorGUI()
        {
//程序未在运行状态则退出if (!Application.isPlaying) return;
if (machines==null)
            {
//通过反射获取状态机列表machines=typeof(FSMMaster).GetField("machines", BindingFlags.Instance|BindingFlags.NonPublic)
                    .GetValue(FSMMaster.Instance) asList<StateMachine>;
            }
        }
    }
}

image.gif

有了状态机的信息后,通过EditorGUILayout类中的Popup去列举所有的状态机,其中需要传入一个string类型数组,即列举的内容,我们声明一个string类型数组来存储所有状态机的名称,使用一个int类型字段来表示当前选中的状态机的索引:

image.gif

usingSystem.Linq;
usingSystem.Reflection;
usingSystem.Collections.Generic;
usingUnityEngine;
usingUnityEditor;
namespaceSK.Framework{
    [CustomEditor(typeof(FSMMaster))]
publicclassFSMEditor : Editor    {
privateList<StateMachine>machines;
privateintcurrentMachineIndex;
privatestring[] machinesName;
publicoverridevoidOnInspectorGUI()
        {
//程序未在运行状态则退出if (!Application.isPlaying) return;
if (machines==null)
            {
//通过反射获取状态机列表machines=typeof(FSMMaster).GetField("machines", BindingFlags.Instance|BindingFlags.NonPublic)
                    .GetValue(FSMMaster.Instance) asList<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);
            }
        }
    }
}

image.gif

image.gif

接下来获取状态机中的所有状态信息, 状态使用一个IState类型的列表存储,修饰符为protected,因此也通过反射去获取:

image.gif

usingSystem.Linq;
usingSystem.Reflection;
usingSystem.Collections.Generic;
usingUnityEngine;
usingUnityEditor;
namespaceSK.Framework{
    [CustomEditor(typeof(FSMMaster))]
publicclassFSMEditor : Editor    {
privateList<StateMachine>machines;
privateFieldInfostatesFieldInfo;
privateintcurrentMachineIndex;
privatestring[] machinesName;
publicoverridevoidOnInspectorGUI()
        {
//程序未在运行状态则退出if (!Application.isPlaying) return;
if (machines==null)
            {
//通过反射获取状态机列表machines=typeof(FSMMaster).GetField("machines", BindingFlags.Instance|BindingFlags.NonPublic)
                    .GetValue(FSMMaster.Instance) asList<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);
varcurrentMachine=machines[currentMachineIndex];
//获取当前状态机的状态列表varstates=statesFieldInfo.GetValue(currentMachine) asList<IState>;
            }
        }
    }
}

image.gif

有了状态的列表信息后,for循环遍历列表,绘制每一个状态的名称,使用不同的GUIStyle来区分该状态是否为状态机的当前状态,如果不是,则提供一个切换到该状态的Button按钮:

usingSystem.Linq;
usingSystem.Reflection;
usingSystem.Collections.Generic;
usingUnityEngine;
usingUnityEditor;
namespaceSK.Framework{
    [CustomEditor(typeof(FSMMaster))]
publicclassFSMEditor : Editor    {
privateList<StateMachine>machines;
privateFieldInfostatesFieldInfo;
privateintcurrentMachineIndex;
privatestring[] machinesName;
publicoverridevoidOnInspectorGUI()
        {
//程序未在运行状态则退出if (!Application.isPlaying) return;
if (machines==null)
            {
//通过反射获取状态机列表machines=typeof(FSMMaster).GetField("machines", BindingFlags.Instance|BindingFlags.NonPublic)
                    .GetValue(FSMMaster.Instance) asList<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);
varcurrentMachine=machines[currentMachineIndex];
//获取当前状态机的状态列表varstates=statesFieldInfo.GetValue(currentMachine) asList<IState>;
GUILayout.BeginVertical("Box");
for (inti=0; i<states.Count; i++)
                {
varstate=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();
            }
        }
    }
}

image.gif

image.gif

除此之外,我们还希望在状态机下面添加一排菜单,绘制三个按钮,分别实现状态机中的切换到下一状态、切换到上一状态、切换到空状态的功能,通过GUILayout类中的BeginHorizontal和EndHorizontal将这三个按钮绘制到一排:

privateclassGUIContents{
publicstaticGUIContentswitch2Next=newGUIContent("Next", "切换到下一状态");
publicstaticGUIContentswitch2Last=newGUIContent("Last", "切换到上一状态");
publicstaticGUIContentswitch2Null=newGUIContent("Null", "切换到空状态 (退出当前状态)");
}

image.gif

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();

image.gif

最终完整代码:

usingSystem.Linq;
usingSystem.Reflection;
usingSystem.Collections.Generic;
usingUnityEngine;
usingUnityEditor;
namespaceSK.Framework{
    [CustomEditor(typeof(FSMMaster))]
publicclassFSMEditor : Editor    {
privateclassGUIContents        {
publicstaticGUIContentswitch2Next=newGUIContent("Next", "切换到下一状态");
publicstaticGUIContentswitch2Last=newGUIContent("Last", "切换到上一状态");
publicstaticGUIContentswitch2Null=newGUIContent("Null", "切换到空状态 (退出当前状态)");
        }
privateList<StateMachine>machines;
privateFieldInfostatesFieldInfo;
privateintcurrentMachineIndex;
privatestring[] machinesName;
publicoverridevoidOnInspectorGUI()
        {
//程序未在运行状态则退出if (!Application.isPlaying) return;
if (machines==null)
            {
//通过反射获取状态机列表machines=typeof(FSMMaster).GetField("machines", BindingFlags.Instance|BindingFlags.NonPublic)
                    .GetValue(FSMMaster.Instance) asList<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);
varcurrentMachine=machines[currentMachineIndex];
//获取当前状态机的状态列表varstates=statesFieldInfo.GetValue(currentMachine) asList<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 (inti=0; i<states.Count; i++)
                {
varstate=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();
            }
        }
    }
}

image.gif

目录
相关文章
|
1月前
|
前端开发
业余时间开发了个海报编辑器
为了满足撰写博客或录制教程视频时对高质量海报的需求,我利用业余时间开发了一款海报编辑器。第一版功能简单,支持固定尺寸、黑底白字的标题。后来经过优化,增加了背景图、模糊效果、文字样式调整等功能,使海报更具吸引力。目前该编辑器已上线,欢迎大家试用并反馈。[访问海报编辑器](https://tool.share888.top/#/poster)
74 6
业余时间开发了个海报编辑器
|
1月前
|
缓存 API 开发工具
有关Unity使用Rider编辑器无法弹出代码提示的有效解决方法
【11月更文挑战第13天】在 Unity 中使用 Rider 编辑器时,若遇到代码提示无法弹出的问题,可以通过检查 Rider 设置(如自动补全选项、Unity 插件安装、索引设置)、Unity 项目设置(如解决方案正确关联、脚本导入设置)以及环境和依赖关系(如 .NET SDK 版本兼容性、Unity 和 Rider 版本兼容性)等方面进行排查和解决。
|
3月前
|
JavaScript 前端开发 API
vue3 v-md-editor markdown编辑器(VMdEditor)和预览组件(VMdPreview )的使用
本文介绍了如何在Vue 3项目中使用v-md-editor组件库来创建markdown编辑器和预览组件。文章提供了安装步骤、如何在main.js中进行全局配置、以及如何在页面中使用VMdEditor和VMdPreview组件的示例代码。此外,还提供了一个完整示例的链接,包括编辑器和预览组件的使用效果和代码。
vue3 v-md-editor markdown编辑器(VMdEditor)和预览组件(VMdPreview )的使用
|
2月前
|
运维 Java Linux
【运维基础知识】掌握VI编辑器:提升你的Java开发效率
本文详细介绍了VI编辑器的常用命令,包括模式切换、文本编辑、搜索替换及退出操作,帮助Java开发者提高在Linux环境下的编码效率。掌握这些命令,将使你在开发过程中更加得心应手。
39 2
|
4月前
|
图形学 开发者 存储
超越基础教程:深度拆解Unity地形编辑器的每一个隐藏角落,让你的游戏世界既浩瀚无垠又细节满满——从新手到高手的全面技巧升级秘籍
【8月更文挑战第31天】Unity地形编辑器是游戏开发中的重要工具,可快速创建复杂多变的游戏环境。本文通过比较不同地形编辑技术,详细介绍如何利用其功能构建广阔且精细的游戏世界,并提供具体示例代码,展示从基础地形绘制到植被与纹理添加的全过程。通过学习这些技巧,开发者能显著提升游戏画面质量和玩家体验。
193 3
|
4月前
|
开发者 图形学 开发工具
Unity编辑器神级扩展攻略:从批量操作到定制Inspector界面,手把手教你编写高效开发工具,解锁编辑器隐藏潜能
【8月更文挑战第31天】Unity是一款强大的游戏开发引擎,支持多平台发布与高度可定制的编辑器环境。通过自定义编辑器工具,开发者能显著提升工作效率。本文介绍如何使用C#脚本扩展Unity编辑器功能,包括批量调整游戏对象位置、创建自定义Inspector界面及项目统计窗口等实用工具,并提供具体示例代码。理解并应用这些技巧,可大幅优化开发流程,提高生产力。
440 1
|
3月前
一款非常棒的十六进制编辑器 —— 010 Editor
一款非常棒的十六进制编辑器 —— 010 Editor
|
5月前
|
开发工具
vi编辑器,现在vi\vim是文本文件进行编辑的最佳选择,Vim是vi的加强的版本,兼容vi的所有指令,vim编辑器有三种工作模式,一开始进入的是命令模式,命令模式i是插入的意思,两下y+p复制内容
vi编辑器,现在vi\vim是文本文件进行编辑的最佳选择,Vim是vi的加强的版本,兼容vi的所有指令,vim编辑器有三种工作模式,一开始进入的是命令模式,命令模式i是插入的意思,两下y+p复制内容
|
6月前
|
开发工具
Vim 编辑器:高效文本编辑的瑞士军刀
**Vim 概览:** Vim 是一个功能丰富的文本编辑器,以其高度可定制性著称。文章介绍了 Vim 的高效使用技巧,包括快捷打开文件、命令行模式下的常用命令、查找与替换、删除和复制文本。还讨论了配置 `.vimrc` 文件以自定义设置,如改变 leader 键、设置缩进和高亮,并展示了安装插件如 vim-airline 和 vim-snazzy 的方法。通过这些技巧,用户能提升 Vim 使用效率。
73 5
|
6月前
|
Ubuntu 搜索推荐 Linux
Linux的Vim编辑器详解
Linux的Vim编辑器详解