语言:TS(TypeScript)
开发引擎:Cocos
开发项目类型:2D 休闲 - 小游戏合集(Hub 大厅式)
客户端:移动端、PC 端双端支持
① 【菜单界面功能介绍及解析】
绑定脚本和组件:
配置脚本:
功能一:点击“开始游戏”时,加载界面进度条,当进度条加载完成后进入主城界面
功能二:点击退出游戏时,停止引擎运行中断游戏
② 【主城界面界面功能介绍及解析】
绑定脚本和组件:
home_Player.ts:
Role_Player.ts:
配置脚本:
【home_Player.ts】 // main_player.ts (物品脚本) @property(Prefab) public fKeyPrefab: Prefab = null; // 在编辑器里只拖拽这一个预制体模板即可 private FKeyNode: Node = null; // 可交互-水晶实例 @property(Node) pebble: Node = null // 可交互-办公桌实例 @property(Node) desk: Node = null // 可交互-餐桌(table2)实例 @property(Node) desk2: Node = null // 判断角色与可交互实例的距离 @property public showDistance: number = 100; // 存放可交互实例 nearestTarget: Node = null; // 防重触发游戏 isdown: boolean = false // 放置节点的label tipLabel: Label = null; // 放置loading实例 @property(Node) loading: Node = null【Role_Player.ts】 // 角色预制体节点 @property(Prefab) Role_Prefab:Prefab = null // 角色的父节点 @property(Node) Canvas:Node = null // 角色实例存放 Role_place:Node = null // 新增静态变量,用于外部访问,获取角色实例 public static Role_place: Node = null // 监听移动速度 @property play_speed: number = 7.5; // 监听最大移动距离 @property maxX:number = 0 @property maxY_top:number = 0 @property maxY_bottom:number = 0 // 监听移动动画 Role_animation:Animation = null; // 监听摇杆组件 // @property(Prefab) // ROle_move_perfab: Prefab = null; @property(Node) Role_move_center:Node = null; // 摇杆方向向量 dir:Vec2 = v2(0, 0) transfrom:UITransform = null; // 设置摇杆移动的最大半径 @property maxR:number = 100 // 监听摄像机 @property(Camera) uiCamera:Camera = null; // 用于判断aswd是否按下 private Button_A: boolean = false; private Button_S: boolean = false; private Button_W: boolean = false; private Button_D: boolean = false; // 控制角色疾跑 private Button_shift:boolean = false; // 记录当前正在播放的动画 private currentAnim: string = ""; private lastDir: string = "right1"; // 点击设置后暂停/开始游戏 private onset: boolean = true; // 背景颜色 @property(Node) background_color: Node = null // 判断是否按下交互 isinto: boolean = false预制体:
① 白毛少女:为2的平面角色,拥有默认待机状态(向右前站立),移动时有配套的移动动画
② 功能按键
移动拉杆: 控制角色的移动,也可用键盘的wasd控制移动
疾跑按键: 单击按键后,角色进入疾跑状态,移动速度变为原先的2倍
F交互按键-圆: 适配移动端交互,短按即可交互
F交互按键-方: 适配PC端交互,短按键盘F可交互
刷新按键: 将界面刷新为初始状态,防止界面卡死
暂停/继续按键: 短按触发,用于暂停游戏运行,方便玩家临时中断游玩
退出按键: 短按退出游戏
功能一:角色的显示、移动
显示:
// 角色预制体节点 @property(Prefab) Role_Prefab:Prefab = null // 角色的父节点 @property(Node) Canvas:Node = null // 角色实例存放 Role_place:Node = null // 新增静态变量,用于外部访问,获取角色实例 public static Role_place: Node = null// 生成角色 Role_node(){ // 实例化角色预制体 const Role_Prefab = instantiate(this.Role_Prefab) // 将角色预制体赋予角色实例中 this.Role_place = Role_Prefab // 将角色实例的父节点设置为Canvas this.Role_place.setParent(this.Canvas) // 设置角色实例位置 this.Role_place.setPosition(0, 0, 0) // 将角色实例赋予动画组件中 // getComponent 会查找 组件 身上挂载的 Animation 组件 this.Role_animation = this.Role_place.getComponent(Animation); // 用于全局变量:当角色成功生成并添加到场景之后,将它赋给静态变量instance Role_Player.Role_place = this.Role_place // 假设 targetNode 是你想要垫在它下面的那个实例 const baseIndex = this.background_color.getSiblingIndex(); // 将当前节点插入到 targetNode 的下方(索引 - 1) this.Role_place.setSiblingIndex(baseIndex - 1); } start() { // 开始时生成角色 this.Role_node() }移动:
角色按下w向上移动,a向左移动,s向下移动,d向右移动,遥感也可控制移动
角色会根据移动的方向,播放不同的移动动画
按下疾跑可将角色的移速变为2倍,再次按下恢复初始移速
移动设有最大移动范围,防止角色移动超出地图外
遥感设有最大移动范围,防止遥感乱飞,增加控制角色的难度
当松开遥感时,遥感会回到中心
// 监听移动速度 @property play_speed: number = 7.5; // 监听最大移动距离 @property maxX:number = 0 @property maxY_top:number = 0 @property maxY_bottom:number = 0 // 监听移动动画 Role_animation:Animation = null; // 监听摇杆组件 // @property(Prefab) // ROle_move_perfab: Prefab = null; @property(Node) Role_move_center:Node = null; // 摇杆方向向量 dir:Vec2 = v2(0, 0) transfrom:UITransform = null; // 设置摇杆移动的最大半径 @property maxR:number = 50 // 监听摄像机 @property(Camera) uiCamera:Camera = null; // 用于判断aswd是否按下 private Button_A: boolean = false; private Button_S: boolean = false; private Button_W: boolean = false; private Button_D: boolean = false; // 控制角色疾跑 private Button_shift:boolean = false; // 记录当前正在播放的动画 private currentAnim: string = ""; private lastDir: string = "right1";// 获取/修改 角色位置 RolePos(PosX:number, PosY:number){ const RoPos = this.Role_place.getPosition() RoPos.x += PosX; RoPos.y += PosY; this.Role_place.setPosition(RoPos) } // 设置角色移动上限 Max_Move(){ // 设置角色在大厅的X轴移动上限 if(this.Role_place.x <= -this.maxX){ this.Role_place.x = -this.maxX // console.log('11111'); }else if(this.Role_place.x >= this.maxX){ this.Role_place.x = this.maxX // console.log('22222'); } // 设置角色在大厅的Y轴移动上限 if(this.Role_place.y >= this.maxY_top){ this.Role_place.y = this.maxY_top // console.log('33333'); }else if(this.Role_place.y <= -this.maxY_bottom){ this.Role_place.y = -this.maxY_bottom // console.log('44444'); } } protected onLoad(): void { // 按下按钮,判断awsd按下 input.on(Input.EventType.KEY_DOWN, this.ondown, this); input.on(Input.EventType.KEY_UP, this.onup, this); // 摇杆 this.transfrom = this.Role_move_center.parent.getComponent(UITransform); this.Role_move_center.on(Input.EventType.TOUCH_START, this.OnTouchStart, this) this.Role_move_center.on(Input.EventType.TOUCH_CANCEL, this.OnTouchEnd, this) this.Role_move_center.on(Input.EventType.TOUCH_MOVE, this.OnTouchMove, this) this.Role_move_center.on(Input.EventType.TOUCH_END, this.OnTouchEnd, this) } protected onDestroy(): void { // 松开按钮 input.off(Input.EventType.KEY_DOWN, this.ondown, this); input.off(Input.EventType.KEY_UP, this.onup, this); } // 摇杆 OnTouchStart(e){ this.dir.x = this.dir.y = 0 // console.log(e); } OnTouchMove(e){ const screenPos:Vec2 = e.getLocation() // 通过摄像机,将屏幕坐标转换成世界坐标 const wPos = this.uiCamera.screenToWorld(v3(screenPos.x, screenPos.y, 0)) const pos = this.transfrom.convertToNodeSpaceAR(wPos) pos.z = 0 // 限制摇杆最大半径 const len = pos.length() if(len > this.maxR){ pos.x = pos.x * this.maxR / len; pos.y = pos.y * this.maxR / len; } this.Role_move_center.setPosition(pos) // 设置摇杆方向和单位向量 if (len > 0) { this.dir.x = pos.x / len // cos(角度) this.dir.y = pos.y / len // sin(角度) } else { this.dir.x = 0; this.dir.y = 0; } // 摇杆控制移动,判断移动方向,并进行移动 if(this.dir.x > 0){ this.Button_A = false this.Button_D = true }else if(this.dir.x < 0){ this.Button_D = false this.Button_A = true } if(this.dir.y > 0){ this.Button_S = false this.Button_W = true }else if(this.dir.y < 0){ this.Button_W = false this.Button_S = true } } OnTouchEnd(e){ this.dir.x = this.dir.y = 0 this.Role_move_center.setPosition(0, 0) // 关闭移动和动画 this.Button_W = false this.Button_A = false this.Button_S = false this.Button_D = false } // 通过可视化按钮实现:疾跑 Role_move_speed(){ if(!this.Button_shift){ this.Button_shift = true; this.play_speed = 15 }else{ this.Button_shift = false; this.play_speed = 7.5 } } // 按下移动角色 ondown(event: EventKeyboard){ switch (event.keyCode) { case KeyCode.KEY_A: this.Button_A = true; // console.log('按下键盘A键,Button_A = true'); break; case KeyCode.KEY_S: this.Button_S = true; // console.log('按下键盘S键,Button_S = true'); break; case KeyCode.KEY_W: this.Button_W = true; // console.log('按下键盘W键,Button_W = true'); break; case KeyCode.KEY_D: this.Button_D = true; // console.log('按下键盘D键,Button_D = true'); break; // 按下控制角色疾跑 case KeyCode.SHIFT_LEFT: if(!this.Button_shift){ this.Button_shift = true; this.play_speed = 15 }else{ this.Button_shift = false; this.play_speed = 7.5 } default: break; } } // 松开停止角色 onup(event: EventKeyboard){ const RoPlace = this.Role_place.getPosition() switch (event.keyCode) { case KeyCode.KEY_A: this.Button_A = false; // console.log('松开下键盘A键,Button_A = false'); // console.log(RoPlace); break; case KeyCode.KEY_S: this.Button_S = false; // console.log('松开键盘S键,Button_S = false'); // console.log(RoPlace); break; case KeyCode.KEY_W: this.Button_W = false; // console.log('松开键盘W键,Button_W = false'); // console.log(RoPlace); break; case KeyCode.KEY_D: this.Button_D = false; // console.log('松开键盘D键,Button_D = false'); // console.log(RoPlace); default: break; } } // 移动角色动画 Role_move(){ let targetAnim = ""; // 1. 先判断:是否在移动(有按键按住) const isMoving = this.Button_W || this.Button_A || this.Button_S || this.Button_D; if (isMoving) { // 移动中:播放跑步动画 if (this.Button_W && this.Button_A) { targetAnim = "bai-left2"; this.lastDir = "left2"; } else if (this.Button_W && this.Button_D) { targetAnim = "bai-right2"; this.lastDir = "right2"; } else if (this.Button_S && this.Button_A) { targetAnim = "bai-left1"; this.lastDir = "left1"; } else if (this.Button_S && this.Button_D) { targetAnim = "bai-right1"; this.lastDir = "right1"; }else if (this.Button_W) { targetAnim = "bai-right2"; this.lastDir = "right2"; } else if (this.Button_S) { targetAnim = "bai-right1"; this.lastDir = "right1"; } else if (this.Button_A) { targetAnim = "bai-left1"; this.lastDir = "left1"; } else if (this.Button_D) { targetAnim = "bai-right1"; this.lastDir = "right1"; } } else { // 完全停下:播放对应方向待机 targetAnim = `bai-${this.lastDir}-zero`; } // 2. 只有动画变化时才播放,避免卡顿 if (targetAnim !== this.currentAnim) { this.Role_animation.play(targetAnim); this.currentAnim = targetAnim; } } // 移动角色 toPos(){ if (this.Button_W && this.Button_A) { // 左上 this.RolePos(-this.play_speed, this.play_speed) } else if (this.Button_W && this.Button_D) { // 右上 this.RolePos(this.play_speed, this.play_speed) } else if (this.Button_S && this.Button_A) { // 左下 this.RolePos(-this.play_speed, -this.play_speed) } else if (this.Button_S && this.Button_D) { // 右下 this.RolePos(this.play_speed, -this.play_speed) } // 单方向 else if (this.Button_W) { // 上 this.RolePos(0, this.play_speed) } else if (this.Button_S) { // 下 this.RolePos(0, -this.play_speed) } else if (this.Button_A) { // 左 this.RolePos(-this.play_speed, 0) } else if (this.Button_D) { // 右 this.RolePos(this.play_speed, 0) } } update(deltaTime: number) { // 调用toPos移动角色 this.toPos() // 角色移动动画 this.Role_move() // 限制角色最大移动距离 this.Max_Move() }
功能二:交互、刷新、暂停/继续、退出、疾跑、显示/隐藏可交互提示
交互:按下键盘F / 按下圆形F交互按键 可与实例交互
【home_Player.ts】 // 判断是否按下F交互按钮 downF(event: EventKeyboard) { // 如果已经在游戏中,或者按下的不是 F 键,直接返回 if (this.isdown || event.keyCode !== KeyCode.KEY_F) return; // 只有不在游戏中且按下了 F 键,才更新状态并执行逻辑 this.isdown = true; this.play_game(); } // 全局事件的回调处理,用于判断是否按下可视化按键F isinto(newValue: boolean) { // 防止全局事件重复触发 if (this.isdown === newValue) return; this.isdown = newValue; this.play_game(); console.log('成功模拟F交互'); } // 判断游玩的游戏 play_game() { // 如果当前没有目标(F键没显示),按 F 键什么都不发生 if (!this.nearestTarget) return; // 【核心判断】根据当前挂载的目标节点,执行不同的逻辑 switch (this.nearestTarget) { case this.pebble: this.playMusic(); this.isdown = false break; case this.desk: this.playYang(); this.isdown = false break; case this.desk2: this.play2048(); this.isdown = false break; default: break; } }【Role_Player.ts】 // 判断是否按下交互 isinto: boolean = false // 按下交互,模拟F键的效果 into(){ this.isinto = true // 向全局派发事件,并携带新的布尔值 director.emit('is_into', this.isinto); }刷新:
// 重新加载 Reload(){ // 清除本地存储的临时数据 (如果有不需要跨局存在的键值对) sys.localStorage.clear(); director.loadScene("home") }暂停/继续:
// 点击暂停/继续 On_Start_or_Stop(){ // 点击后,暂停游戏 if(this.onset){ this.background_color.active = true director.pause(); console.log("游戏已暂停"); this.onset = false; } // 点击后,恢复游戏 else{ this.background_color.active = false director.resume(); console.log("游戏已恢复"); this.onset = true; } }退出:
// 退出到主页 Exit_Game(){ director.loadScene("main"); }疾跑:
// 通过可视化按钮实现:疾跑 Role_move_speed(){ if(!this.Button_shift){ this.Button_shift = true; this.play_speed = 15 }else{ this.Button_shift = false; this.play_speed = 7.5 } }显示/隐藏可交互提示:
当角色与可交互实例的距离小于等于100时,显示出可交互实例,并允许交互
当两个可交互实例都在范围内时,通过比对距离,允许最小距离的实例交互
【Role_Player.ts】 // 监听最大移动距离 @property maxX:number = 0 @property maxY_top:number = 0 @property maxY_bottom:number = 0【home_Player.ts】 // 判断角色与可交互实例的距离 @property public showDistance: number = 100; // 生成F交互按钮 show_hide_FKey(){ // 获取并赋值角色节点 const Role_place = Role_Player.Role_place // console.log("在home_Player中获取的位置为:", Role_place.worldPosition, ",获取成功了喵~"); // 安全检查(空值检测):如果 玩家节点不存在,或者 F键 UI 没绑定,那么 立刻停止执行下面的代码 if (!Role_place || !this.FKeyNode || !this.pebble) return; // 计算两者世界坐标的距离 const distance1 = Vec3.distance(Role_place.worldPosition, this.pebble.worldPosition); const distance2 = Vec3.distance(Role_place.worldPosition, this.desk.worldPosition); const distance3 = Vec3.distance(Role_place.worldPosition, this.desk2.worldPosition); this.nearestTarget = null // 最近距离 let minDist = Infinity; // 初始设为无穷大 // 【核心逻辑】:谁在范围内,且距离最小,就选谁 if (distance1 <= this.showDistance && distance1 < minDist) { minDist = distance1; this.nearestTarget = this.pebble; } if (distance2 <= this.showDistance && distance2 < minDist) { minDist = distance2; this.nearestTarget = this.desk; } if (distance3 <= this.showDistance && distance3 < minDist) { minDist = distance3; this.nearestTarget = this.desk2; } // 统一处理最终的显隐和父节点切换 if (this.nearestTarget) { // 只有当目标发生变化时,才重新 setParent(防止每帧都在重组节点树导致卡顿) if (this.FKeyNode.parent !== this.nearestTarget) { this.FKeyNode.setParent(this.nearestTarget); // 根据目标节点设置label if (this.nearestTarget == this.pebble) { this.tipLabel.string = "按F进入《游戏一》"; } else if (this.nearestTarget == this.desk) { this.tipLabel.string = "按F进入《游戏二》"; } else if (this.nearestTarget == this.desk2) { this.tipLabel.string = "按F进入《游戏三》"; } } this.FKeyNode.active = true; } else { // 如果都不在范围内,才隐藏 this.FKeyNode.active = false; } }
功能三:加载进度条、退出小游戏
加载进度条:
此为通用工具脚本模块,可独立复用,能够直接引入、粘贴至其他业务脚本中调用,减少重复编码,提升代码复用性。
【loading.ts】 import { _decorator, Component, director, Label, Node, ProgressBar } from 'cc'; const { ccclass, property } = _decorator; @ccclass('loading') export class loading extends Component { // 绑定你的进度条组件 @property(ProgressBar) progressBar: ProgressBar = null!; // 绑定显示百分比的文本组件 @property(Label) percentText: Label = null!; // 存储game game: string = "" start() { // 初始化状态:确保进度条和提示文字处于隐藏状态 this.progressBar.node.active = false; } run_loading(data:string){ console.log(data); this.game = data console.log('game is: ', this.game); this.Start_Game() } protected onEnable(): void { // 接收全局变量,用于确定loading的传参 director.on('run_loading', this.run_loading, this) } protected onDestroy(): void { // 销毁,防止内存泄漏 director.off('run_loading', this.run_loading, this); } // 开始游戏 Start_Game(){ // 1. 隐藏按钮或点击区域,防止玩家重复点击 // 2. 显示进度条和提示文字 this.progressBar.node.active = true; this.progressBar.progress = 0; this.percentText.string = "加载中...0%"; // 3. 触发预加载 home 场景 director.preloadScene(this.game, // 回调函数1:实时获取加载进度 (completedCount: number, totalCount: number) => { const progress = completedCount / totalCount; // 更新进度条和文本 this.progressBar.progress = progress; this.percentText.string = `${Math.floor(progress * 100)}%`; }, // 回调函数2:加载完成后的处理 () => { console.log(this.game + " 场景资源加载完成!"); // 正式切换到 home 场景 director.loadScene(this.game); } ); } update(deltaTime: number) { } }退出小游戏:
专为小游戏退出到主城界面的脚本,在离开小游戏时,会销毁当前游戏,防止内存泄漏,导致游戏缓存过高出现卡顿
同时具有预加载场景的功能,可以在退出时出现进度条实时显示预加载场景的进度,防止游戏突然切换场景导致的卡顿
此脚本可辅助优化游戏体验,起到减少卡顿的作用
【exit_home.ts】 import { _decorator, Component, director, input, Input, Label, Node, ProgressBar } from 'cc'; const { ccclass, property } = _decorator; @ccclass('exit_home') export class exit_home extends Component { // 绑定你的进度条组件 @property(ProgressBar) progressBar: ProgressBar = null!; // 绑定显示百分比的文本组件 @property(Label) percentText: Label = null!; start() { // 初始化状态:确保进度条和提示文字处于隐藏状态 this.progressBar.node.active = false; // 接收全局变量,用于判断loading是否执行 director.on('exit_home', this.Exit_Home, this) this.Exit_Home() } protected onDestroy(): void { // 销毁,防止内存泄漏 director.off('exit_home', this.Exit_Home, this); } // 回到主城界面 Exit_Home(){ // 1. 隐藏按钮或点击区域,防止玩家重复点击 // 2. 显示进度条和提示文字 this.progressBar.node.active = true; this.progressBar.progress = 0; this.percentText.string = "加载中...0%"; // 3. 触发预加载 home 场景 director.preloadScene("home", // 回调函数1:实时获取加载进度 (completedCount: number, totalCount: number) => { const progress = completedCount / totalCount; // 更新进度条和文本 this.progressBar.progress = progress; this.percentText.string = `${Math.floor(progress * 100)}%`; }, // 回调函数2:加载完成后的处理 () => { console.log("home 场景资源加载完成!"); // 正式切换到 home 场景 director.loadScene("home"); } ); } update(deltaTime: number) { } }【所有小游戏.ts】 // 用于可视化退出实例 exit_home(){ this.loading.active = true this.exit = true sys.localStorage.clear(); // 清除本地存储的临时数据 (如果有不需要跨局存在的键值对) this.unscheduleAllCallbacks(); // 清理定时器、延时任务,防止旧延时继续跑逻辑 director.emit('exit_home', this.exit); // 向全局派发事件,并携带判断exit_home是否执行的布尔值 // 停止音乐 Audio_Manager.instance.ChangeBGM(null); }
③ 【小游戏——羊了个羊】
功能一:使用缓动系统,制作循迹移动小羊,并实现小羊在移动时改变朝向
在update生命周期函数中,根据Y轴坐标位置的不同,设置小羊预制体的缩放比例调整X轴,实现其翻转效果(X轴负数即为翻转)
程序:
使用Cocos 缓动系统(Tween,补间动画)实现小羊的移动
核心概念:
- to:变到【目标绝对值】(比如透明度从 0 → 255)
- by:变化【相对增量】(比如 y 坐标 +50)
- easing(缓动曲线):控制速度节奏
- linear:匀速(死板)
- sineOut:先快后慢(最常用,弹窗、进度)
- sineIn:先慢后快
- backOut、elasticOut:回弹、弹性效果
delay延时、call执行回调、repeat循环、start()启动、stop()停止程序:
【SheepMove.ts】 start() { // 循迹移动小羊——安全校验 if(this.MoveSheep.length > 0){ // 循迹移动小羊 this.moveAlongPath(); } } // 使用缓动系统,制作循迹移动小羊 moveAlongPath(){ // 便利所有路劲节点,并赋值于自定points let points = this.MoveSheep.map(point => point.getPosition()); let currentIndex = 0; // 小羊当前的位置 // 递归方式使得每次tween结束后继续下一个点循环 const moveToNextPoint=()=>{ let targetPos = points[currentIndex]; // 获取小羊当前位置 currentIndex = (currentIndex+1)%points.length; // 循环索引,每次循环时获取下一个位置 // 创建新的tween动画,移动到目标点 tween(this.node) .to(1,{position:targetPos},{easing:'sineInOut'}) // 1秒到达目标点 .call(moveToNextPoint) // 动画结束后调用moveToNextPoint函数,继续下一个点 .start(); // 开始执行 }; moveToNextPoint(); }
功能二:音乐播放
此为通用工具脚本模块,可独立复用,能够直接引入、粘贴至其他业务脚本中调用,减少重复编码,提升代码复用性。
使用时,在其他脚本中导入import { Audio_Manager } from "./Audio_Manager";即可直接使用
【Audio_Manager.ts】 import { _decorator, AudioClip, AudioSource, Component, Game, game, Node } from 'cc'; const { ccclass, property } = _decorator; @ccclass('Audio_Manager') export class Audio_Manager extends Component { static instance: Audio_Manager = null; /* 音乐实例——存放背景音乐等 当前文件内使用 | .play();开始 .pause();暂停 .stop();停止 | 来控制 全局使用:{ 切换游戏BGM:Audio_Manager.instance.ChangeBGM(Audio_Manager.instance.音乐名); 停止BGM: Audio_Manager.instance.ChangeBGM(null); } */ @property(AudioSource) Audio_BGM: AudioSource = null; // 多个背景音乐存放区 —— 羊了个羊 @property(AudioClip) Audio_bgm1_start: AudioClip = null; // 开始界面背景音 @property(AudioClip) Audio_bgm2_play: AudioClip = null; // 游戏中背景音 /* 音乐实例——存放音效等 当前文件内使用 | .playOneShot(this.音效, 音量);短音效 | 来控制 音量的取值为0~1,0完全静音,1为原音量 全局使用:{ 播放音效:Audio_Manager.instance.ChangeSounds(Audio_Manager.instance.音效名, 音量); } */ @property(AudioSource) Audio_Sounds: AudioSource = null; // 多个音效存放区 —— 羊了个羊 @property(AudioClip) Audio_sounds_GameStart: AudioClip = null; // 点击“开始游戏”音效 @property(AudioClip) Audio_sounds_click: AudioClip = null; // 点击音效 @property(AudioClip) Audio_sounds_clear: AudioClip = null; // 三消音效 @property(AudioClip) Audio_sounds_win: AudioClip = null; // 胜利音效 @property(AudioClip) Audio_sounds_lose: AudioClip = null; // 失败音效 /* newBGM 为自定的背景音乐,可从“音乐实例——存放音效”中找 */ ChangeBGM(newBGM: AudioClip){ // 如果传空就直接停止音效 if (!newBGM) { this.Audio_BGM.stop(); return; } // 四个步骤:先停止音乐,再替换音乐素材,保证音乐循环,最后播放音乐 this.Audio_BGM.stop(); // 停止音乐 this.Audio_BGM.clip = newBGM; // 替换音乐素材 this.Audio_BGM.loop = true; // 开启音乐循环 this.Audio_BGM.play(); // 播放音乐 } /* newSounds 为自定的音效,可从“音乐实例——存放背景音乐”中找 Vol 为自定的音量大小(取值范围为0 ~ 1) */ ChangeSounds(newSounds: AudioClip, Vol: number){ // 如果传空就直接停止音效 if (!newSounds || Vol < 0 || Vol > 1) { return; } // 设置音效素材,设置音量大小(取值范围为0 ~ 1) this.Audio_Sounds.playOneShot(newSounds, Vol); } start() { } onLoad() { Audio_Manager.instance = this; game.addPersistRootNode(this.node); } update(deltaTime: number) { } }
功能三:进入游戏界面、游戏游玩界面、游戏结算画面 场景切换
对于游戏规模较小时,通过 设置Active 控制节点显隐来切换,性能消耗差异较小,切换响应更快;
对于游戏规模较大、资源与节点数量多、性能开销高时,采用场景切换,销毁并释放上一场景资源,降低内存占用。
此处为前者的应用。
// 放置“进入游戏界面”组件 @property(Node) Game_interface_element: Node = null; // 进入游戏界面 Game_interface(){ this.loading.active = false; // 初始默认隐藏加载进度条 this.Game_interface_element.active = true; // 显示“进入游戏界面”组件 this.Game_Start_interface_element.active = false; // 隐藏“游戏游玩界面”组件 this.Game_end_element.active = false; // “游戏结算画面”默认为隐藏 }/* 放置“游戏游玩界面”组件、存放关卡数实例 关卡编号:Level 可调 关卡文本:Level_label 可调 使用:{ this.Level = 自定关卡数(数字) this.update_Level_Label() // 函数实现更新关卡数 } */ @property(Node) Game_Start_interface_element: Node = null; // 放置“游戏游玩界面”组件 @property Level: number = 1; // 设置关卡数 @property(Label) Level_label: Label = null; // 设置关卡文本 // 游戏游玩界面 Game_Start_interface(){ this.Game_state = Game_end.None; // 重置游戏结算状态,回到未结算 this.Level = 1 // 设置初始关卡数 this.update_Level_Label() // 函数实现更新关卡数 this.Game_interface_element.active = false; // 隐藏“进入游戏界面”组件 this.Game_Start_interface_element.active = true; // 显示“游戏游玩界面”组件 this.Game_end_element.active = false; // “游戏结算画面”默认为隐藏 }/* 游戏结算提示 None, // 未结算 Win, // 胜利 Lose // 失败 stop_or_start // 游戏暂停/继续 `===`:**严格相等**(值 + 类型都必须一样,推荐在 Cocos/TS 项目全程用这个) `==`:**宽松相等**(会自动做类型转换,坑很多,尽量不要用) 使用:{ this.Game_end_tips.string = "自定义游戏结算提示内容"; 配备函数:this.Game_end_interface(Game_end.游戏状态); } */ @property(Node) Game_end_element: Node = null; // 放置“游戏结算画面”组件 通过active设置显隐 @property(Label) Game_end_tips: Label = null; // 设置游戏结算提示 // 游戏结算画面 Game_end_interface(Game_state: Game_end){ switch (Game_state) { case Game_end.None: this.Game_end_element.active = true; // 展示游戏结算画面 this.Game_end_tips.string = "游戏未结算"; // 展示游戏结算提示:游戏未结算 break; case Game_end.Win: this.Game_end_element.active = true; // 展示游戏结算画面 this.Game_end_tips.string = "游戏胜利"; // 展示游戏结算提示:游戏胜利 break; case Game_end.Lose: this.Game_end_element.active = true; // 展示游戏结算画面 this.Game_end_tips.string = "游戏失败"; // 展示游戏结算提示:游戏失败 break; case Game_end.stop_or_start: this.Game_end_element.active = true; // 展示游戏结算画面 this.Game_end_tips.string = "游戏暂停"; // 展示游戏结算提示:游戏暂停 break; default: break; } }
功能四:游戏结算后点击“再来一次”重启游戏
点击“再来一次”按键后,游戏会被销毁,同时在新场景加载完成后,给一个标记,让新场景脚本启动时读取标记,自动进入游玩界面。
start() { Audio_Manager.instance.ChangeBGM(Audio_Manager.instance.Audio_bgm1_start); // 开启背景音乐 // 读取标记 const autoStart = sys.localStorage.getItem("autoStartGame"); if(autoStart === "1"){ // 清除标记,防止下次进来自动开启 sys.localStorage.removeItem("autoStartGame"); // 自动进入游戏游玩界面 this.Game_Start(); } } // 游戏结算画面按键 —— 再来一次点击效果:恢复到第一关状态 Game_end_btn(){ sys.localStorage.clear(); // 清除本地存储的临时数据 (如果有不需要跨局存在的键值对) this.unscheduleAllCallbacks(); // 清理定时器、延时任务,防止旧延时继续跑逻辑 director.loadScene("yang", ()=>{ // 在新场景加载完成后,给一个标记,让新场景脚本启动时读取标记,自动进入游玩界面 sys.localStorage.setItem("autoStartGame","1"); }); }