Unity编辑器的扩展:IMGUI
IMGUI 介绍
所有关于 Editor 的相关 UI,包括 Inspector、Hierarchy、Window、Game 视图上动态创建的那些半透明 UI、还有 Scene 视图上可添加的辅助显示 UI,叫做 IMGUI,全称 Immediate Mode GUI。该名字来源于两类型的 UI 系统:immediate 和 retained。
- retained:当你设置好各种组件如 Text、Button 等的信息,或修改它们的相关属性后,这些组件的相关信息和改动就被保存(retained)下来了,系统会根据这些新的信息来绘制响应事件等,你可以随时去查询如 Text 文本内容或颜色等信息。UGUI 即是典型的 retained mode GUI。
immediate:跟上面的相反,系统不会自动保存 UI 控件上的各种信息,不会用上次的状态继续工作,而是反复的询问你这些控件应当是处于什么位置什么文本等状态信息。因此任何的用户交互结果是立即呈现返回给用户,而不是当用户需要的时候自行查询。如:
bool selected = false;
void OnGUI()
{
selected = GUILayout.Toggle(selected, "A Toggle text");
if (selected)
{
DoSomething()
} //if (GUILayout.Toggle(selected, "A Toggle text");)
//{
// DoSomething()
//}
}OnGUI 会被反复调用以更新绘制 UI,通常控件的返回值需要自行保存下来再传入控件中以更新控件状态,如果像注释中的代码那样则 Toggle 的状态改变后下一次更新则又变为旧的了,感受就像功能失效了一下。
IMGUI 是十分低效的,它是纯代码驱动的,对于美术而言基本无法使用(非可视化的,稍复杂点的 UI 程序写起来也很蛋疼...)。但是对于非实时交互的情况下却是一种可选的方式,比如 Inspector 上的 UI,它本身就是对代码脚本的扩展,通常不是美术人员所写脚本,控件可立即展示对应的脚本状态的修改。
关于IMGUI的基本介绍请看官方文档
相关类介绍
Editor 类和 EditorWindow 类都继承自同一个基类:ScriptableObject,因此他们都可以针对某种脚本类来进行操作。
Editor 类只能定制针对脚本的扩展,从脚本内容在 Inspector 里的显示布局,到变量在 Scene 视图的可视化编辑
EditorWindow 主要是扩展编辑器的功能,不必针对某种脚本(虽然可以做到),而且它有独立的窗口,使用 OnGUI 函数来绘制 2D 的 UI。
能在 Game 视图上显示的 ingame GUI 主要是 GUI 和 GUILayout 两个类,另外与之对应的 editor-only 的类是 EditorGUI 和 EditorGUILayout 两个类,两套类提供的控件功能都差不多,可以混合搭配一起使用。
GUI 和 EditorGUI 提供的接口为 Fixed Layout 的,基本上都需要传入一个 Rect 变量来指定控件的位置和大小,当窗口大小改时控件会保持不变。可以将代码放到 GUI.BeginGroup() 和 GUI.EndGroup() 之间将控件进行分组或划分子区域进行布局。
GUILayout 和 EditorGUILayout 则是与前面两个对应的 Auto Layout 类,不需要指定控件位置和大小,会根据当前显示区域的大小自动调整布局适应变化。多个控件默认是从上往下的顺序排列,可以用 GUILayout.BeginHoriztontal(), GUILayout.EndHorizontal(), GUILayout.BeginVertical(), GUILayout.EndVertical()(或者对应的 EditorGUILayout 类)将代码写到这些调用之间进行水平或者垂直排列控件,将这些布局互相组合或嵌套即可排布出复杂的 UI 界面。
GUIUtility 类提供了一些工具方法,如获取控件 id、转换 Screen 和 GUI 之间的坐标等。
EditorGUIUtility 类是针对 Editor 提供一些工具方法,除了 GUIUtility 那些方法外还增加了很多有用的方法,如获取内建资源图标、高亮选中某个物体、产生复制粘贴命令等。
Event:该类包含了所有的用户输入如按钮或鼠标点击等和 UI 相关布局和绘制事件。调用 Event.current 获得当前事件信息,查看 EventType 枚举定义可查询全部可用类型事件。这个信息在 OnGUI,OnInspector,OnSceneGUI 里都可以使用以处理一些特定逻辑。
自定义控件
GUILayout.Button(GUIContent content, GUIStyle style, params GUILayoutOption[] options)
大部分控件都可以传入 GUIContent、GUIStyle 来指定控件的风格外观,自动布局的控件还可以传入多个 GUILayoutOption 组合来设定大小。
- GUIContent:该类定义了控件需要显示什么(what to render),包含三个基本要素:图片 image、文本 text、鼠标停留的提示信息 tooltip(play mode 运行时 tooltip 无效)。也可以用控件的其它重载分别传入这几项中的一个或多个内容。可以用
EditorGUIUtility.IconContent(name)获得一个内置图标,如下可获得一个播放按钮:GUILayout.Button(EditorGUIUtility.IconContent("PlayButton")
- GUIStyle:该类定义了控件要如何显示(how to render),包括 Normal Hover Active 等状态切换显示、文字大小颜色、指定图标显示位置等各种信息。每种类型的控件都会有默认的外观风格,通常可以在现有的控件风格上进行修改:
//默认控件文本会显示在图标之后,下面可获得图上字下的按钮风格
GUIStyle style = new GUIStyle(GUI.skin.button); //或者传入 unity 的默认风格名称 new GUIStyle("button")
style.imagePosition = ImagePosition.ImageAbove;
- GUILayoutOption:该类为自动布局 GUILayout 和 EditorGUILayout 的控件提供一系列预定条件,如最小宽度、最大高度、是否横向拉伸等。
GUISkin:是一系列GUIStyle的集合,可对每种控件分别指定样式,可设置一整套风格统一完全不同于默认风格的UI。通过 Assets->Create->GUI Skin 可创建,代码中
GUI.skin = customSkin;即可一次应用所有的 GUIStyle。
当要实现一个自定义功能的控件,大体的处理流程为以下所示代码,该代码为 GUILayout.RepeatButton 的主要代码,该代码在 OnGUI 中被调用:
private static bool DoRepeatButton(
Rect position,
GUIContent content,
GUIStyle style,
FocusType focusType)
{
GUIUtility.CheckOnGUI();
//分配一个唯一 id 值给该控件,传入的第一个参数为任意唯一的值,此处为一个 string 的 hash。
int controlId = GUIUtility.GetControlID(GUI.s_RepeatButtonHash, focusType, position);
//获得对应的各种事件并处理关心的。
switch (Event.current.GetTypeForControl(controlId))
{
case EventType.MouseDown:
//鼠标的点击位置在当前控件上。
if (position.Contains(Event.current.mousePosition))
{
//保存当前的 id 为 hot 的控件,全局只能有一个为 hot 控件。
GUIUtility.hotControl = controlId;
//消耗掉当前事件防止后续控件处理无效逻辑。(故所有在该控件之后的控件全部无法再判断点击事件)
Event.current.Use();
}
return false;
case EventType.MouseUp:
//仅当前的 hot 控件为本控件时才处理对应逻辑,忽略掉其它。
if (GUIUtility.hotControl != controlId)
return false;
//当前 hot 控件功能结束后一定要置 0 恢复,否则当前 UI 是冻结的,其它控件全部无法响应。
GUIUtility.hotControl = ;
Event.current.Use();
return position.Contains(Event.current.mousePosition);
case EventType.Repaint:
//该事件处理显示相关。
style.Draw(position, content, controlId);
return controlId == GUIUtility.hotControl && position.Contains(Event.current.mousePosition);
default:
return false;
}
}
关于 ControlID 相关概念,我个人理解感觉作用不大,GetControlID 获得的 id 与控件其实并没有直接的联系,连续多次调用便能获得多个不同的值,在每次 OnGUI 结束后 id 栈信息就会清空以便每次重入时能产生与之前一致的 id。id 与控件的关系为手动关联起来的,因代码的顺序执行,当前环境的后续以该 id 相关的处理 “认为” 即是对应该控件。如以上代码,GUI.xxx 等内置的控件中全部都有对应的一个 id,该 id 外部是无法访问的,故在 GUI.Button 之后立即调用 GetControlID 是得不到 Button 的 id 的,仅是又产生了一个新的值,同样不能再拿到控件内部已处理过的事件。
GetTypeForControl 获得传入 id 对应的事件类型,该信息与实际控件同样是无直接关联的,必须同时判断 controlPosition.Contains(Event.current.mousePosition) 才能得知为当前控件上的事件,经测试发现它与 Event.current.type 并没有什么区别,即使当前 hotControl 或 keyboardControl 不是当前 id,它好像没有针对传入的 id 作任何有效过滤。
比较有用的可能是 GUIUtility.GetStateObject,它给 id 绑定了一个自定义的数据信息以供后续逻辑处理,从而不需要自己维护与各控件相关的数据。
如果有同学对 ControlID 有更深入的理解望可以讨论一下~
Tips
Getting control 0’s position in a group with only 0 controls when doing Repaint.
OnGUI 循环实际上是被一系列的 Event 所调用,如,IMGUI 会在 EventType.Layout 中收集所有控件的包含关系及占用的空间大小位置等信息,然后在 EventType.Repaint 事件中才实际以 Layout 中统计的信息来分配空间绘制显示。
如果某逻辑在 Layout 与 Repaint 之间导致了 UI 数据不一致时就会出现上面类似的报错,有时也会看到一个 if 判断肯定是进不去的但实际却进去了等现象也是在不同的事件中情况会不一样。
此时需要仔细检查逻辑是否有意外导致 Layout 布局在即将 Repaint 显示时不一致。
可以将可能有问题的代码写在下面代码块中:
if (Event.current.type == EventType.Repaint)
{
//
}
或
if (Event.current.type == EventType.Layout)
{
//
}
GUIUtility.ExitGUI 也可处理该报错,但后续代码也得不到执行了……该接口在 2018 上有文档说明,在 5.x 上面搜不到,但实际上仍是可调用的。
NullReferenceException: Object reference not set to an instance of an object
UnityEngine.GUILayoutUtility.BeginLayoutGroup (UnityEngine.GUIStyle style, UnityEngine.GUILayoutOption[] options, System.Type layoutType) (at /Users/builduser/buildslave/unity/build/Runtime/IMGUI/Managed/GUILayoutUtility.cs:296)
该报错有一种情况是当调用了 EditorUtility.DisplayProgressBar 显示进度条时会出现,它会主动调用到 OnGUI,猜测是当前的 OnGUI 还未结束就再次进入 OnGUI 导致的类似上一个问题中的在函数重入时数据不一致布局信息错乱。
暂时未找到解决方法,只有在卡顿死等与进度展示伴随报错二者中选择了。
GUIContent 中设置的 tooltip 功能只在非运行起来时可用,将编辑器运行后即失效,最后查到该问题是 By Design...
任一个 GUIStyle 可用于任一类似的控件,在美术不提供图的情况下混用 style 即可搭出较好的效果:
//传入默认风格名称即可将整块垂直显示区域添加一个类似文本框的背景以区分其他区域
EditorGUILayout.BeginVertical("textfield");
...
EditorGUILayout.EndVertical(); //将按钮风格显示成工具栏按钮的样式有时可能会更美观
if (GUILayout.Button("My Button", EditorStyles.toolbarButton))
{
} //把单选的组按钮 button 改成选框 toggle 样式
GUILayout.SelectionGrid(m_selectedIndex, m_Names, , EditorStyles.toggleGroup)
获得一个内置的默认风格有三种方式:"textfield"、GUI.skin.textField、EditorStyles.textField。
文本内容想要其中部分文字添加一个颜色以突出显示,需要开启富文本支持 myGUIStyle.richText = true;
由于 Unity 编译顺序决定了 Runtime 脚本是无法调用 Editor 代码的,有些逻辑因历史原因不方便修改,但非要运行时调用编辑器脚本,有个办法是在编辑器脚本初始化时去绑定运行时脚本中的静态委托或事件:
public class MyRuntimeScript : MonoBehaviour
{
#if UNITY_EDITOR
public static System.Action<GameObject> onEvent;
#endif
...
#if UNITY_EDITOR
if (onEvent != null)
onEvent(gameObject);
#endif
} public class MyEditorScript
{
[InitializeOnLoadMethod]
static void Init()
{
MyRuntimeScript.onEvent = go =>
{
...
};
}
有时需要在自动布局(GUILayout/EditorGUILayout)中插入固定布局(GUI/EditorGUI)控件,如以 UV 坐标显示一张图片时只有固定布局接口 GUI.DrawTextureWithTexCoords,需要传入固定布局接口一个 Rect,但此时很难确定自动布局下当前位置坐标与大小,可调用 Rect rect = GUILayoutUtility.GetRect(new GUIContent(), GUIStyle.none); 或 Rect rect = GUILayoutUtility.GetRect(0f, 10f, GUILayout.ExpandWidth(true)); 获得一片当前空间可用的一块区域,后续可基于该 rect 进行坐标计算。另 rect.Contains(Event.current.mousePosition)可判断鼠标是否在某区域内。
GUILayout.FlexibleSpace()可以将空白区域全部占满。自动布局 Layout.XXX 控件默认是会占据尽量大的空间(通常是整个窗口的宽度),连续两个控件想一个在最左边一个最右边时,在之间插入该调用即可,同理三个控件之间插入即可实现平均占据整行空间的排列,这仅靠 BeginHorizontal() 或 BeginVertical() 组合是比较难实现的。
Unity编辑器的扩展:IMGUI的更多相关文章
- Unity编辑器扩展
在开发中有可能需要自己开发编辑器工具,在Unity中界面扩展常见两种情况,拿某插件为例: 1,自建窗口扩展 2,脚本Inspector显示扩展 不管使用那种样式,都需要经常用到两个类EditorGUI ...
- Unity编辑器扩展 Chapter7--使用ScriptableObject持久化存储数据
Unity编辑器扩展 Chapter7--使用ScriptableObject持久化存储数据 unity unity Editor ScirptableObject Unity编辑器扩展 Chapt ...
- Unity编辑器扩展chapter1
Unity编辑器扩展chapter1 unity通过提供EditorScript API 的方式为我们提供了方便强大的编辑器扩展途径.学好这一部分可以使我们学会编写一些工具来提高效率,甚至可以自制一些 ...
- unity 编辑器扩展简单入门
unity 编辑器扩展简单入门 通过使用编辑器扩展,我们可以对一些机械的操作实现自动化,而不用使用额外的环境,将工具与开发环境融为一体:并且,编辑器扩展也提供GUI库,来实现可视化操作:编辑器扩展甚至 ...
- 使用C#的Conditional特性与Unity编辑器宏命令做条件编译
概要 在传统的C#项目中,用Conditional特性做条件编译时,需要在Visual Studio中项目的属性里添加上条件编译符号,用法参考这篇文章. 而在Unity项目中,条件编译符号需要在Uni ...
- 【Unity】2.1 初识Unity编辑器
分类:Unity.C#.VS2015 创建日期:2016-03-26 一.简介 本节要点:了解Unity编辑器的菜单和视图界面,以及最基本的操作,这是入门的最基础部分,必须掌握. 二.启动界面 双击桌 ...
- 定制你的Unity编辑器
Unity的编辑器可以通过写脚本进行界面定制,包括添加功能菜单,今天写游戏Demo用到了记录一下. 为Unity添加子菜单 示例程序 [AddComponentMenu("Defend Ho ...
- 实现Unity编辑器模式下的旋转
最近在做一个模型展示的项目,我的想法是根据滑动屏幕的x方向差值和Y方向的差值,来根据世界坐标下的X轴和Y轴进行旋转,但是实习时候总是有一些卡顿.在观察unity编辑器下的旋转之后,发现编辑器下的旋转非 ...
- 【Unity优化】如何实现Unity编辑器中的协程
Unity编辑器中何时需要协程 当我们定制Unity编辑器的时候,往往需要启动额外的协程或者线程进行处理.比如当执行一些界面更新的时候,需要大量计算,如果用户在不断修正一个参数,比如从1变化到2,这种 ...
随机推荐
- java中接口的概念及使用(补充final修饰符的使用)
接口 初期理解,可以是一个特殊的抽象类 当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示 class 用于定义类 interface 用于定义接口 接口定义时,格式特点: 1.接口中常见的 ...
- <再看TCP/IP第一卷>TCP/IP协议族中的最压轴戏----TCP协议及细节
题外话:刚刚过去的半个月实在是忙得我喘不过来气,虽然手里还压着几个项目得在期末考试之前做完,但是想想还是更新一下随笔,稍微换个心情.另外小吐槽一下那些在博客园里原封不动抄书当随笔的人,唉真是....算 ...
- 使用ksar解析sar监控日志
sar 是属于sysstat包中的一个工具 安装sysstat包后,默认创建一个/etc/cron.d/sysstat文件,其默认内容为: # run system activity accounti ...
- Win7_自动播放
1.gpedit.msc(组策略)--.> 本地组策略编辑器 --> 展开“计算机配置→管理模板→所有设置” --> 右侧窗口"关闭自动播放" 2. 3.
- java学习进度条四
- MySQL 当记录不存在时insert,当记录存在时update
MySQL当记录不存在时insert,当记录存在时更新:网上基本有三种解决方法 第一种: 示例一:insert多条记录 假设有一个主键为 client_id 的 clients 表,可以使用下面的语句 ...
- Python解决中文字符的问题
from __future__ import unicode_literals print(type("test")) #<type 'unicode'> Chinat ...
- Debian for ARM install python 3.5.x
/********************************************************************************** * Debian for ARM ...
- FFmpeg基础知识之————H264编码profile & level控制
H.264有四种画质级别,分别是baseline, extended, main, high: 1.Baseline Profile:基本画质.支持I/P 帧,只支持无交错(Progressive)和 ...
- FFMPEG内存操作(二)从内存中读取数及数据格式的转换
相关博客列表: FFMPEG内存操作(一) avio_reading.c 回调读取数据到内存解析 FFMPEG内存操作(二)从内存中读取数及数据格式的转换 FFmpeg内存操作(三)内存转码器 在雷神 ...