创建游戏界面 (UI)
阅读本文大概需要 10 分钟
什么是游戏界面?
游戏界面指游戏软件的用户界面,包括游戏画面中的按钮、动画、文字、窗口等与游戏用户直接或间接接触的游戏设计元素。
如何创建游戏界面?
- 首先在工程内容库中创建新的 UI 文件:点击工程内容的 UI 页签,点击新建 UI 按钮
- 然后双击打开新创建的 UI 文件,会弹出 UI 编辑器
- 我们可以在 UI 编辑器中制作理想的游戏界面,并点击保存,即可保存此 UI 文件
UI 编辑器模块说明:
UI编辑器包括工具栏、系统UI控件和自定义UI列表、设计器、对象列表、属性面板、本地资源库、脚本列表:
- 工具栏:放置保存、按键绑定、视图等常用设置功能。
- 系统UI控件和自定义UI列表:放置通用的UI控件,以及自定义控件。
- 设计器:展示界面效果,并可以对可视化的UI控件进行操作和修改。
- 对象列表:放置所有UI控件,方便用户操作与管理。
- 属性面板:展示选中的UI控件的所有属性,方便用户修改参数以达到预期效果。
- 本地资源库:展示本地资源库中的所有UI贴图资源,方便拖拽到控件更换资源。
- 脚本列表:展示工程内容中的所有脚本,方便拖拽到根目录Root的属性中。
如何使用 UI 编辑器拼接 UI?
- 首先我们要理解UI的基础控件都有哪些?
- 包括:容器、图片、按钮、文本、输入框、进度条、滚动框等,具体UI控件的相关功能教程请见手册中的其他部分
- 我们在分析出一个UI的所有基本控件后,就可以将相应的控件拖入主视口内进行拼接UI。
- 例如我们想要拼接一个选择性别的界面。
- 首先我们拖入一个图片控件,作为界面的背景。
- 可以调整图片的位置与大小,将图片放置在比较合理的位置;
- 将对齐修改为水平方向为中心对齐,垂直方向为顶端对齐,方便界面适配;
- 设置图片的样式。将资源库中的UI资源拖入到图片中,修改图片表现样式。
- 其次我们拖入一个文本控件,作为界面的文字介绍。
- 可以设置文字的位置与大小,将文字放置在比较合理的位置;
- 将对齐修改为水平方向为中心对齐,垂直方向为顶端对齐,方便界面适配;
- 设置文本内容,将文本修改为“请选择你的性别”,大小设置为24;
- 设置文本样式,将文本颜色修改为黑色,对齐方式修改为居中。
- 然后我们拖入三个按钮控件,作为性别选项和确认功能的UI,这样就完成了初步的UI拼接。
- 分别设置按钮的位置与大小,将按钮放置在比较合理的位置;
- 将三个按钮对齐修改为水平方向为中心对齐,垂直方向为顶端对齐,方便界面适配;
- 再拖入一个文本控件挂在确认按钮下,并将文本修改为“确认”,大小设置为24;
- 设置按钮的图片样式,将资源库中的UI资源拖入到图片中,修改图片。
如何将 UI 添加到游戏场景中?
- 在完成拼接 UI 后,我们将进行学习如何实现将 UI 添加到游戏场景中,并且能执行我们写在脚本中的逻辑。
方式一:在编辑器中将 UI 文件拖入场景
- 在主编辑器中,直接将UI文件拖入游戏场景中或对象列表中,就可以把UI文件实例化为UI对象;
- 使用这种方式不需要在脚本中编写如何添加UI,进入游戏后,对象列表中存在的UI对象会自动添加到游戏场景中
- 如果想要为UI文件编写逻辑(例如用按钮执行跳跃/选择性别),首先我们需要创建一个新的UI脚本。
- 打开刚才拼好的UI文件,在其根目录Root的属性下,将UI脚本拖入属性栏,完成UI脚本与UI文件的绑定。
- 最后点击UI编辑器菜单栏的【打开脚本】按钮,或者双击【工程内容】-【脚本】中新建的UI脚本,打开此UI文件所绑定的UI脚本,就可以编写相关的UI逻辑。
- 例如实现一个跳跃按钮的简单逻辑:
ts
@UIBind('')
export default class DefaultUI extends UIScript {
private character: Character;
/** 仅在游戏时间对非模板实例调用一次 */
protected onStart() {
//设置能否每帧触发onUpdate
this.canUpdate = false;
//找到对应的跳跃按钮
const jumpBtn = this.uiWidgetBase.findChildByPath('RootCanvas/Button_Jump') as Button
const attackBtn = this.uiWidgetBase.findChildByPath('RootCanvas/Button_Attack') as Button
//点击跳跃按钮,异步获取人物后执行跳跃
jumpBtn.onPressed.add(()=>{
if (this.character) {
this.character.jump();
} else {
Player.asyncGetLocalPlayer().then((player) => {
this.character = player.character;
//角色执行跳跃功能
this.character.jump();
});
}
})
}
@UIBind('')
export default class DefaultUI extends UIScript {
private character: Character;
/** 仅在游戏时间对非模板实例调用一次 */
protected onStart() {
//设置能否每帧触发onUpdate
this.canUpdate = false;
//找到对应的跳跃按钮
const jumpBtn = this.uiWidgetBase.findChildByPath('RootCanvas/Button_Jump') as Button
const attackBtn = this.uiWidgetBase.findChildByPath('RootCanvas/Button_Attack') as Button
//点击跳跃按钮,异步获取人物后执行跳跃
jumpBtn.onPressed.add(()=>{
if (this.character) {
this.character.jump();
} else {
Player.asyncGetLocalPlayer().then((player) => {
this.character = player.character;
//角色执行跳跃功能
this.character.jump();
});
}
})
}
- 由于在编辑器中被拖入对象列表中的UI文件已实例化为UI对象,游戏开始后所绑定的UI脚本内编写的逻辑会在游戏中执行,只要玩家点击跳跃按钮就能使角色跳起来了。
方式二 :在脚本中将 UI 文件创建为自定义 UI 组件,并挂在已有的 UI 对象内
- 使用createUIByName函数创建想要添加的UI文件为自定义UI控件,然后使用addChild函数将自定义UI控件挂在当前UI对象下的某个容器中,从而实现UI动态加载到游戏场景中。
ts
//比如在UI脚本中创建一个自定义UI控件,并挂在UI对象的某个容器下
@UIBind('')
export default class DefaultUI extends UIScript {
private character: Character;
/** 仅在游戏时间对非模板实例调用一次 */
protected onStart() {
//通过路径找到按钮和容器
const canvas_1=this.uiWidgetBase.findChildByPath('RootCanvas') as Canvas
const btn = this.uiWidgetBase.findChildByPath("RootCanvas/Button") as Button;
//点击此按钮后打开某个自定义UI
btn.onPressed.add(() => {
let uiprefab1= createUIByName('/NewUI_1.ui') as UserWidget
canvas_1.addChild(uiprefab1)
})
}
}
//比如在UI脚本中创建一个自定义UI控件,并挂在UI对象的某个容器下
@UIBind('')
export default class DefaultUI extends UIScript {
private character: Character;
/** 仅在游戏时间对非模板实例调用一次 */
protected onStart() {
//通过路径找到按钮和容器
const canvas_1=this.uiWidgetBase.findChildByPath('RootCanvas') as Canvas
const btn = this.uiWidgetBase.findChildByPath("RootCanvas/Button") as Button;
//点击此按钮后打开某个自定义UI
btn.onPressed.add(() => {
let uiprefab1= createUIByName('/NewUI_1.ui') as UserWidget
canvas_1.addChild(uiprefab1)
})
}
}
- 以上只介绍了两种常见的UI添加方法,编辑器还提供了很多其他方法
- 比如把项目中的UI文件动态创建成新UI对象并直接添加到画面中,不需要挂在已有UI对象内的某个容器下。使用这种创建方法时,需要将UI游戏逻辑编写在所创建的UI文件绑定的UI脚本中,类似于方式一。
ts
//比如在普通脚本中实例化一个UI文件作为新的UI对象
@Component
export default class NewScript extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
createUIByName('/NewUI_1.ui').addToViewport(0)
}
}
//比如在普通脚本中实例化一个UI文件作为新的UI对象
@Component
export default class NewScript extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
createUIByName('/NewUI_1.ui').addToViewport(0)
}
}
(进阶)如何使用 UI 脚本导出功能代替手动获取 UI 组件?
- 只有获取到UI控件之后,才能继续编写UI控件的游戏逻辑:
- 在前文中,我们使用findChildByPath函数用路径找到了UI对象中的各个UI控件;
- 而除了用findChildByPath函数逐一获取UI控件,还可以使用UI编辑器工具栏中的【导出所有脚本】和【UI脚本模板】功能高效的自动导出获取UI控件的脚本
- 导出所有脚本:
- 点击【导出所有脚本】按钮后,会弹出提示已导出成功的弹窗,同时把项目中所有UI文件内以小写开头的UI控件信息自动导入到名称为"UI文件名-generate"的脚本中完成声明,这些脚本将存放在ui-generate文件夹内
- 完成导出后,请将UI文件所绑定的UI脚本修改为继承"UI文件名_generate"脚本,之后就可以在UI文件所绑定的UI脚本中直接使用UI控件,无需再逐个使用findChildByPath函数获取UI控件
注意请勿在"UI文件名-generate"脚本内编写逻辑,每次执行【导出所有脚本】会重新导出所有UI控件信息,并且覆盖掉当前ui-generate文件夹内的全部脚本
UI脚本模板:
- 点击【UI脚本模板】按钮后,会打开脚本模板文件,在执行【导出所有脚本】时,会按照此脚本模板文件中的规则在"UI文件名-generate"脚本中填充代码,便于批量生成重复代码
ts
${Import}
@UIBind('${UIPath}')
export default class ${ClassName}_Generate extends ${SuperClassName} {
${Fileds}
/**
* onStart 之前触发一次
*/
protected onAwake() {
${Button_Start}this.${Button}.onClicked.add(()=>{
})${Button_End}
}
}
${Import}
@UIBind('${UIPath}')
export default class ${ClassName}_Generate extends ${SuperClassName} {
${Fileds}
/**
* onStart 之前触发一次
*/
protected onAwake() {
${Button_Start}this.${Button}.onClicked.add(()=>{
})${Button_End}
}
}
- ${Import} :用于自动化框架导入脚本引用
- ${Fileds} :用于自动化框架导入变量
- ${UIPath} :替换为脚本附加的 UI 路径
- ${ClassName} :替换为脚本的类名
- ${SuperClassName} :脚本的父类名称
- ${Author} :替换为用户名
- ${Time} :保存时间
- ${UIName} :UI 文件名字
- ${FileName} :脚本文件名字
- ${Button_Start}this.${Button}...Any Code;${Button_End} :会把所有导出的 Button 都填入到这个规则里。比如:脚本导出了两个 Button 变量 b1 和 b2 那么就会生成如下代码(其他组件同理)
- ${Button_Path} :可以替换为当前组件的路径,${Button_Start}${Button_Path}${Button_End},这样就会替换重复生成时,当前写入的组件的 path 路径。(其他组件同理)
- ${Button_HideStart}this.${Button}...Any Code;${Button_HideEnd} :会把所有没有导出的 Button 都填入到这个规则里。比如:脚本没有导出了两个 Button 变量 b1 和 b2 那么就会生成如下代码(其他组件同理)
ts
//例如根据模板:
${Button_Start}this.${Button}.onClicked.add(()=>{})${Button_End}
//自动生成的脚本:
this.b1.onClicked.add(()=>{});
this.b2.onClicked.add(()=>{});
//例如根据模板:
${Button_Start}this.${Button}.onClicked.add(()=>{})${Button_End}
//自动生成的脚本:
this.b1.onClicked.add(()=>{});
this.b2.onClicked.add(()=>{});