• Unity编辑器扩展 UI控件篇


    前摇 :认识编辑器扩展的必要性

    由于各种各样的原因,无论是移动端亦或是主机/PC端,进几年的发行的游戏体量是越来越大。通常来说大体量的游戏开发需要一套很成熟完善的工作流,亦或说有很强的工业化的能力,像R星育碧这样的游戏厂商一定会有非常强大的工业化能力,才能有大表哥这样充斥大量细节或刺客信条这样充斥BUG,口误,是持续迭代的游戏作品

    所谓工欲善其事必先利其器,要想提升游戏开发效率,就要有好用的游戏开发工具,说到最后就绕不开游戏开发引擎

    游戏业界常说,自研的引擎不一定是最好的,但是最好的引擎一定是自研的,一定程度上说明了定制化引擎存在优势。 虽然像Unity与虚幻这样的商业引擎已经非常成熟好用,但其本身还是通用性的商品,很难在每一个方面都做到很好的适配性

    而就Unity引擎,又相比于虚幻来说在整个工作流上表现还不够成熟。操作方式使用习惯上还是存在非常多的程序员思想,很多开发工具可视化程度还不够完善,对于程序员来说也许问题不大,但是对策划与美术来说就非常不友好

    各种层面上来说,为提升编辑器的易操作性与保障项目开发独特性,而对编辑器进行扩展魔改,已经变成了游戏开发者所必须的技能之一

    发生:Unity编辑器扩展之UI控件

    乱七八糟的话说完后,回归正题。根据Unity文档的解释与案例,了解编辑器界面创建过程所需的结构与方法

    与做游戏UI界面不同,引擎编辑器界面的搭建完全没有现成控件做可视化搭建,而只能依靠较底层的代码命令手撸完成。不过好在Unity提供了一套完整的纯代码界面编辑模式,并称其为“即时模式”GUI 系统。关于该系统的具体解释这里直接搬运官方文档的说明:

    • “即时模式”GUI 系统(也称为 IMGUI)是一个完全独立的功能系统,不同于 Unity 基于游戏对象的主 UI 系统。IMGUI 是一个代码驱动的 GUI 系统,主要用作程序员的工具

    虽然该系统与主UI系统不太相同,但是也同根同源,学习起来难度并不大。通过下面的一个小Demo来演示IMGUI的具体使用方法与应该注意的一些点

    继承EditorWindow创建编辑器窗口:

    编辑器UI界面展示与逻辑刷新一般都是继承于EditorWindow这个类来实现的,对其生命周期结构的使用类似于通过编辑继承MonoBehavior的脚本代码完成游戏生命周期逻辑的实现。两者有着相似的监听事件,一样的周期性刷新方法,所以可以无学习的成功的上手使用

    Unity中创建并继承于EditorWindow类编辑器脚本并命名为EditorDemo ,然后使用类中的GetWindow方法创建CreateWindow静态方法(注意一定要是静态),通过该静态方法实现编辑器窗口界面的初始化,具体的代码细节如下:

    public class EditorDemo : EditorWindow
    {
        [MenuItem("EditorDemo/Test  &1")]
        public static EditorDemo CreateWindow()
        {
            EditorDemo window = GetWindow<EditorDemo>("测试编辑器");
            window.autoRepaintOnSceneChange = true;
            window.Show();
            return window;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在上面的代码中,会在初始化静态方法上添加MenuItem命令用以实现在引擎内的上部导航栏中增加一个自定义编辑器的开启页签

    • 关于MenuItem:
      MenuItem 属性用于向主菜单和检视面板上下文菜单添加菜单项,使用特定格式可以实现一些额外效果,如上面案例中使用反斜杠符号可以创建多个层级的菜单,而“&”符号则可以为该编辑器界面添加开启快捷键(通常与Alt形成组合键),如图所示:

    在这里插入图片描述

    在对编辑器界面完成初始化的工作后,后面的界面细节需要对EditorWindow类做一些了解。查阅文档来熟悉以下该类提供的一些消息函数。了解他们分别用来实现不同状态下的哪些编辑器功能:
    在这里插入图片描述
    在上面的消息,对编辑器扩展UI方面最需要关注的OnGUI函数,通常来说,界面元素的创建通常都需要写入到改方法内。前面有说到即时模式GUI创建编辑器UI控件无法通过可视化的操作工具来完成,只能由Unity提供的相关API来做,过程类似于不使用游戏开发引擎,直接基于相应编程语言的游戏开发,常用的控件创建类有下面几个:

    • GUI:是UnityEngine下的类,其方法可在Runtime状态下调用执行
    • GUILayoutGUI的可自动排版版本,无需Rect布局定位
    • EditorGUI:是UnityEditor下的编辑器类
    • EditorGUILayoutEditorGUI的可自动排版版本,无需Rect布局定位

    通过四个类的命名也很容易的看出他们之间的区别与使用区间。通常来说,GUIGUILayout是使用最广的,编辑器状态与RunTime下都可用。独立的编辑窗口通常GUI与EditorGUI两个类可以混合使用,而在运行状态下使用就只能使用GUIGUIlayerout

    以使用场景较多的GUIlayout为案例,可以从文档介绍中看到Unity引擎提供了相当多的创建控件的接口,熟悉使用这些接口,完全可以利用自己游戏界面的开发界面方便的开发出所需的编辑器

    在这里插入图片描述
    通过一个简单的案例介绍这些API的使用方法。尝试在场景中选中某一个物体,并通过编辑器来控制其整体缩放与颜色改变:

    首先关于选择对象的获取,Unity提供了获取编辑器状态下的开发者所选择的对象编辑器方法Selection.activeGameObject,而在前面的关于EditorWindow的消息中存在OnSelectionChange函数,该函数会在选择对象改变时被调用。通过使用这两个函数与接口可以高效的得到实时选择的物体:

        public GameObject selectObj;
        private void OnSelectionChange()
        {
            selectObj = Selection.activeGameObject;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在得到编辑器场景内的某一物体后,通常我们可以调节Inspector面板内的对应参数来改变选中对象的状态。下面的代码是编辑器控件来模仿这个效果:

        public string selectName;
    	public string textStr;
        public float slideNum;
        public Color color;
        private void OnGUI()
        {
            selectName = selectObj == null ? "未选中物体" : selectObj.name;
            selectName = EditorGUILayout.TextField("选中的物体名:", selectName);
            GUILayout.Space(20);
            slideNum = EditorGUILayout.Slider("控制对象缩放:", slideNum, 1, 10);
            if (selectObj!=null)
            {
                selectObj.transform.localScale=new Vector3(slideNum,slideNum,slideNum);
            }
            GUILayout.Space(20);
            color = EditorGUILayout.ColorField("选择颜色:",color);
            GUILayout.Space(10);
            if (GUILayout.Button("点击更换颜色")&&selectObj != null)
            {
                selectObj.GetComponent<MeshRenderer>().material.color = color;
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    演示效果:
    请添加图片描述

    Inspector面板之脚本组件扩展:

    有时某些扩展需求不需要通过独立窗口来完成,比如对某些脚本组件希望有一些轻量的编辑操作,就可以做一些嵌入式的方法扩展

    简单的做一个案例演示。首先创建一个继承于MonoBehavior脚本并挂载到场景中的物体上,然后在该脚本中写入一个对字符串填充数据的方法:

    public class Demo : MonoBehaviour
    {
        public string  str;
        public void ChangeStrDemo()
        {
            str = "编辑器扩展测试";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然后创建一个继承于Editor的编辑器扩展类,代码如下:

    [CustomEditor(typeof(Demo))]
    class EditorInspectorDemo : Editor
    {
        public Demo demo;
        private void Awake()
        {
            demo = target as Demo;
        }
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            if(GUILayout.Button("测试按钮"))
            {
                demo.ChangeStrDemo();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    完成上面的代码后回到引擎中,就可以得到一个编辑器中执行函数的方法

    请添加图片描述

    小Tip:

    编辑器状态下执行函数方法有很多中方式,如最简单的常用的ContextMenu命令。通过在脚本函数上添加命令,就可以在编辑器类快速调用方法,而且与Runtime模式不同,该方式会保存修改后的状态,非常适合快速开发扩展

    Runtime模式下Game窗口扩展:

    Runtime模式下可以在MonoBehavior脚本中直调用GUI方法做扩展,通过一个案例来简单的陈述该过程:

    通过创建一个游戏内窗口得到事件响应控件(Button),并实现一个编辑器方法:记录打开指定文件目录内的文件的资源路径:

    public class Demo : MonoBehaviour
    {
        private void OnGUI()
        {
            GUI.Window(908, new Rect(0, 0, 300, 300), CreateGuiWindow, "测试小窗口");
        }
        void CreateGuiWindow(int id)
        {
            if (GUILayout.Button("打开asset文件夹并筛选文件"))
            {
                EditorUtility.OpenFilePanel("选择文件", Application.dataPath,"cs");            
            }
            if (GUILayout.Button("打开asset文件夹选择文件夹"))
            {
                 EditorUtility.OpenFolderPanel("选择文件夹", Application.dataPath, "");
            }       
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    执行上面的代码,效果如下:
    请添加图片描述

    由于Runtime模式下Game窗口扩展通常都是基于自身项目结构的模式执行的,所以用上面的这个案例来理解其使用模式并不是很合适,不过该模式并不会经常使用到,稍微了解其控件用法即可

    虽然Runtime模式下编辑器扩展不常用到,但是相比于Editor状态是有更高的自由度,可以执行很多运行状态下才可调起的方法函数,或者说可以更好的和当前项目的框架内容交互。而不用额外再j将其转为编辑器方法,这对一些小的功能扩展来说可能会很高效

    UI控件小集合:

    具体的UI控件可以通过查阅文档获取,链接地址如下:

    为了方便重复查阅,这里也简要罗列一些常用的:

    EditorGUILayout:

    • TextArea: 创建一个文本区域

    • TextField: 创建一个文本字段

    • IntField与FloatField:数字输入框

    • Vector2Field与Vector3Field等:向量输入框

    • ObjectField:生成一个可接收任何对象类型的字段

    • Space: 在上一个控件和下一个控件之间留出一个小空间。

    • BoundsField: 创建用于输入 Bounds 的 Center 和 Extents 字段。

    • ColorField:颜色选择器

    • Toggle: 创建一个开关

    • RectField 创建用于输入 Rect 的 X、Y、W 和 H 字段

    • EditorToolbar: 创建一个用指定的编辑器工具集合填充的工具栏

    • EnumFlagsField: 单击后,系统会为枚举类型的每个值显示带有选项的菜单

    • EnumPopup: 创建一个枚举弹出选择字段

    • IntSlider: 创建一个滑动条,用户可以进行拖动以在最小值和最大值之间更改整数值

    • Foldout: 创建一个左侧带有折叠箭头的标签

    • HelpBox: 创建一个带有发送给用户的消息的帮助框

    • InspectorTitlebar: 创建一个类似于 Inspector 窗口的标题栏

    • LayerField: 创建一个层选择字段

    • TagField: 创建一个标签选择字段

    • PasswordField: 创建一个可让用户输入密码的文本字段。

    后摇:编辑器扩展功能篇预告

    上面介绍了一些基本的编辑器界面搭建相关的UI控件,算是说明了一些基础的知识。而编辑器扩展除了界面外,另外一块比较重要的是编辑器扩展之编辑功能的实现。如上面Runtime案例中的打开本地文件夹的小案例,就是编辑器扩展中编辑功能的体现,通过UI控件维护触发这些提升开发效率的辅助功能才是扩展的核心所在

    为了使编辑器扩展的内容介绍更完整,我会努力尽快更新下篇的,当然最重要的是,如果有大佬发现文章中存在的任何问题,希望可以留言指出,我会在后续努力完善的

  • 相关阅读:
    线上化变迁,使得销售与市场的脱节像一场濒临破裂的婚姻!
    【每日一题】找到字符串中所有字母异位词
    二级Java程序题--03综合应用:源代码(all)
    Docker Machine简介
    常见的变体卷积
    Linux quota
    论文阅读CVPR2022 《Language As Queries for Referring Video Object Segmentation》
    腾讯数字孪生和To B简介
    V853 替换开机启动LOGO
    健与美杂志健与美杂志社健与美编辑部2022年第7期目录
  • 原文地址:https://blog.csdn.net/xinzhilinger/article/details/125462320