游戏UI
UI是游戏中必不可少的一部分,本节课我们需要制作一个UI,用来进行胜利提示、死亡倒计时、关卡进度。
1.创建UI
UI的制作包含两个步骤,分别是创建UI以及编写代码,下面我们就进行创建UI的步骤。
新建UI
①在工程内容中点击 “UI”
②点击 “新建UI”
③将UI的名称修改为 “GameUI”
制作胜利提示UI
创建好UI之后,我们双击UI就能进入UI编辑器,在这里需要根据游戏的实际需求来制作UI。
① 将图片控件拖动到UI上,并修改大小和颜色(不知道如何修改大小和颜色,可以观看视频)
② 将文本控件拖动到UI上,并修改大小和位置
③ 将图片控件的名称修改为"mBG"
④ 将文本控件的名称修改为"mTextBlock"
注意
这里的控件名,决定着后面控制UI的代码是否生效。因为代码本质上是通过控件名来查找到对应的控件,如果控件名称和代码中的代码不一致,那么就有可能导致代码查找不到对应的UI控件,最终产生报错
2.导出UI_generate脚本
当我们使用UI的时候,前提是需要先用代码查找到对应的控件。UI编辑器提供了一个“导出所有脚本”的功能,能够将查找控件的代码导出到一个脚本中,这个功能大大提升了我们制作UI的效率。下面给大家展示导出UI脚本的步骤:
导出步骤
①在UI编辑器中点击“导出所有脚本”(记得导出前先点击一下保存)
②弹出导出完毕窗口就代表UI导出成功了
导出结果
①如果是第一次进行UI脚本导出,就会在脚本下创建一个名为ui-generate的文件夹
②导出的UI脚本会以UI名+"_generate"来进行命名
③这部分代码就是用来查找到名字为 "mBG" 的控件的代码
④这部分代码就是用来查找到名字为 "mTextBlock" 的控件的代码
3.编写UI脚本
有了UI_generate脚本之后,我们就可以创建UI脚本来对UI进行控制了。
创建UI脚本
①在“脚本”下新建一个文件夹,命名为“UI”,这个文件夹用来专门存放UI脚本
②点击“新建脚本”
③将脚本的名称修改为"GameUI"
编写UI脚本
在GameUI脚本中编写如下内容:
下列内容主要是对GameUI_Generate脚本进行了继承,因此GameUI就能够通过this来访问到GameUI_Generate中的控件,从而实现了用代码控制UI的功能。
ts
import GameUI_Generate from "../ui-generate/GameUI_generate";
export default class GameUI extends GameUI_Generate {
protected onAwake(): void {
this.mTextBlock.text = "胜利提示"
}
}
import GameUI_Generate from "../ui-generate/GameUI_generate";
export default class GameUI extends GameUI_Generate {
protected onAwake(): void {
this.mTextBlock.text = "胜利提示"
}
}
为什么不直接使用UI_generate脚本?
因为当我们每次使用UI编辑器提供的“导出所有脚本”功能时,都会将UI_generate脚本重新覆盖一次。如果代码直接编写在UI_generate脚本中,这会导致我们编写的代码丢失。
4.尝试将UI进行显示
显示UI,编辑器提供了一套专门的API:UIService
在GameStart脚本中添加一行代码:
这行代码的作用就是将UI脚本所代表的UI显示在游戏画面中
ts
import { LevelManager } from "./LevelManager";
import GameUI from "./UI/GameUI";
@Component
export default class GameStart extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected async onStart(): Promise<void> {
if(SystemUtil.isClient()){
LevelManager.instance.init()
UIService.show(GameUI)
}
}
/**
* 周期函数 每帧执行
* 此函数执行需要将this.useUpdate赋值为true
* @param dt 当前帧与上一帧的延迟 / 秒
*/
protected onUpdate(dt: number): void {
}
/** 脚本被销毁时最后一帧执行完调用此函数 */
protected onDestroy(): void {
}
}
import { LevelManager } from "./LevelManager";
import GameUI from "./UI/GameUI";
@Component
export default class GameStart extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected async onStart(): Promise<void> {
if(SystemUtil.isClient()){
LevelManager.instance.init()
UIService.show(GameUI)
}
}
/**
* 周期函数 每帧执行
* 此函数执行需要将this.useUpdate赋值为true
* @param dt 当前帧与上一帧的延迟 / 秒
*/
protected onUpdate(dt: number): void {
}
/** 脚本被销毁时最后一帧执行完调用此函数 */
protected onDestroy(): void {
}
}
添加了上面这一行后,运行游戏,就能看UI了,并且UI还显示出了我们设定的文本:
4.胜利提示
有了GameUI以及GameUI脚本后,我们就需要开始编写具体的业务逻辑了。胜利提示功能就是当角色到达终点后,将GameUI显示出来,并把GameUI上的文本控件的内容修改为“胜利啦!”
制作这个功能的核心逻辑就是让GameUI脚本开启一个事件监听,然后当角色到达终点的逻辑里进行事件派发,通知GameUI展示出UI的内容。
所以首先要在GameUI脚本中开启一个本地事件监听:
该脚本有如下几个要点:
① 在UI第一次显示的时候,将背景和文本隐藏
② 对本地事件"Victory"进行监听,事件触发时将背景和文本进行展示
③ 在本地事件触发时,将mTextBlock控件的文本内容修改为“胜利啦!”
ts
import CheckPointTrigger from "../CheckPointTrigger";
import GameUI_Generate from "../ui-generate/GameUI_generate";
export default class GameUI extends GameUI_Generate {
protected onAwake(): void {
// UI第一次显示的时候,将背景和文本隐藏
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
Event.addLocalListener("Victory",()=>{
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
this.mTextBlock.text = "胜利啦!"
})
}
}
import CheckPointTrigger from "../CheckPointTrigger";
import GameUI_Generate from "../ui-generate/GameUI_generate";
export default class GameUI extends GameUI_Generate {
protected onAwake(): void {
// UI第一次显示的时候,将背景和文本隐藏
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
Event.addLocalListener("Victory",()=>{
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
this.mTextBlock.text = "胜利啦!"
})
}
}
有了监听,对应就需要有派发,我们需要在角色到达终点时派发Victory事件。
在CheckPointTrigger脚本中进行事件派发:
本次新增的逻辑:
在判断到角色进入终点触发器时,如果进入的角色是当前客户端,就派发"Victory"事件,从而让GameUI展示出UI内容。
ts
import { LevelManager } from "./LevelManager"
@Component
export default class CheckPointTrigger extends Script {
@Property({displayName:"序号"})
public pointNumber:number = 0
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
let trigger = this.gameObject as Trigger
trigger.onEnter.add((other:GameObject)=>{
// 进入的物体是否是角色
if(other instanceof Character){
// 进入的角色 是否是 当前客户端角色
if(other == Player.localPlayer.character){
// 本地事件通信(派发)
Event.dispatchToLocal("CheckPoint",this)
// 播放特效
EffectService.playAtPosition("89097",this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("38193")
}
if(this.pointNumber==-1){
// 播放动作
other.loadAnimation("14509").play()
// 播放特效
EffectService.playAtPosition("142750",this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("47425")
if(other == Player.localPlayer.character){
Event.dispatchToLocal("Victory")
}
}
}
})
}
}
import { LevelManager } from "./LevelManager"
@Component
export default class CheckPointTrigger extends Script {
@Property({displayName:"序号"})
public pointNumber:number = 0
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
let trigger = this.gameObject as Trigger
trigger.onEnter.add((other:GameObject)=>{
// 进入的物体是否是角色
if(other instanceof Character){
// 进入的角色 是否是 当前客户端角色
if(other == Player.localPlayer.character){
// 本地事件通信(派发)
Event.dispatchToLocal("CheckPoint",this)
// 播放特效
EffectService.playAtPosition("89097",this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("38193")
}
if(this.pointNumber==-1){
// 播放动作
other.loadAnimation("14509").play()
// 播放特效
EffectService.playAtPosition("142750",this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("47425")
if(other == Player.localPlayer.character){
Event.dispatchToLocal("Victory")
}
}
}
})
}
}
效果演示
当角色进入终点,就会展示出GameUI
5.死亡倒计时
死亡倒计时的制作逻辑和胜利提示类似,都是通过一个事件来进行触发。
所以可以在GameUI脚本中开启一个对死亡事件的监听:
本次新增的逻辑:
在onAwake函数中对本地事件“Death”进行监听,监听到事件之后,先将背景和文本控件进行显示。然后开启一个间隔函数,每隔一秒更新一次文本,倒计时结束时就关闭间隔函数并隐藏背景和文本控件。
ts
import GameUI_Generate from "../ui-generate/GameUI_generate";
export default class GameUI extends GameUI_Generate {
protected onAwake(): void {
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
Event.addLocalListener("Victory", () => {
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
this.mTextBlock.text = "胜利啦!"
})
Event.addLocalListener("Death", () => {
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
let time = 3;
this.mTextBlock.text = time.toString()
let handle = setInterval(() => {
if (time == 1) {
clearInterval(handle)
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
}
time--
this.mTextBlock.text = time.toString()
}, 1000)
})
}
}
import GameUI_Generate from "../ui-generate/GameUI_generate";
export default class GameUI extends GameUI_Generate {
protected onAwake(): void {
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
Event.addLocalListener("Victory", () => {
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
this.mTextBlock.text = "胜利啦!"
})
Event.addLocalListener("Death", () => {
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
let time = 3;
this.mTextBlock.text = time.toString()
let handle = setInterval(() => {
if (time == 1) {
clearInterval(handle)
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
}
time--
this.mTextBlock.text = time.toString()
}, 1000)
})
}
}
接着,我们还需要在角色死亡的时候,派发这个死亡事件。
在LevelManager脚本中添加如下代码:
ts
import CheckPointTrigger from "./CheckPointTrigger"
import GameUI from "./UI/GameUI"
/**
* 关卡管理器
*/
export class LevelManager{
// 单例模式
private static _instacne:LevelManager
public static get instance():LevelManager{
if(LevelManager._instacne==null){
LevelManager._instacne = new LevelManager()
}
return LevelManager._instacne
}
/**死亡触发器 */
private _deathTrigger:Trigger
/**复活位置 */
private _rebornPosition:Vector = new Vector(10,0,420)
public async init(){
this._deathTrigger = await GameObject.asyncFindGameObjectById("299CDDA6") as Trigger
this._deathTrigger.onEnter.add((other:GameObject)=>{
// 当进入的物体是角色类型
if(other instanceof Character){
// 让角色死亡
this.charDeath(other)
}
})
Event.addLocalListener("CheckPoint",(checkPointTrigger:CheckPointTrigger)=>{
this._rebornPosition = checkPointTrigger.gameObject.worldTransform.position.clone()
})
}
/**让角色死亡 */
private charDeath(char:Character){
// 开启布娃娃属性
char.ragdollEnabled = true
// 播放特效
EffectService.playAtPosition("27421",char.worldTransform.position)
// 播放音效
SoundService.playSound("120841")
setTimeout(() => {
// 让角色复活
this.charReborn(char)
}, 3000);
if(char==Player.localPlayer.character){
Event.dispatchToLocal("Death")
}
}
/**让角色复活 */
private charReborn(char:Character){
if(char == Player.localPlayer.character){
// 将角色的位置改变到复活点
char.worldTransform.position = this._rebornPosition.clone()
}
// 关闭布娃娃属性
char.ragdollEnabled = false
}
}
import CheckPointTrigger from "./CheckPointTrigger"
import GameUI from "./UI/GameUI"
/**
* 关卡管理器
*/
export class LevelManager{
// 单例模式
private static _instacne:LevelManager
public static get instance():LevelManager{
if(LevelManager._instacne==null){
LevelManager._instacne = new LevelManager()
}
return LevelManager._instacne
}
/**死亡触发器 */
private _deathTrigger:Trigger
/**复活位置 */
private _rebornPosition:Vector = new Vector(10,0,420)
public async init(){
this._deathTrigger = await GameObject.asyncFindGameObjectById("299CDDA6") as Trigger
this._deathTrigger.onEnter.add((other:GameObject)=>{
// 当进入的物体是角色类型
if(other instanceof Character){
// 让角色死亡
this.charDeath(other)
}
})
Event.addLocalListener("CheckPoint",(checkPointTrigger:CheckPointTrigger)=>{
this._rebornPosition = checkPointTrigger.gameObject.worldTransform.position.clone()
})
}
/**让角色死亡 */
private charDeath(char:Character){
// 开启布娃娃属性
char.ragdollEnabled = true
// 播放特效
EffectService.playAtPosition("27421",char.worldTransform.position)
// 播放音效
SoundService.playSound("120841")
setTimeout(() => {
// 让角色复活
this.charReborn(char)
}, 3000);
if(char==Player.localPlayer.character){
Event.dispatchToLocal("Death")
}
}
/**让角色复活 */
private charReborn(char:Character){
if(char == Player.localPlayer.character){
// 将角色的位置改变到复活点
char.worldTransform.position = this._rebornPosition.clone()
}
// 关闭布娃娃属性
char.ragdollEnabled = false
}
}
效果演示
角色进入死亡区域就会触发死亡倒计时
6.关卡进度
6.1.制作关卡进度UI
还是在GameUI的基础上进行制作,主要是在GameUI上添加了一个进度条和一个进度提示文本
① 拖入一个进度条控件,并调整大小和位置
② 拖入一个文本控件,并调整大小和位置
③ 将进度条控件的名称修改为 "mLevelProgress"
④ 将文本控件的名称修改为 "mLevelText"
⑤ 点击“保存”
⑥ 点击“导出所有脚本”
帮你一波
如果大家不想自己制作UI,可以移步至7.4 我为大家提供好了UI文件
6.2.onShow函数
onShow函数是属于UI脚本的生命周期函数,onShow会在UI被显示的时候进行执行。
我们目前的需求是需要GameUI显示出关卡进度,那么首要任务就是得让GameUI能够接收到最大关卡数和当前关卡数。
所以现在需要在GameUI脚本中,通过onShow来接收到最大关卡数和当前关卡数,在第一次显示UI的时候就更新进度条的进度以及进度文本的内容。
除了第一次显示GameUI的时候需要更新进度条,在玩家每一次进入新的关卡的时候,也需要更新进度条,我们可以通过对本地事件CheckPoint进行监听,来实现进度更新。
将上述逻辑添加到GameUI脚本中:
ts
import CheckPointTrigger from "../CheckPointTrigger";
import GameUI_Generate from "../ui-generate/GameUI_generate";
export default class GameUI extends GameUI_Generate {
/**最大关卡数 */
maxLevelNum:number = 0
/**当前关卡数 */
nowLevelNum:number = 0
protected onAwake(): void {
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
Event.addLocalListener("Victory",()=>{
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
this.mTextBlock.text = "胜利啦!"
})
Event.addLocalListener("Death",()=>{
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
let time = 3;
this.mTextBlock.text = time.toString()
let handle = setInterval(()=>{
if(time==1){
clearInterval(handle)
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
}
time--
this.mTextBlock.text = time.toString()
},1000)
})
Event.addLocalListener("CheckPoint",(chekPoint:CheckPointTrigger)=>{
this.nowLevelNum = chekPoint.pointNumber
// 更新进度条
this.freshProgress()
})
}
onShow(maxLevelNum:number,nowLevelNum:number){
// 最大关卡数
this.maxLevelNum = maxLevelNum
// 当前关卡数
this.nowLevelNum = nowLevelNum
this.freshProgress()
}
private freshProgress(){
if(this.nowLevelNum == -1){
this.mLevelText.text = "第"+this.maxLevelNum+"关"
this.mLevelProgress.currentValue = 1
return
}
this.mLevelText.text = "第"+this.nowLevelNum+"关"
this.mLevelProgress.currentValue = this.nowLevelNum / this.maxLevelNum
}
}
import CheckPointTrigger from "../CheckPointTrigger";
import GameUI_Generate from "../ui-generate/GameUI_generate";
export default class GameUI extends GameUI_Generate {
/**最大关卡数 */
maxLevelNum:number = 0
/**当前关卡数 */
nowLevelNum:number = 0
protected onAwake(): void {
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
Event.addLocalListener("Victory",()=>{
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
this.mTextBlock.text = "胜利啦!"
})
Event.addLocalListener("Death",()=>{
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
let time = 3;
this.mTextBlock.text = time.toString()
let handle = setInterval(()=>{
if(time==1){
clearInterval(handle)
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
}
time--
this.mTextBlock.text = time.toString()
},1000)
})
Event.addLocalListener("CheckPoint",(chekPoint:CheckPointTrigger)=>{
this.nowLevelNum = chekPoint.pointNumber
// 更新进度条
this.freshProgress()
})
}
onShow(maxLevelNum:number,nowLevelNum:number){
// 最大关卡数
this.maxLevelNum = maxLevelNum
// 当前关卡数
this.nowLevelNum = nowLevelNum
this.freshProgress()
}
private freshProgress(){
if(this.nowLevelNum == -1){
this.mLevelText.text = "第"+this.maxLevelNum+"关"
this.mLevelProgress.currentValue = 1
return
}
this.mLevelText.text = "第"+this.nowLevelNum+"关"
this.mLevelProgress.currentValue = this.nowLevelNum / this.maxLevelNum
}
}
6.3.用Map来管理所有的检查点
在上一点,我们编写了逻辑,来根据获取到的最大关卡号以及当前关卡号对进度进行更新。
但是我们现在还没有获取最大关卡号的逻辑,所以我们可以在LevelManager脚本中,添加一个Map,用于存储所有的检查点脚本。
在LevelManager脚本中添加一个Map,用于存储所有的检查点:
向关卡管理器中添加了一个成员变量checkPointMap,这个变量是一个Map类型,其中Map的key为number类型,用来填入检查点的序号;Map的value为CheckPointTrigger类型
ts
import CheckPointTrigger from "./CheckPointTrigger"
import GameUI from "./UI/GameUI"
/**
* 关卡管理器
*/
export class LevelManager{
// 单例模式
private static _instacne:LevelManager
public static get instance():LevelManager{
if(LevelManager._instacne==null){
LevelManager._instacne = new LevelManager()
}
return LevelManager._instacne
}
/**死亡触发器 */
private _deathTrigger:Trigger
/**复活位置 */
private _rebornPosition:Vector = new Vector(10,0,420)
/**所有的检查点脚本 */
public checkPointMap:Map<number,CheckPointTrigger> = new Map()
public async init(){
this._deathTrigger = await GameObject.asyncFindGameObjectById("299CDDA6") as Trigger
this._deathTrigger.onEnter.add((other:GameObject)=>{
// 当进入的物体是角色类型
if(other instanceof Character){
// 让角色死亡
this.charDeath(other)
}
})
Event.addLocalListener("CheckPoint",(checkPointTrigger:CheckPointTrigger)=>{
this._rebornPosition = checkPointTrigger.gameObject.worldTransform.position.clone()
})
}
/**让角色死亡 */
private charDeath(char:Character){
// 开启布娃娃属性
char.ragdollEnabled = true
// 播放特效
EffectService.playAtPosition("27421",char.worldTransform.position)
// 播放音效
SoundService.playSound("120841")
setTimeout(() => {
// 让角色复活
this.charReborn(char)
}, 3000);
if(char==Player.localPlayer.character){
Event.dispatchToLocal("Death")
}
}
/**让角色复活 */
private charReborn(char:Character){
if(char == Player.localPlayer.character){
// 将角色的位置改变到复活点
char.worldTransform.position = this._rebornPosition.clone()
}
// 关闭布娃娃属性
char.ragdollEnabled = false
}
}
import CheckPointTrigger from "./CheckPointTrigger"
import GameUI from "./UI/GameUI"
/**
* 关卡管理器
*/
export class LevelManager{
// 单例模式
private static _instacne:LevelManager
public static get instance():LevelManager{
if(LevelManager._instacne==null){
LevelManager._instacne = new LevelManager()
}
return LevelManager._instacne
}
/**死亡触发器 */
private _deathTrigger:Trigger
/**复活位置 */
private _rebornPosition:Vector = new Vector(10,0,420)
/**所有的检查点脚本 */
public checkPointMap:Map<number,CheckPointTrigger> = new Map()
public async init(){
this._deathTrigger = await GameObject.asyncFindGameObjectById("299CDDA6") as Trigger
this._deathTrigger.onEnter.add((other:GameObject)=>{
// 当进入的物体是角色类型
if(other instanceof Character){
// 让角色死亡
this.charDeath(other)
}
})
Event.addLocalListener("CheckPoint",(checkPointTrigger:CheckPointTrigger)=>{
this._rebornPosition = checkPointTrigger.gameObject.worldTransform.position.clone()
})
}
/**让角色死亡 */
private charDeath(char:Character){
// 开启布娃娃属性
char.ragdollEnabled = true
// 播放特效
EffectService.playAtPosition("27421",char.worldTransform.position)
// 播放音效
SoundService.playSound("120841")
setTimeout(() => {
// 让角色复活
this.charReborn(char)
}, 3000);
if(char==Player.localPlayer.character){
Event.dispatchToLocal("Death")
}
}
/**让角色复活 */
private charReborn(char:Character){
if(char == Player.localPlayer.character){
// 将角色的位置改变到复活点
char.worldTransform.position = this._rebornPosition.clone()
}
// 关闭布娃娃属性
char.ragdollEnabled = false
}
}
6.4 让Map获取到所有的检查点
上一点我们在LevelManger脚本中添加了一个checkPointMap,所以现在就需要让每个CheckPointTrigger脚本,在启动的时候,将自己设置到checkPointMap中。
在CheckPointTrigger脚本中添加一行代码:
tsx
import { LevelManager } from "./LevelManager"
@Component
export default class CheckPointTrigger extends Script {
@Property({ displayName: "序号" })
public pointNumber: number = 0
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
LevelManager.instance.checkPointMap.set(this.pointNumber, this);
let trigger = this.gameObject as Trigger
trigger.onEnter.add((other: GameObject) => {
// 进入的物体是否是角色
if (other instanceof Character) {
// 进入的角色 是否是 当前客户端角色
if (other == Player.localPlayer.character) {
// 本地事件通信(派发)
Event.dispatchToLocal("CheckPoint", this)
// 播放特效
EffectService.playAtPosition("89097", this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("38193")
}
if (this.pointNumber == -1) {
// 播放动作
other.loadAnimation("14509").play()
// 播放特效
EffectService.playAtPosition("142750", this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("47425")
if (other == Player.localPlayer.character) {
Event.dispatchToLocal("Victory")
}
}
}
})
}
}
import { LevelManager } from "./LevelManager"
@Component
export default class CheckPointTrigger extends Script {
@Property({ displayName: "序号" })
public pointNumber: number = 0
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
LevelManager.instance.checkPointMap.set(this.pointNumber, this);
let trigger = this.gameObject as Trigger
trigger.onEnter.add((other: GameObject) => {
// 进入的物体是否是角色
if (other instanceof Character) {
// 进入的角色 是否是 当前客户端角色
if (other == Player.localPlayer.character) {
// 本地事件通信(派发)
Event.dispatchToLocal("CheckPoint", this)
// 播放特效
EffectService.playAtPosition("89097", this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("38193")
}
if (this.pointNumber == -1) {
// 播放动作
other.loadAnimation("14509").play()
// 播放特效
EffectService.playAtPosition("142750", this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("47425")
if (other == Player.localPlayer.character) {
Event.dispatchToLocal("Victory")
}
}
}
})
}
}
6.5.调整GameUI显示的时机
由于现在GameUI需要在显示的时候接收到最大关卡号,所以就不能在GameStart脚本中调用UIService来对其进行显示了,因为GameStart脚本启动的比较快,可能会出现LevelManager还没有获取到所有检查点的时候,GameStart脚本就已经将GameUI显示出来了,这就会导致GameUI获取到错误的最大关卡号。
所以我们首先需要将GameStart脚本中显示GameUI的逻辑删除:
ts
import { LevelManager } from "./LevelManager";
import GameUI from "./UI/GameUI";
@Component
export default class GameStart extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected async onStart(): Promise<void> {
if(SystemUtil.isClient()){
LevelManager.instance.init()
UIService.show(GameUI)
}
}
/**
* 周期函数 每帧执行
* 此函数执行需要将this.useUpdate赋值为true
* @param dt 当前帧与上一帧的延迟 / 秒
*/
protected onUpdate(dt: number): void {
}
/** 脚本被销毁时最后一帧执行完调用此函数 */
protected onDestroy(): void {
}
}
import { LevelManager } from "./LevelManager";
import GameUI from "./UI/GameUI";
@Component
export default class GameStart extends Script {
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected async onStart(): Promise<void> {
if(SystemUtil.isClient()){
LevelManager.instance.init()
UIService.show(GameUI)
}
}
/**
* 周期函数 每帧执行
* 此函数执行需要将this.useUpdate赋值为true
* @param dt 当前帧与上一帧的延迟 / 秒
*/
protected onUpdate(dt: number): void {
}
/** 脚本被销毁时最后一帧执行完调用此函数 */
protected onDestroy(): void {
}
}
在6.4.中,我们将各个检查点都添加到了关卡管理器的checkPointMap中,因此我们只需要获取map的count即可获得最大关卡数。
所以现在我们就需要在LevelManger脚本中,延迟2000毫秒,等待所有检查点获取完毕之后,再调用显示GameUI的逻辑,并且将最大关卡数传递给GameUI脚本。
由LevelManager脚本向GameUI发送最大关卡数:
本此新增的逻辑:
①UIService.show这个API的第二个参数是一个变长参数,可以传递任意长度的数值进去
②通过UIService.show将最大关卡数和当前关卡数传递给了GameUI
③显示UI的逻辑被延迟了2000毫秒。(这是为了防止检查点启动时序比关卡管理器慢,导致关卡管理器不能够在传递参数前获取到正确的关卡管理器数量)
ts
import CheckPointTrigger from "./CheckPointTrigger"
import GameUI from "./UI/GameUI"
/**
* 关卡管理器
*/
export class LevelManager{
// 单例模式
private static _instacne:LevelManager
public static get instance():LevelManager{
if(LevelManager._instacne==null){
LevelManager._instacne = new LevelManager()
}
return LevelManager._instacne
}
/**死亡触发器 */
private _deathTrigger:Trigger
/**复活位置 */
private _rebornPosition:Vector = new Vector(10,0,420)
/**所有的检查点脚本 */
public checkPointMap:Map<number,CheckPointTrigger> = new Map()
public async init(){
this._deathTrigger = await GameObject.asyncFindGameObjectById("299CDDA6") as Trigger
this._deathTrigger.onEnter.add((other:GameObject)=>{
// 当进入的物体是角色类型
if(other instanceof Character){
// 让角色死亡
this.charDeath(other)
}
})
Event.addLocalListener("CheckPoint",(checkPointTrigger:CheckPointTrigger)=>{
this._rebornPosition = checkPointTrigger.gameObject.worldTransform.position.clone()
})
setTimeout(() => {
UIService.show(GameUI,this.checkPointMap.size,0)
}, 2000);
}
/**让角色死亡 */
private charDeath(char:Character){
// 开启布娃娃属性
char.ragdollEnabled = true
// 播放特效
EffectService.playAtPosition("27421",char.worldTransform.position)
// 播放音效
SoundService.playSound("120841")
setTimeout(() => {
// 让角色复活
this.charReborn(char)
}, 3000);
if(char==Player.localPlayer.character){
Event.dispatchToLocal("Death")
}
}
/**让角色复活 */
private charReborn(char:Character){
if(char == Player.localPlayer.character){
// 将角色的位置改变到复活点
char.worldTransform.position = this._rebornPosition.clone()
}
// 关闭布娃娃属性
char.ragdollEnabled = false
}
}
import CheckPointTrigger from "./CheckPointTrigger"
import GameUI from "./UI/GameUI"
/**
* 关卡管理器
*/
export class LevelManager{
// 单例模式
private static _instacne:LevelManager
public static get instance():LevelManager{
if(LevelManager._instacne==null){
LevelManager._instacne = new LevelManager()
}
return LevelManager._instacne
}
/**死亡触发器 */
private _deathTrigger:Trigger
/**复活位置 */
private _rebornPosition:Vector = new Vector(10,0,420)
/**所有的检查点脚本 */
public checkPointMap:Map<number,CheckPointTrigger> = new Map()
public async init(){
this._deathTrigger = await GameObject.asyncFindGameObjectById("299CDDA6") as Trigger
this._deathTrigger.onEnter.add((other:GameObject)=>{
// 当进入的物体是角色类型
if(other instanceof Character){
// 让角色死亡
this.charDeath(other)
}
})
Event.addLocalListener("CheckPoint",(checkPointTrigger:CheckPointTrigger)=>{
this._rebornPosition = checkPointTrigger.gameObject.worldTransform.position.clone()
})
setTimeout(() => {
UIService.show(GameUI,this.checkPointMap.size,0)
}, 2000);
}
/**让角色死亡 */
private charDeath(char:Character){
// 开启布娃娃属性
char.ragdollEnabled = true
// 播放特效
EffectService.playAtPosition("27421",char.worldTransform.position)
// 播放音效
SoundService.playSound("120841")
setTimeout(() => {
// 让角色复活
this.charReborn(char)
}, 3000);
if(char==Player.localPlayer.character){
Event.dispatchToLocal("Death")
}
}
/**让角色复活 */
private charReborn(char:Character){
if(char == Player.localPlayer.character){
// 将角色的位置改变到复活点
char.worldTransform.position = this._rebornPosition.clone()
}
// 关闭布娃娃属性
char.ragdollEnabled = false
}
}
6.6.效果演示
前面的步骤如果没有问题,那么现在运行游戏,每当角色经过检查点,就会刷新关卡进度。
7.本节完整代码
7.1.GameUI
ts
import CheckPointTrigger from "../CheckPointTrigger";
import GameUI_Generate from "../ui-generate/GameUI_generate";
export default class GameUI extends GameUI_Generate {
/**最大关卡数 */
maxLevelNum:number = 0
/**当前关卡数 */
nowLevelNum:number = 0
protected onAwake(): void {
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
Event.addLocalListener("Victory",()=>{
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
this.mTextBlock.text = "胜利啦!"
})
Event.addLocalListener("Death",()=>{
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
let time = 3;
this.mTextBlock.text = time.toString()
let handle = setInterval(()=>{
if(time==1){
clearInterval(handle)
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
}
time--
this.mTextBlock.text = time.toString()
},1000)
})
Event.addLocalListener("CheckPoint",(chekPoint:CheckPointTrigger)=>{
this.nowLevelNum = chekPoint.pointNumber
// 更新进度条
this.freshProgress()
})
}
onShow(maxLevelNum:number,nowLevelNum:number){
// 最大关卡数
this.maxLevelNum = maxLevelNum
// 当前关卡数
this.nowLevelNum = nowLevelNum
this.freshProgress()
}
private freshProgress(){
if(this.nowLevelNum == -1){
this.mLevelText.text = "第"+this.maxLevelNum+"关"
this.mLevelProgress.currentValue = 1
return
}
this.mLevelText.text = "第"+this.nowLevelNum+"关"
this.mLevelProgress.currentValue = this.nowLevelNum / this.maxLevelNum
}
}
import CheckPointTrigger from "../CheckPointTrigger";
import GameUI_Generate from "../ui-generate/GameUI_generate";
export default class GameUI extends GameUI_Generate {
/**最大关卡数 */
maxLevelNum:number = 0
/**当前关卡数 */
nowLevelNum:number = 0
protected onAwake(): void {
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
Event.addLocalListener("Victory",()=>{
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
this.mTextBlock.text = "胜利啦!"
})
Event.addLocalListener("Death",()=>{
this.mBG.visibility = SlateVisibility.Visible
this.mTextBlock.visibility = SlateVisibility.Visible
let time = 3;
this.mTextBlock.text = time.toString()
let handle = setInterval(()=>{
if(time==1){
clearInterval(handle)
this.mBG.visibility = SlateVisibility.Hidden
this.mTextBlock.visibility = SlateVisibility.Hidden
}
time--
this.mTextBlock.text = time.toString()
},1000)
})
Event.addLocalListener("CheckPoint",(chekPoint:CheckPointTrigger)=>{
this.nowLevelNum = chekPoint.pointNumber
// 更新进度条
this.freshProgress()
})
}
onShow(maxLevelNum:number,nowLevelNum:number){
// 最大关卡数
this.maxLevelNum = maxLevelNum
// 当前关卡数
this.nowLevelNum = nowLevelNum
this.freshProgress()
}
private freshProgress(){
if(this.nowLevelNum == -1){
this.mLevelText.text = "第"+this.maxLevelNum+"关"
this.mLevelProgress.currentValue = 1
return
}
this.mLevelText.text = "第"+this.nowLevelNum+"关"
this.mLevelProgress.currentValue = this.nowLevelNum / this.maxLevelNum
}
}
7.2.CheckPointTrigger
ts
import { LevelManager } from "./LevelManager"
@Component
export default class CheckPointTrigger extends Script {
@Property({displayName:"序号"})
public pointNumber:number = 0
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
LevelManager.instance.checkPointMap.set(this.pointNumber,this);
let trigger = this.gameObject as Trigger
trigger.onEnter.add((other:GameObject)=>{
// 进入的物体是否是角色
if(other instanceof Character){
// 进入的角色 是否是 当前客户端角色
if(other == Player.localPlayer.character){
// 本地事件通信(派发)
Event.dispatchToLocal("CheckPoint",this)
// 播放特效
EffectService.playAtPosition("89097",this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("38193")
}
if(this.pointNumber==-1){
// 播放动作
other.loadAnimation("14509").play()
// 播放特效
EffectService.playAtPosition("142750",this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("47425")
if(other == Player.localPlayer.character){
Event.dispatchToLocal("Victory")
}
}
}
})
}
}
import { LevelManager } from "./LevelManager"
@Component
export default class CheckPointTrigger extends Script {
@Property({displayName:"序号"})
public pointNumber:number = 0
/** 当脚本被实例后,会在第一帧更新前调用此函数 */
protected onStart(): void {
LevelManager.instance.checkPointMap.set(this.pointNumber,this);
let trigger = this.gameObject as Trigger
trigger.onEnter.add((other:GameObject)=>{
// 进入的物体是否是角色
if(other instanceof Character){
// 进入的角色 是否是 当前客户端角色
if(other == Player.localPlayer.character){
// 本地事件通信(派发)
Event.dispatchToLocal("CheckPoint",this)
// 播放特效
EffectService.playAtPosition("89097",this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("38193")
}
if(this.pointNumber==-1){
// 播放动作
other.loadAnimation("14509").play()
// 播放特效
EffectService.playAtPosition("142750",this.gameObject.worldTransform.position)
// 播放音效
SoundService.playSound("47425")
if(other == Player.localPlayer.character){
Event.dispatchToLocal("Victory")
}
}
}
})
}
}
7.3.LevelManager
tsx
import CheckPointTrigger from "./CheckPointTrigger"
import GameUI from "./UI/GameUI"
/**
* 关卡管理器
*/
export class LevelManager {
// 单例模式
private static _instacne: LevelManager
public static get instance(): LevelManager {
if (LevelManager._instacne == null) {
LevelManager._instacne = new LevelManager()
}
return LevelManager._instacne
}
/**死亡触发器 */
private _deathTrigger: Trigger
/**复活位置 */
private _rebornPosition: Vector = new Vector(10, 0, 420)
/**所有的检查点脚本 */
public checkPointMap: Map<number, CheckPointTrigger> = new Map()
public async init() {
this._deathTrigger = await GameObject.asyncFindGameObjectById("299CDDA6") as Trigger
this._deathTrigger.onEnter.add((other: GameObject) => {
// 当进入的物体是角色类型
if (other instanceof Character) {
// 让角色死亡
this.charDeath(other)
}
})
Event.addLocalListener("CheckPoint", (checkPointTrigger: CheckPointTrigger) => {
this._rebornPosition = checkPointTrigger.gameObject.worldTransform.position.clone()
})
setTimeout(() => {
UIService.show(GameUI, this.checkPointMap.size, 0)
}, 2000);
}
/**让角色死亡 */
public charDeath(char: Character) {
// 开启布娃娃属性
char.ragdollEnabled = true
// 播放特效
EffectService.playAtPosition("27421", char.worldTransform.position)
// 播放音效
SoundService.playSound("120841")
setTimeout(() => {
// 让角色复活
this.charReborn(char)
}, 3000);
if (char == Player.localPlayer.character) {
Event.dispatchToLocal("Death")
}
}
/**让角色复活 */
public charReborn(char: Character) {
if (char == Player.localPlayer.character) {
// 将角色的位置改变到复活点
char.worldTransform.position = this._rebornPosition.clone()
}
// 关闭布娃娃属性
char.ragdollEnabled = false
}
}
import CheckPointTrigger from "./CheckPointTrigger"
import GameUI from "./UI/GameUI"
/**
* 关卡管理器
*/
export class LevelManager {
// 单例模式
private static _instacne: LevelManager
public static get instance(): LevelManager {
if (LevelManager._instacne == null) {
LevelManager._instacne = new LevelManager()
}
return LevelManager._instacne
}
/**死亡触发器 */
private _deathTrigger: Trigger
/**复活位置 */
private _rebornPosition: Vector = new Vector(10, 0, 420)
/**所有的检查点脚本 */
public checkPointMap: Map<number, CheckPointTrigger> = new Map()
public async init() {
this._deathTrigger = await GameObject.asyncFindGameObjectById("299CDDA6") as Trigger
this._deathTrigger.onEnter.add((other: GameObject) => {
// 当进入的物体是角色类型
if (other instanceof Character) {
// 让角色死亡
this.charDeath(other)
}
})
Event.addLocalListener("CheckPoint", (checkPointTrigger: CheckPointTrigger) => {
this._rebornPosition = checkPointTrigger.gameObject.worldTransform.position.clone()
})
setTimeout(() => {
UIService.show(GameUI, this.checkPointMap.size, 0)
}, 2000);
}
/**让角色死亡 */
public charDeath(char: Character) {
// 开启布娃娃属性
char.ragdollEnabled = true
// 播放特效
EffectService.playAtPosition("27421", char.worldTransform.position)
// 播放音效
SoundService.playSound("120841")
setTimeout(() => {
// 让角色复活
this.charReborn(char)
}, 3000);
if (char == Player.localPlayer.character) {
Event.dispatchToLocal("Death")
}
}
/**让角色复活 */
public charReborn(char: Character) {
if (char == Player.localPlayer.character) {
// 将角色的位置改变到复活点
char.worldTransform.position = this._rebornPosition.clone()
}
// 关闭布娃娃属性
char.ragdollEnabled = false
}
}
7.4.GameUI文件
如果大家懒得拼UI的话,可以直接下载这个文件:
https://arkimg.ark.online/GameUI.rar
什么?你问我怎么用?
① 点击UI
② 随便选中一个UI,然后右键,点击“打开文件所在位置”
③ 将压缩包解压到上一步打开的文件夹中,然后回到游戏即可看见UI
④ 有了UI后,大家回到导出UI的介绍中继续制作即可