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

目录
相关文章
|
10天前
|
前端开发
业余时间开发了个海报编辑器
为了满足撰写博客或录制教程视频时对高质量海报的需求,我利用业余时间开发了一款海报编辑器。第一版功能简单,支持固定尺寸、黑底白字的标题。后来经过优化,增加了背景图、模糊效果、文字样式调整等功能,使海报更具吸引力。目前该编辑器已上线,欢迎大家试用并反馈。[访问海报编辑器](https://tool.share888.top/#/poster)
52 6
业余时间开发了个海报编辑器
|
2月前
|
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 )的使用
|
1月前
|
运维 Java Linux
【运维基础知识】掌握VI编辑器:提升你的Java开发效率
本文详细介绍了VI编辑器的常用命令,包括模式切换、文本编辑、搜索替换及退出操作,帮助Java开发者提高在Linux环境下的编码效率。掌握这些命令,将使你在开发过程中更加得心应手。
32 2
|
3月前
|
图形学 开发者 存储
超越基础教程:深度拆解Unity地形编辑器的每一个隐藏角落,让你的游戏世界既浩瀚无垠又细节满满——从新手到高手的全面技巧升级秘籍
【8月更文挑战第31天】Unity地形编辑器是游戏开发中的重要工具,可快速创建复杂多变的游戏环境。本文通过比较不同地形编辑技术,详细介绍如何利用其功能构建广阔且精细的游戏世界,并提供具体示例代码,展示从基础地形绘制到植被与纹理添加的全过程。通过学习这些技巧,开发者能显著提升游戏画面质量和玩家体验。
156 3
|
3月前
|
开发者 图形学 开发工具
Unity编辑器神级扩展攻略:从批量操作到定制Inspector界面,手把手教你编写高效开发工具,解锁编辑器隐藏潜能
【8月更文挑战第31天】Unity是一款强大的游戏开发引擎,支持多平台发布与高度可定制的编辑器环境。通过自定义编辑器工具,开发者能显著提升工作效率。本文介绍如何使用C#脚本扩展Unity编辑器功能,包括批量调整游戏对象位置、创建自定义Inspector界面及项目统计窗口等实用工具,并提供具体示例代码。理解并应用这些技巧,可大幅优化开发流程,提高生产力。
369 1
|
3月前
|
开发者 图形学 Java
揭秘Unity物理引擎核心技术:从刚体动力学到关节连接,全方位教你如何在虚拟世界中重现真实物理现象——含实战代码示例与详细解析
【8月更文挑战第31天】Unity物理引擎对于游戏开发至关重要,它能够模拟真实的物理效果,如刚体运动、碰撞检测及关节连接等。通过Rigidbody和Collider组件,开发者可以轻松实现物体间的互动与碰撞。本文通过具体代码示例介绍了如何使用Unity物理引擎实现物体运动、施加力、使用关节连接以及模拟弹簧效果等功能,帮助开发者提升游戏的真实感与沉浸感。
85 1
|
2月前
一款非常棒的十六进制编辑器 —— 010 Editor
一款非常棒的十六进制编辑器 —— 010 Editor
|
2月前
|
图形学 C++ C#
Unity插件开发全攻略:从零起步教你用C++扩展游戏功能,解锁Unity新玩法的详细步骤与实战技巧大公开
【8月更文挑战第31天】Unity 是一款功能强大的游戏开发引擎,支持多平台发布并拥有丰富的插件生态系统。本文介绍 Unity 插件开发基础,帮助读者从零开始编写自定义插件以扩展其功能。插件通常用 C++ 编写,通过 Mono C# 运行时调用,需在不同平台上编译。文中详细讲解了开发环境搭建、简单插件编写及在 Unity 中调用的方法,包括创建 C# 封装脚本和处理跨平台问题,助力开发者提升游戏开发效率。
227 0
|
2月前
|
图形学 iOS开发 Android开发
从Unity开发到移动平台制胜攻略:全面解析iOS与Android应用发布流程,助你轻松掌握跨平台发布技巧,打造爆款手游不是梦——性能优化、广告集成与内购设置全包含
【8月更文挑战第31天】本书详细介绍了如何在Unity中设置项目以适应移动设备,涵盖性能优化、集成广告及内购功能等关键步骤。通过具体示例和代码片段,指导读者完成iOS和Android应用的打包与发布,确保应用顺利上线并获得成功。无论是性能调整还是平台特定的操作,本书均提供了全面的解决方案。
152 0
|
3月前
|
开发者 图形学 API
从零起步,深度揭秘:运用Unity引擎及网络编程技术,一步步搭建属于你的实时多人在线对战游戏平台——详尽指南与实战代码解析,带你轻松掌握网络化游戏开发的核心要领与最佳实践路径
【8月更文挑战第31天】构建实时多人对战平台是技术与创意的结合。本文使用成熟的Unity游戏开发引擎,从零开始指导读者搭建简单的实时对战平台。内容涵盖网络架构设计、Unity网络API应用及客户端与服务器通信。首先,创建新项目并选择适合多人游戏的模板,使用推荐的网络传输层。接着,定义基本玩法,如2D多人射击游戏,创建角色预制件并添加Rigidbody2D组件。然后,引入网络身份组件以同步对象状态。通过示例代码展示玩家控制逻辑,包括移动和发射子弹功能。最后,设置服务器端逻辑,处理客户端连接和断开。本文帮助读者掌握构建Unity多人对战平台的核心知识,为进一步开发打下基础。
133 0