在这篇文章中我将给读者介绍Unity中的图形用户界面(GUI)编程.Unity有一个非常强大的GUI脚本API.它允许你使用脚本快速创建简单的菜单和GUI.
简介
Unity提供了使用脚本创建GUI界面的能力.在写这篇文章的时候,Unity还没有提供一套原生的可视化GUI开发工具,尽管你可能会在Unity Asset商店找到一些使用某种形式的图形化脚本编程编写GUI的工具.Autodesk Scaleform也提供了一个可以单独购买并整合进Unity的插件但这超出了这篇文章的范围.如果你对Scaleform插件的unity版本感兴趣,我推荐你看看Scaleform Unity Plugin.
Unity提供了两个主要的类来创建GUI.GUI类用于创建手动放置的GUI控件.GUILayout类用于创建自动放置的GUI控件.这两个类之间的区别将在文章后面搞清楚.
Unity也提供了GUISkin资源(asset翻译成套件?).它可以被应用于给你的GUI控件提供一种通用的的"外观和感觉".一个GUISkin只是GUIStyle对象的集合.每个GUIStyle对象定义了单个GUI控件的样式,比如按钮,标签或者文本域.
GUiText组件可被用于渲染单个的文本元素,GUITexture组件可以被用于渲染2D材质到屏幕.GUIText和GUITexture都适用于为你的游戏绘制GUI元素(就像HUD),但这些组件不适用于在游戏中绘制菜单.对于游戏中的菜单(像等级选择和选项设置页面)你应该使用GUI和GUILayout类.
这些不同的类,资源(Asset)和组件每一个本文中都会阐述
创建菜单
首先我讲述一下如何在使用GUI和GUILayout在Unity中创建菜单.我也会向你展示如何使用GUISkin和GUIStyle来自定义GUI控件的外观
OnGUI回调函数
GUI的渲染是通过创建脚本并定义OnGUI函数来执行的.所有的GUI渲染都应该在该函数中执行或者在一个被OnGUI调用的函数中执行
ButtonDemo.js
function OnGUI()脚本ButtonDemo.js将会在屏幕中间绘制一个按钮,松开按钮文字"Thanks!"就会被打印到调试控制台.
{
var buttonWidth = 100;
var buttonHeight = 50;
var buttonX = (Screen.width - buttonWidth) / 2.0f;
var buttonY = (Screen.height - buttonHeight) / 2.0f;
//在屏幕中间绘制一个button组件
if(GUI.Button(Rect(buttonX,buttonY,buttonWidth,buttonHeight),"Press Me!"))
{
//在调试控制台打印一些文字
Debug.Log("Thanks!");
}
}
GUIContent
大多数通用控件比如按钮和标签允许你指定控件上该呈现在控件上的的文本或者材质.如果你想在一个控件上指定文本与材质,那必须使用GUIContent结构.
CUIContent结构有几个构造函数允许你创建一个带有文本,材质和tooltip的CUIContent对象.
下面的例子扩展了之前的例子,但在这个例子中按钮的内容由GUIContent结构指定.
ButtonDemo.js
#pragma strict改进后的ButtonDemo.js脚本将会在屏幕上显示一个文本与材质.材质,文本和tooltip参数可以在检视器(inspector)中指定.
var texture:Texture;
var text:String = "hello text";
var toolTip:String = "hello toolTip";
function OnGUI()
{
var buttonWidth = 100;
var buttonHeight = 50;
var buttonX = (Screen.width - buttonWidth) / 2.0f;
var buttonY = (Screen.height - buttonHeight) / 2.0f;
//在屏幕中心绘制一个带有文本和图像的按钮
if(GUI.Button(Rect(buttonX,buttonY,buttonWidth,buttonHeight),GUIContent(text,texture,toolTip)))
{
//在调试控制台打印一些文字
Debug.Log("Thanks!");
}
}
ButtonDemo.js脚本会产生如下结果.
放置控件
使用GUI类你必须手动的摆放屏幕上的控件.控件使用GUI静态函数的position参数来摆放.为了在屏幕上摆放控件,必须将一个Rect结构作为第一个参数传递给GUI控件函数.Rect结构的为控件定义了X,Y,Width,Height属性.X,Y,Width和Height单位都是像素.
Screen.width和Screen.height属性可以被用于检视当前屏幕的范围.
脚本ScreenDimension.js可以被用于渲染上面的截图.
ScreenDimensions.js
function OnGUI()ScreenDimension.js脚本将绘制4个标签.每个标签都使用Screen.width和Screen.height属性来决定其屏幕位置并对齐到屏幕角落里(测量单位为像素)
{
var width = Screen.width;
var height = Screen.height;
GUI.Label(Rect(0,0,100,20),"Top Left");
GUI.Label(Rect(width - 100,0,100,20),"Top Right");
GUI.Label(Rect(0,height - 20,100,20),"Bottom Left");
GUI.Label(Rect(width - 100,height - 20,100,20),"Bottom Right");
}
GUI类
GUI类是Unity用于将控件渲染到屏幕上的主要类.GUI类使用手动摆放来决定屏幕上控件的位置.这意味着在渲染控件时你必须显式的指定控件在屏幕上的位置.使用这种方法手动摆放控件需要多做些工作但他可以让你精确地控制屏幕上的控件位置.如果你不想手动的指定GUI控件的位置,那你应该使用GUILayout类.我们后面再详细阐述GUILayout.
GUI控件
在下面的章节中,我将介绍在使用GUI和GUILayout时可利用的不同控件这些类提供的默认控件是box,button,label,window,texture,scroll bars,sliders,textfield,textarea,toggle和toolbar
GUI.BUTTON
可能你最常用的控件之一就是按钮了.你可以使用GUI.Button()静态函数来创建一个按钮.这个函数用于将按钮渲染到屏幕上,当松开按钮时函数返回true.
这里值得一说的是GUI.Button函数只会在鼠标在按钮上按下并且在按钮上松开时才返回true,如果用户按下按钮移动鼠标在按钮外面释放鼠标,则函数不会返回TRUE,同样的如果用户按下了鼠标之后将光标移动到按钮上然后释放鼠标该函数也不会返回TRUE.要使该函数返回true,鼠标必须在按钮上按下并释放.
以下代码可用于使用按钮创建一个简单的等级选择屏幕(假定你在Build Settings对话框中有多个场景文件要设置)
LevelSelect.js
function OnGUI()将脚本添加到空的场景中会产生如下结果:
{
var groundWidth = 120;
var groundHeight = 150;
var screenWidth = Screen.width;
var screenHeight = Screen.height;
var groupx = (screenWidth - groundWidth) / 2;
var groupy = (screenHeight - groundHeight) / 2;
GUI.BeginGroup(Rect(groupx,groupy,groundWidth,groundHeight));
GUI.Box(Rect(0,0,groundWidth,groundHeight),"Level Select");
if(GUI.Button(Rect(10,30,100,30),"Level 1"))
{
Application.LoadLevel(1);
}
if(GUI.Button(Rect(10,70,100,30),"Level 2"))
{
Application.LoadLevel(2);
}
if(GUI.Button(Rect(10,110,100,30),"Level 3"))
{
Application.LoadLevel(3);
}
GUI.EndGroup();
}
你也许会想看看Unity脚本参考里的GUI.Toggle和GUI.RepeatButton,这些与GUI.Button控件类似但略有不同的功能.
GUI.Label
GUI.Label()静态函数用于绘制一个标签.标签通常实在屏幕上指定位置绘制的文字.标签控件最常用的是在菜单屏幕中指定选项名称(比如文本框和文本域).
标签可包含文字,材质或者两者兼有(使用之前讲过的GUIContent结构)
下面的例子在屏幕上显示绘制了两个选项.选项名称和滑块的值使用标签呈现
OptionsMenu.js
#pragma strictOptionsMenu.js脚本运行结果如下:
private var masterVolume:float = 1.0;
private var sfxVolume:float = 1.0;
function OnGUI()
{
var groupWidth = 380;
var groupHeight = 110;
var screenWidth = Screen.width;
var screenHeight = Screen.height;
var groupX = (screenWidth - groupWidth) / 2;
var groupY = (screenHeight - groupHeight) / 2;
GUI.BeginGroup(Rect(groupX,groupY,groupWidth,groupHeight));
GUI.Box(Rect(0,0,groupWidth,groupHeight),"Audio Settings");
GUI.Label(Rect(10,30,100,30),"Master Volume");
masterVolume = GUI.HorizontalSlider(Rect(120,35,200,30),masterVolume,0.0,1.0);
GUI.Label(Rect(330,30,50,30),"(" + masterVolume.ToString("f2") + ")");
GUI.Label(Rect(10,70,100,30),"Effect Volume");
sfxVolume = GUI.HorizontalSlider(Rect(120,75,200,30),sfxVolume,0.0,1.0);
GUI.Label(Rect(330,70,50,30),"(" + sfxVolume.ToString("f2") + ")");
GUI.EndGroup();
}
你也应该改看看GUI.TextField和GUI.TextArea这些控件允许你创建可编辑文字输入控件.
GUI.HorizontalSlider和GUI.verticalSlider
GUI.HorizontalSlider和GUI.verticalslider静态函数可相应的用于绘制水平和竖直滑块.滑块用于指定在一定范围内的一个数值.在上面的例子中,使用了两个水平滑块来指定主音量和音效范围为0到1
Slider函数接受当前滑块值和滑块最小值和滑块最大值.上面的例子展示了如何使用水平滑块,竖直滑块使用与水平滑块同样的参数只是滑块是竖直绘制而不是水平绘制.
下面的例子展示了使用竖直滑块来创建一个音频均衡器
#pragma strict结果应类似下面的:
private var equalizerValues = new float[10];
function OnGUI()
{
var groupWidth = 320;
var groupHeight = 260;
var screenWidth = Screen.width;
var screenHeight = Screen.height;
var groupX = (screenWidth - groupWidth) / 2;
var groupY = (screenHeight - groupHeight) / 2;
GUI.BeginGroup(Rect(groupX,groupY,groupWidth,groupHeight));
GUI.Box(Rect(0,0,groupWidth,groupHeight),"Equalizer");
for(var i = 0; i < equalizerValues.Length; i++)
{
equalizerValues[i] = GUI.VerticalSlider(Rect(i * 30 + 20,30,20,200),equalizerValues[i],0.0,1.0);
}
GUI.EndGroup();
}
当使用水平滑块时,最小值在滑块的左边最大值在滑块的右边.当使用竖直滑块时,最小值在顶部,最大值在滑块底部.
您可能也想看看GUI.HorizontalScrollbar和GUI.VerticalScrollbar它们类似于滑块,但具有不同的功能。
GUI.WINDOW和GUI.DRAGWINDOW
GUI类提供了在屏幕上绘制窗口的函数,窗口可以使用外部函数(除了OnGUI)来渲染窗口的内容.
如果在窗口的回调函数中使用GUI.DragWindow函数,那窗口将会是可拖动的.
下面的代码创建了一个简单而可拖动的窗口
DraggableWindow.js
#pragma strict如果在新场景中将脚本应用到GameObject上,你就会看到窗口,点击并拖动窗口标题你就可以在屏幕上把窗口拖来拖去.
//窗口的初始位置以及大小
private var windowRect0 = Rect(20,20,150,0);
function OnGUI()
{
//渲染窗口ID为0
windowRect0 = GUILayout.Window(0,windowRect0,WindowFunction,"Draggable Window");
}
function WindowFunction()
{
GUILayout.Label("This is a draggable window!");
//窗口的拖条(drag-strip),坐标相对于窗口的左上角
GUI.DragWindow(Rect(0,0,150,20));
}
在窗口中可以放置任意数量的控件.如果你想要Unity在窗口中自动布局控件(就像例子中一样)那你应该使用GUILayout.Window函数而不是GUI.Window函数.当使用GUILayout.Window函数时,unity会自动修改窗口的高度以适应内容.就像上面的例子展示的一样.
自动布局
在这里展示的绝大数例子,我都是用GUI类来创建菜单.GUI类需要我们手动地在屏幕上放置空间,在某些情况下,手动摆放控件很有用,但如果你想要unity为你自动布局控件,那你需要使用GUILayout类.这个类提供了许多像GUI一样的功能,但不要求你指定控件的大小
GUILayout.beginHorizontal和guilayout.beginVertical
默认情况下,当使用GUILayout函数时所有的视图中的组件都会竖直排列.你可以使用guilayout.beginhorizontal和guilayout.endhorizontal静态函数使控件相邻排放.每出现一次guilayout.BeginVertical必须有对应的GUILayout.EndVertical与其对应,每出现一次GUILayout.BeginHorizontal则必须有对应的GUILayouyudHorizontal与其对应
下面的例子展示了如何使用竖直和水平布局来创建复杂的表格
UserForm.js
#pragma strict这段代码看上去挺复杂的.这里面有些东西如果你不熟悉脚本编写的话可能看起来不熟悉.需要强调的一点是我使用一个静态窗口(不可拖动)的来把控件据合起来.窗口需要一个绘制其内部结构的函数.为此我是用了UseForm函数.
private var firstName:String = "First Name";
private var lastName:String = "Last Name";
private var age:uint = 0;
private var submitted:boolean = false;
private var windowRect0:Rect;
function Start()
{
}
function OnGUI()
{
var screenWidth = Screen.width;
var screenHeight = Screen.height;
var windowWidth = 300;
var windowHeight = 180;
var windowX = (screenWidth - windowWidth) / 2;
var windowY = (screenHeight - windowHeight) / 2;
//将窗口放置到屏幕中间
windowRect0 = Rect(windowX,windowY,windowWidth,windowHeight);
GUILayout.Window(0,windowRect0,UserForm,"User information");
}
function UserForm()
{
GUILayout.BeginVertical();
//first name
GUILayout.BeginHorizontal();
GUILayout.Label("First Name",GUILayout.Width(80));
firstName = GUILayout.TextField(firstName);
GUILayout.EndHorizontal();
//last name
GUILayout.BeginHorizontal();
GUILayout.Label("Last Name",GUILayout.Width(80));
lastName = GUILayout.TextField(lastName);
GUILayout.EndHorizontal();
//Age
GUILayout.BeginHorizontal();
GUILayout.Label("Age",GUILayout.Width(80));
var ageText = GUILayout.TextField(age.ToString());
var newAge = age;
if(uint.TryParse(ageText,newAge))
{
age = newAge;
}
GUILayout.EndHorizontal();
if(GUILayout.Button("Submit"))
{
submitted = true;
}
if(GUILayout.Button("Reset"))
{
firstName = "First Name";
lastName = "Last Name";
age = 0;
submitted = false;
}
if(submitted)
{
GUILayout.Label("submitted!");
}
GUILayout.EndVertical();
}
用户表格开头使用竖直布局.因为当使用自动布局时竖直布局是默认的,所以显式的指定它是可有可无的.
之后每一个字段都是用水平布局创建,标签旁边跟着文本.标签被显式的使用GUILayout.Width()函数设置为80像素.我这样做是为了确保所有的文本都整洁的对齐.文本域扩展填充窗口的剩余部分.
在两个文本之后,添加了两个竖直布局的按钮.按钮将扩展填充区域其高度由按钮的内容决定.
不要忘了常使用GUILayout.EndVertical()或GUILayout.EndHorizontal()函数结果竖直或水平布局.
脚本UserForm.js将会产生如下结果:
你可能也想看看GUI.BeginGroup,GUILayout.BeginArea和GUI.BeginScrollView.所有的这些函数都可以被用于创建组(或者可以自动布局的区域),这些组或者区域可以用于保持控件在特定区域内聚合起来.
样式和皮肤
unity为所有的GUI控件提供了默认的外观.作为一个快速解决方案的话默认的样式足够用了,但你可能不想在你要面市的游戏中使用unity的默认GUI样式.
GUISkin
使用自定义的GUISkin你可以更改按钮,标签,滑块,和滚动条的外观.
GUISkin是一个套件(asset),你可以从主菜单中选择Assets>Create>GUISkin来创建它.
如果你在项目视图(project view)里选择了GUISkin,你可以为你可创建的多种控件编辑单独的样式设置.
要使用你自定义的皮肤将默认皮肤替换掉,在GUI脚本里面设置GUI.skin属性为你自定义的皮肤.将GUI.skin属性设置为null将还原回默认的GUISkin.
GUIStyle
GUISkin只是一系列GUIStyle样式的集合.GUIStyle定义了一个控件所有可变状态的样式.一个控件有以下状态:
- 正常状态:控件的默认状态.鼠标既没有悬停到控件上控件也没有获得系统焦点.
- 悬停状态:鼠标当前悬停在控件上
- 注视状态:当前正选择控件,选中的控件将会接受键盘输入.这个状态对于按钮和可编辑文本控件.
- 活动状态:控件被点击,这个状态对于按钮,滑块和滚动条都是合法的.
GUIStyle可以在没有GUISkin的情况下使用以便改写控件的样式.要使用GUIStyle,简单的在脚本中创建一个GUIStyle类型的公开变量并在检视器(inspecot),当你想要为控件应用样式.简单把样式作为控件函数的最后一个参数数传进去就行.
作为练习,下载下面的Unity包并将其导入一个新的Unity工程中:
使用下面代码,创建一个名称为LevelSelect.js的脚本.
LevelSelect.js
#pragma strict你可能注意到这就是上面按钮例子里面的代码,但是现在我们要为这些按钮设置一套自定的皮肤.
function OnGUI()
{
var groundWidth = 120;
var groundHeight = 150;
var screenWidth = Screen.width;
var screenHeight = Screen.height;
var groupx = (screenWidth - groundWidth) / 2;
var groupy = (screenHeight - groundHeight) / 2;
GUI.BeginGroup(Rect(groupx,groupy,groundWidth,groundHeight));
GUI.Box(Rect(0,0,groundWidth,groundHeight),"Level Select");
if(GUI.Button(Rect(10,30,100,30),"Level 1"))
{
Application.LoadLevel(1);
}
if(GUI.Button(Rect(10,70,100,30),"Level 2"))
{
Application.LoadLevel(2);
}
if(GUI.Button(Rect(10,110,100,30),"Level 3"))
{
Application.LoadLevel(3);
}
GUI.EndGroup();
}
在检视器(inspector)中,在Button Style属性中设置如下选项:
- Normal.Background: roundedButton_Normal.psd
- Normal.Text Color: (0, 0, 0, 255)
- Hover.Background: roundedButton_Hover.psd
- Hover.Text Color: (0, 0, 0, 255)
- Active.Background: roundedButton_Active.psd
- Active.Text Color: (80, 80, 255, 255)
- Border.Left: 6
- Border.Right: 6
- Border.Top: 6
- Border.Bottom: 6
- Padding.Left: 6
- Padding.Right: 6
- Padding.Top: 12
- Padding.Bottom: 6
- Font: SHOWG
- Alignment: Middle Center
你会得到类似于下面的展示的.
Demo
下面是一个交互demo,它展示了如何使用多种控件,demo包含如下场景:
- GUI基础:描述了OnGUI函数并展示了一个使用按钮和箱子(box)组件的例子
- 控件:这个场景展示了多种你能创建的组件
- 自定义:如何创建皮肤和样式
- 布局:讲述了固定和自动布局模式.也展示了一个使用竖直和水平布局的例子
- 扩展Unity编辑器:这个场景展示了一个如何在unity中创建自定义编辑器窗口.下载这个unity工程并将其加载到Unity来看自定义的编辑器窗口.
下载并解压zip文件,在unity编辑器中打开Assets/Scenes/Introduction.unity场景文件.