IMGUI:
一个即时模式的GUI系统,即时模式的GUI系统中通常不会保存GUI的信息,一切依赖于代码驱动。如通过函数调用的形式指定UI的每个部分(Button,Label等)及相应的位置
if(GUILayout.Button()){}
,并立即返回给你交互的结果、信息。
RetainedGUI:
保留模式GUI,用户设置的各种组件(Button、Text等)信息都会被保留并被系统应用于渲染至屏幕、响应事件等。更改文本或位置就是在操作存储在某处的信息。用户更改值系统则保存这个更改,并由用户进行查询或相应回调。
事件:
IMGUI代码在运行时都会处理 当前 的事件,比如:"EventType.MouseDown\EventType.Repaint".如果用户自己编写对应的事件处理那就会看起来像这样,对不同事件分别写对应的回调处理。
![](https://img-blog.csdnimg.cn/img_convert/92d976eaaa74d1ad5e016766bd570733.png)
通过组合的方式:
![](https://img-blog.csdnimg.cn/img_convert/3a3acfff9305a5fa79513d2196524ed8.png)
对于不同的按钮实际结构是相似的,只是对于不同事件的相应处理不同。同时为了将这些不同事件下的不同行为联系在一起,IMGUI还有"ControlID"的概念,ControlID与控件其实并没有联系,主要用来处理事件。在每次OnGUI调用结束后,相关的ID栈信息便会被清空,并且ID仅仅是根据顺序赋予相应的控件,由于代码顺序执行所以在此之后的相同id都会被对应为该控件。如GUI.Button中都会有对应的ControlID,但不会供外部进行访问同样也拿不到控件内部处理了的事件。
如何自定义控件
//决定函数参数:①Rect包含位置、Size信息,②GUIContent,内容(文字or图片)③GUIStyle GUI的表现样式
publicstaticboolMyButton(Rectrect, GUIContentcontent, GUIStylestyle){
//首先获取ControlID,IMGUI中ControlID仅仅是根据代码执行顺序进行分配,因此即便此时控件正在执行我们并不关注的事件如(Repaint)也需要进行ControlID的注册,除非不为他的任何事件注册ControlID,如果仅在她执行我们所期望的事件如MouseDown时注册ControlID则会导致id的混乱
intcontrolID=GUIUtility.GetControlID(FocusType.Passive);
//接下来对事件进行判断
switch(Event.current.GetTypeForControl(controlID)){
caseEventType.Repaint:{
floatdefaultWidth=10;
Rectrect=newRector(rect){width=defaultWidth;}
GUI.DrawTexture(rect,style.normal.background);
break;
}
caseEventType.MouseDown{
if(rect.Contains(Event.current.mousePosition)&&Event.current.button==0){
returntrue;
//如果需要在此控件的mouseDown事件期间,鼠标的其他操作(如拖拽期间鼠标的位置转移到了其他控件上,则应注意mouseUp事件)不会影响到其他控件,直到获取mouseUp事件触发,则可以:
GUIUtility.hotControl=controlID;
break;
}
}
caseEventType.MouseUp{
if( GUIUtility.hotControl==controlID)
GUIUtility.hotControl=0;
break;
}
}
//如果说此控件的操作会影响值的改变
GUI.changed=true;//注意不要设置为false,代码顺序执行,可能会隐藏其他控件更改了值的操作
returnfalse;
}
如何存储控件状态
IMGUI为控件所关联的 “状态对象(自己的定义的状态类)” 提供了一个简单的存储系统,并通过控件的
关联相应的状态对象实例,每个ControlID只能拥有一个状态对象,重新加载编辑器代码时,状态对象也不会被序列化(即便标记为[Seriallizable])。
//实现一个按住Button2s后闪烁红色
//状态类:
publicclassButtonFlashingStateInfo{
publicfloatmouseDownTime;
publicvoidSetMouseDownTime(){
mouseDownTime=EditorApplication.timeSinceStartUp;
}
publicboolisFlashing(intcontrolID){
if(GUIUtility.ControlID!=controlID) returnfalse;
floatelapsedTime=EditorApplication.timeSinceStartUp-mouseDownTime;
if(elapsedTime<=2f) returnfalse;
return
}
}
//Button:
publicstaticboolFlashButton(Rectrect, GUIContentcontent, GUIStylestyle){
intcontrolID=GUIUtility.GetControlID (FocusType.Native);
varstate= (FlashingButtonInfo)GUIUtility.GetStateObject(
typeof(FlashingButtonInfo),controlID);
switch (Event.current.GetTypeForControl(controlID)) {
caseEventType.Repaint:{
GUI.color=state.IsFlashing (controlID)?Color.red: Color.white;
style.Draw (rect, content, controlID);
break;
}
caseEventType.MouseDown:{
if (rect.Contains (Event.current.mousePosition)
&&Event.current.button==0
&&GUIUtility.hotControl==0){
GUIUtility.hotControl=controlID;
state.SetMouseDownTime();
}
break;
}
caseEventType.MouseUp:{
if (GUIUtility.hotControl==controlID)
GUIUtility.hotControl=0;
break;
}
}
returnGUIUtility.hotControl==controlID;
}
GUIStyle
GUIStyle包含有关与GUI元素的视觉属性信息,如字体、颜色、布局属性等。所有存储在GUIStyle中的这些信息都会用来计算出给定内容的宽度高度并将其画在屏幕上。并且它还可以处理不同情况,如当鼠标悬停在上方时绘制不同的样式、控件处于active状态样式等等。
GUIStyle绘制控件的四种主要方式:
构造一个新的GUIStyle new GUIStyle() 并设置他的属性
使用内置的Style样式(可通过style预览工具预览)
克隆已有的Style样式并进行修改 new GUIStyle(rootStyle)
使用GUISkin
Layout
GUI库中的所有控件函数都包含一个Rect参数,我们通过该参数来确定控件在屏幕上的位置和size。当我使用GUIStyle涉及到间隔时,我们可能会需要大量的计算工作来保证这个间距值。
但是,IMGUI包含一种 layouting 机制,他可以通过响应EventType.Layout为我们自动计算出合适的Rect值。
IMGUI发送事件
用户调用的控件通过使用IMGUI布局函数-(GUILayoutUtility.GetRect(),GUILayout.BeginHorizonal/Vertical等)来为我们布局空间中的空间及其所需空间来建立一棵树。
树构建完成后,进行递归计算每个元素实际的宽度、高度以及他们之间的相对位置。
//GetRect部分代码
//接收到Layout事件
caseEventType.Layout:
if (style.isHeightDependantOnWidth){
//向GUILayoutGroup添加 GUILayoutEntry (对Rect进行的封装,定义了高度范围、宽度范围、自身rect)
GUILayoutUtility.current.topLevel.Add((GUILayoutEntry) newGUIWordWrapSizer(style, content, options));
}else{
Vector2constraints=newVector2(0.0f, 0.0f);
if (options!=null)
{
foreach (GUILayoutOptionoptioninoptions)
{
switch (option.type)
{
caseGUILayoutOption.Type.maxWidth:
constraints.x= (float) option.value;
break;
caseGUILayoutOption.Type.maxHeight:
constraints.y= (float) option.value;
break;
}
}
}
Vector2vector2=style.CalcSizeWithConstraints(content, constraints);
vector2.x=Mathf.Ceil(vector2.x);
vector2.y=Mathf.Ceil(vector2.y);
GUILayoutUtility.current.topLevel.Add(newGUILayoutEntry(vector2.x, vector2.x, vector2.y, vector2.y, style, options));
}
returnGUILayoutUtility.kDummyRect;
如果需要响应EventType.Repaint或其他类型事件时,控件会调用相同的IMGUI布局函数,但是会直接返回它计算的矩形。如在LayoutEvent中已经调用了GUILayoutUtility.GetRect()来注册一个矩形,然后再repaint事件中重调用,它实际上返回了应该使用的矩形。也就是说GetRect做了两件事(Ⅰ.在Layout事件期间,它记录我们想要使用的Style来绘制一些空内容。Ⅱ.在其他事件期间则返回给我们一个真正的Rect。并且在Layout期间矩形实际上并没有用于其中的任何内容,因为IMGUI直到LayoutTree完成才能知道真正要返回的矩形),但是这也意味着需要在Layout事件合其他事件之间的布局调用要保持一致,否则就会得到错误的矩形