first commit
This commit is contained in:
9
assets/seripts/components.meta
Normal file
9
assets/seripts/components.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "50c20bdf-cc41-411c-a19a-f8f6b4e22af1",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
220
assets/seripts/components/DinoControl.ts
Normal file
220
assets/seripts/components/DinoControl.ts
Normal file
@@ -0,0 +1,220 @@
|
||||
import {
|
||||
_decorator,
|
||||
Component,
|
||||
input,
|
||||
EventTouch,
|
||||
Node,
|
||||
Input,
|
||||
Vec3,
|
||||
Collider2D,
|
||||
Contact2DType,
|
||||
SpriteFrame,
|
||||
Sprite,
|
||||
tween,
|
||||
Prefab,
|
||||
instantiate,
|
||||
director,
|
||||
} from "cc";
|
||||
import { NoteControl } from "./NoteControl";
|
||||
import { BombEffect } from "../effects/BombEffectControl";
|
||||
import { EventDispatcher } from "../libs/EventDispatcher";
|
||||
import { GlobalData } from "../config/GlobalData";
|
||||
import { GameState } from "../config/Config";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("DinoControl")
|
||||
export class DinoControl extends Component {
|
||||
// 小恐龙脖子图片
|
||||
@property(SpriteFrame)
|
||||
neckSpriteFrame: SpriteFrame = null;
|
||||
|
||||
// 小恐龙脖子的高度
|
||||
private neckHeight: number = 62;
|
||||
|
||||
// x 轴的边界
|
||||
private xMaxDistance: number = 180;
|
||||
|
||||
// 爆炸特效预制体
|
||||
@property(Prefab)
|
||||
bombEffectPrefab: Prefab = null;
|
||||
|
||||
// 获取线节点
|
||||
@property(Node)
|
||||
lineNode: Node = null;
|
||||
|
||||
onLoad() {
|
||||
// 监听继续游戏事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.GAME_RESUME,
|
||||
this.continueGame,
|
||||
this,
|
||||
);
|
||||
// 监听小恐龙移动到线的位置
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.MOVE_DINO_TO_LINE,
|
||||
this.moveDinoToLine,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
start() {
|
||||
// 监听手指滑动事件
|
||||
input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
|
||||
// 监听碰撞事件
|
||||
this.node
|
||||
.getComponent(Collider2D)
|
||||
.on(Contact2DType.BEGIN_CONTACT, this.onCollisionEnter, this);
|
||||
}
|
||||
|
||||
// 滑动事件
|
||||
onTouchMove(event: EventTouch) {
|
||||
// 获取小恐龙的位置
|
||||
const dinoPos = this.node.position;
|
||||
// 获取手指x移动距离
|
||||
const deltaX = event.getDeltaX();
|
||||
// 更新小恐龙的位置
|
||||
// 目标位置
|
||||
const targetPos = new Vec3(dinoPos.x + deltaX, dinoPos.y, dinoPos.z);
|
||||
if (targetPos.x < -this.xMaxDistance) {
|
||||
targetPos.x = -this.xMaxDistance;
|
||||
} else if (targetPos.x > this.xMaxDistance) {
|
||||
targetPos.x = this.xMaxDistance;
|
||||
}
|
||||
this.node.setPosition(targetPos);
|
||||
|
||||
// 移动距离大于0时,小恐龙向右移动,否则向左移动
|
||||
if (deltaX > 0) {
|
||||
this.node.scale = new Vec3(-1, 1, 1);
|
||||
} else {
|
||||
this.node.scale = new Vec3(1, 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
onCollisionEnter(selfCollider: Collider2D, otherCollider: Collider2D) {
|
||||
// 如果游戏状态不是进行中,直接返回
|
||||
if (GlobalData.gameState != GameState.PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (otherCollider.tag == 2) {
|
||||
// 得分操作
|
||||
GlobalData.curRoundTotalScore += GlobalData.addScore;
|
||||
|
||||
// 触发小恐龙吃到音符事件
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.DINO_EAT_NOTE);
|
||||
// 小恐龙碰到了音符
|
||||
otherCollider.node.getComponent(NoteControl).noteBeEaten();
|
||||
// 增加脖子长度
|
||||
this.addDinoNeckLength();
|
||||
// 执行吃掉音符动画
|
||||
this.playEatNoteAnimation();
|
||||
// 播放爆炸特效
|
||||
this.playBombEffect();
|
||||
|
||||
// 检测是否闯关成功
|
||||
const isPassLevel = this.checkIsPassLevel();
|
||||
|
||||
if (isPassLevel) {
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.PASS_LEVLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 增加脖子长度
|
||||
addDinoNeckLength() {
|
||||
// 添加脖子空节点
|
||||
let neckChild = new Node();
|
||||
// 给这个节点添加精灵组件
|
||||
neckChild.addComponent(Sprite).spriteFrame = this.neckSpriteFrame;
|
||||
|
||||
// 设置这个空节点的父节点
|
||||
neckChild.parent = this.node.getChildByName("dino_neck_box");
|
||||
|
||||
// 获取小恐龙身体
|
||||
const bodyNode = this.node.getChildByName("dino_body");
|
||||
|
||||
// 获取小恐龙位置
|
||||
const bodyPos = bodyNode.getPosition();
|
||||
// 设置小恐龙新的位置(往下移动一个脖子的距离)
|
||||
bodyNode.setPosition(bodyPos.x, bodyPos.y - this.neckHeight, 0);
|
||||
}
|
||||
|
||||
// 执行吃掉音符动画
|
||||
playEatNoteAnimation() {
|
||||
// 获取当前所有脖子列表
|
||||
const neckList: Node[] = this.node
|
||||
.getChildByName("dino_neck_box")
|
||||
.children.slice(0, 16) as Node[];
|
||||
|
||||
// 定义存储动画的 promise
|
||||
let animationPromises = Promise.resolve();
|
||||
// 遍历所有脖子
|
||||
for (let i = 0; i < neckList.length; i++) {
|
||||
let neckNode = neckList[i];
|
||||
// 播放吃掉音符动画
|
||||
animationPromises = animationPromises.then(() => {
|
||||
return new Promise((resolve) => {
|
||||
tween(neckNode)
|
||||
.to(0.1, { scale: new Vec3(1.2, 1, 1) })
|
||||
.to(0.1, { scale: new Vec3(1, 1, 1) })
|
||||
.call(() => resolve())
|
||||
.start();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 播放爆炸特效
|
||||
playBombEffect() {
|
||||
// 实例化爆炸特效
|
||||
let bombEffectNode = instantiate(this.bombEffectPrefab);
|
||||
// 设置爆炸特效节点的位置
|
||||
let bombPos = new Vec3(
|
||||
this.node.position.x,
|
||||
this.node.position.y + 100,
|
||||
this.node.position.z,
|
||||
);
|
||||
// 设置位置
|
||||
bombEffectNode.setPosition(bombPos);
|
||||
// 设置爆炸特效节点的父节点
|
||||
bombEffectNode.setParent(this.node.parent);
|
||||
// 播放爆炸特效
|
||||
bombEffectNode.getComponent(BombEffect).playBombEffect();
|
||||
}
|
||||
|
||||
// 移动小恐龙到线的位置
|
||||
moveDinoToLine() {
|
||||
// 获取线的世界坐标
|
||||
const linePos = this.lineNode.getWorldPosition();
|
||||
// 获取小恐龙身体的世界坐标位置
|
||||
const bodyPos = this.node.getChildByName("dino_body").getWorldPosition();
|
||||
// 计算两个坐标 y 轴的差值
|
||||
const deltaY = linePos.y - bodyPos.y;
|
||||
// 移动小恐龙到线的位置动画
|
||||
tween(this.node)
|
||||
.by(1, { position: new Vec3(0, deltaY - 44, 0) })
|
||||
.start();
|
||||
}
|
||||
|
||||
// 检测是否通过本关
|
||||
checkIsPassLevel(): boolean {
|
||||
// 获取 note_manager 节点
|
||||
const noteManager = director
|
||||
.getScene()
|
||||
.getChildByName("Canvas")
|
||||
.getChildByName("note_manager");
|
||||
if (noteManager?.children?.length === 0) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 继续游戏
|
||||
continueGame() {
|
||||
const isPassLevel = this.checkIsPassLevel();
|
||||
if (isPassLevel) {
|
||||
// 发送闯关成功事件
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.PASS_LEVLE);
|
||||
}
|
||||
}
|
||||
|
||||
update(deltaTime: number) {}
|
||||
}
|
||||
9
assets/seripts/components/DinoControl.ts.meta
Normal file
9
assets/seripts/components/DinoControl.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "3af5932a-d1bb-4429-b815-ab2215ac75e8",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
52
assets/seripts/components/LineControl.ts
Normal file
52
assets/seripts/components/LineControl.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { _decorator, Collider2D, Component, Contact2DType, Node } from "cc";
|
||||
import { GlobalData } from "../config/GlobalData";
|
||||
import { GameState } from "../config/Config";
|
||||
import { NoteControl } from "./NoteControl";
|
||||
import { EventDispatcher } from "../libs/EventDispatcher";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("LineControl")
|
||||
export class LineControl extends Component {
|
||||
start() {
|
||||
// 监听碰撞事件
|
||||
this.node
|
||||
.getComponent(Collider2D)
|
||||
.on(Contact2DType.BEGIN_CONTACT, this.onCollisionEnter, this);
|
||||
}
|
||||
|
||||
onCollisionEnter(selfCollider: Collider2D, otherCollider: Collider2D) {
|
||||
// 如果游戏状态不是进行中,直接返回
|
||||
if (GlobalData.gameState != GameState.PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (otherCollider.tag == 2) {
|
||||
// 1. 剩余生命值减 1
|
||||
GlobalData.lifeNumPerRound--;
|
||||
|
||||
// 2. 剩余生命值是否大于等于0
|
||||
if (GlobalData.lifeNumPerRound >= 0) {
|
||||
// 触发音符碰到线事件
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.UPDATE_LIFE);
|
||||
}
|
||||
// 3. 如果小于0,游戏结束
|
||||
if (GlobalData.lifeNumPerRound < 0) {
|
||||
GlobalData.gameState = GameState.GAME_OVER;
|
||||
// 调用 NoteControl 中的 pause 方法暂停音符
|
||||
otherCollider.getComponent(NoteControl).noteHitLine();
|
||||
// 发送游戏结束事件
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.GAME_OVER);
|
||||
} else {
|
||||
GlobalData.gameState = GameState.PAUSED;
|
||||
otherCollider.getComponent(NoteControl).noteHitLine();
|
||||
// 发送暂停事件
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.GAME_PAUSE);
|
||||
}
|
||||
// 4. 如果剩余生命值大于等于0,继续游戏(弹出暂停界面)
|
||||
|
||||
// 线碰到了音符
|
||||
}
|
||||
}
|
||||
|
||||
update(deltaTime: number) {}
|
||||
}
|
||||
9
assets/seripts/components/LineControl.ts.meta
Normal file
9
assets/seripts/components/LineControl.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "2ae0bd15-7a12-4d46-825c-b0d40fc8db6b",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
158
assets/seripts/components/NoteControl.ts
Normal file
158
assets/seripts/components/NoteControl.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import {
|
||||
_decorator,
|
||||
Color,
|
||||
Component,
|
||||
Node,
|
||||
Sprite,
|
||||
tween,
|
||||
Vec3,
|
||||
view,
|
||||
} from "cc";
|
||||
import { GlobalData } from "../config/GlobalData";
|
||||
import { GameState } from "../config/Config";
|
||||
import { EventDispatcher } from "../libs/EventDispatcher";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("NoteControl")
|
||||
export class NoteControl extends Component {
|
||||
// 音符下落的速度
|
||||
private noteSpeed: number = 800;
|
||||
|
||||
// 是否开始下落
|
||||
private isEnableFall: boolean = false;
|
||||
start() {
|
||||
// 监听清除可视区域音符事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.CLEAR_SCREEN_NOTE,
|
||||
this.clearScreenNote,
|
||||
this,
|
||||
);
|
||||
|
||||
// 监听游戏开始事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.GAME_STAET,
|
||||
this.startFall,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
// 开始下落
|
||||
startFall() {
|
||||
this.isEnableFall = true;
|
||||
}
|
||||
|
||||
// 当前音符被小恐龙吃到了
|
||||
noteBeEaten() {
|
||||
// 删除当前音符
|
||||
this.node.removeFromParent();
|
||||
// 销毁当前音符
|
||||
this.node.destroy();
|
||||
}
|
||||
|
||||
// 当前音符撞到线了
|
||||
noteHitLine() {
|
||||
// 1. 获取文字提示节点
|
||||
const noteHitLabel = this.node.getChildByName("tip_text");
|
||||
// 2. 显示文字提示
|
||||
noteHitLabel.active = true;
|
||||
// 3. 获取 node 节点下的 node_img 下的 Sprite 组件
|
||||
const noteHitSprite = this.node
|
||||
.getChildByName("note_img")
|
||||
.getComponent(Sprite);
|
||||
// 4. 获取音符的颜色
|
||||
const noteColor = new Color("ffffff");
|
||||
// 5. 操作音符闪烁
|
||||
tween(noteHitSprite)
|
||||
.repeat(
|
||||
3,
|
||||
tween()
|
||||
.to(0.2, {
|
||||
color: new Color(noteColor.r, noteColor.g, noteColor.b, 0),
|
||||
})
|
||||
.to(0.2, {
|
||||
color: noteColor,
|
||||
}),
|
||||
)
|
||||
.call(() => {
|
||||
// 文字提示节点逐渐缩小并消失
|
||||
tween(noteHitLabel)
|
||||
.to(0.5, {
|
||||
scale: new Vec3(0, 0, 0),
|
||||
})
|
||||
.call(() => {
|
||||
// 完成闪烁之后的逻辑
|
||||
|
||||
// 判断游戏状态
|
||||
if (GlobalData.gameState === GameState.GAME_OVER) {
|
||||
// 1. 游戏结束,删除当前界面内的音符节点
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.CLEAR_ALL_NOTE);
|
||||
// 2. 在0.1 s后操作小恐龙身体往上移动到线的位置
|
||||
this.scheduleOnce(() => {
|
||||
EventDispatcher.getTarget().emit(
|
||||
EventDispatcher.MOVE_DINO_TO_LINE,
|
||||
);
|
||||
}, 0.1);
|
||||
// 3. 弹出结束的弹窗
|
||||
EventDispatcher.getTarget().emit(
|
||||
EventDispatcher.SHOW_RESULT_MODAL,
|
||||
);
|
||||
} else if (GlobalData.gameState === GameState.PAUSED) {
|
||||
// 1. 显示继续游戏弹窗界面
|
||||
EventDispatcher.getTarget().emit(
|
||||
EventDispatcher.SHOW_CONTINUE_MODAL,
|
||||
);
|
||||
// 2. 清楚可视区域内的音符节点
|
||||
EventDispatcher.getTarget().emit(
|
||||
EventDispatcher.CLEAR_SCREEN_NOTE,
|
||||
);
|
||||
}
|
||||
})
|
||||
.start();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
// 清除可视区域音符
|
||||
private clearScreenNote() {
|
||||
// 判断是否在可视区域内
|
||||
if (this.checkIsInScreen()) {
|
||||
// 清除所有音符节点
|
||||
this.node.removeFromParent();
|
||||
this.node.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否在可视区域内
|
||||
private checkIsInScreen() {
|
||||
// 获取屏幕可视区域的宽度和高度
|
||||
const screenWidth = view.getVisibleSize().width;
|
||||
const screenHeight = view.getVisibleSize().height;
|
||||
|
||||
// 获取当前节点的世界坐标
|
||||
const worldPos = this.node.getWorldPosition();
|
||||
|
||||
// 判断音符是否在屏幕内
|
||||
if (
|
||||
worldPos.y >= 0 &&
|
||||
worldPos.y <= screenHeight &&
|
||||
worldPos.x >= 0 &&
|
||||
worldPos.x <= screenWidth
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
update(deltaTime: number) {
|
||||
// 游戏未开始,不更新音符位置
|
||||
if (!this.isEnableFall || GlobalData.gameState != GameState.PLAYING) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.node.setPosition(
|
||||
this.node.position.x,
|
||||
this.node.position.y - this.noteSpeed * GlobalData.speedRate * deltaTime,
|
||||
);
|
||||
}
|
||||
}
|
||||
9
assets/seripts/components/NoteControl.ts.meta
Normal file
9
assets/seripts/components/NoteControl.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "0b44dfc7-4b5b-4e74-a075-6fb71b84fb64",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/seripts/config.meta
Normal file
9
assets/seripts/config.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "52df94a7-d505-4d67-ad62-f067ff2a27e4",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
32
assets/seripts/config/Config.ts
Normal file
32
assets/seripts/config/Config.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// 一些枚举值
|
||||
export enum GameState {
|
||||
// 游戏未开始
|
||||
WAIT_START = 0,
|
||||
// 游戏进行中
|
||||
PLAYING = 1,
|
||||
// 游戏结束
|
||||
GAME_OVER = 2,
|
||||
// 游戏暂停
|
||||
PAUSED = 3,
|
||||
}
|
||||
|
||||
export class GameStorageKeyConfig {
|
||||
//存储音乐对应的音符列表信息
|
||||
static MusicNoteList: string = "_dino_music_note_list";
|
||||
//存储玩家最好的得分成绩
|
||||
static BestScore: string = "_dino_best_score";
|
||||
//存储玩家累计的总得分(总的金币数)
|
||||
static TotalScore: string = "_dino_total_score";
|
||||
//存储当前选择的音乐ID
|
||||
static SelectedMusicID: string = "_dino_selected_music_id";
|
||||
//存储解锁了哪些音乐的ID
|
||||
static UnlockMusicIDList: string = "_dino_unlock_music_id_list";
|
||||
//存储当前选择的小恐龙ID
|
||||
static SelectedDinoID: string = "_dino_selected_dino_id";
|
||||
//存储解锁了哪些小恐龙的ID
|
||||
static UnlockDinoIDList: string = "_dino_unlock_dino_id_list";
|
||||
//存储当前的小恐龙的信息
|
||||
static CurrentDinoInfo: string = "_dino_current_dino_info";
|
||||
//存储当前选择的音乐的信息
|
||||
static CurrentMusicInfo: string = "_dino_current_music_info";
|
||||
}
|
||||
9
assets/seripts/config/Config.ts.meta
Normal file
9
assets/seripts/config/Config.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "13b507c4-76cc-4fb6-9d95-e779b8280d88",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
73
assets/seripts/config/GlobalData.ts
Normal file
73
assets/seripts/config/GlobalData.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// 保存一些全局的数据
|
||||
|
||||
import Utils from "../libs/Utils";
|
||||
import { GameState, GameStorageKeyConfig } from "./Config";
|
||||
|
||||
export class GlobalData {
|
||||
// 游戏状态
|
||||
public static gameState: GameState = GameState.PLAYING;
|
||||
// 每吃到一个音符 增加的分数
|
||||
public static addScore: number = 1;
|
||||
//保存用户当前这局的分数
|
||||
public static curRoundTotalScore: number = 0;
|
||||
// 定义每一局用户默认剩余几条生命值
|
||||
public static lifeNumPerRound: number = 2;
|
||||
// 定义音符移动的速度
|
||||
public static noteSpeed: number = 800;
|
||||
// 定义当前音符下落的速率(也是音乐播放的速率)
|
||||
public static speedRate: number = 1;
|
||||
// 每次闯关增加的速率
|
||||
public static speedRateAdd: number = 0.2;
|
||||
// 获取当前选择的音乐 id
|
||||
public static get selectedMusicID(): number {
|
||||
return Utils.getCache(GameStorageKeyConfig.SelectedMusicID) || 1;
|
||||
}
|
||||
//设置当前选择的音乐ID
|
||||
public static set selectedMusicID(value) {
|
||||
//存储到本地缓存中
|
||||
Utils.setCache(GameStorageKeyConfig.SelectedMusicID, value);
|
||||
}
|
||||
//获取玩家累计的总得分(总的金币数)
|
||||
public static get totalScore() {
|
||||
//从本地缓存中获取
|
||||
let score = Utils.getCache(GameStorageKeyConfig.TotalScore) || 0;
|
||||
return score ? parseInt(score) : 0;
|
||||
}
|
||||
//存储玩家累计的总得分(总的金币数)
|
||||
public static set totalScore(value) {
|
||||
//存储到本地缓存中
|
||||
Utils.setCache(GameStorageKeyConfig.TotalScore, value);
|
||||
}
|
||||
//获取玩家最好的成绩
|
||||
public static get bestScore() {
|
||||
//从本地缓存中获取
|
||||
let scoreMap = Utils.getCache(GameStorageKeyConfig.BestScore) || {};
|
||||
//获取当前选择的音乐ID
|
||||
let bestScoreKey = GlobalData.selectedMusicID;
|
||||
//从本地获取最好成绩
|
||||
let score = scoreMap[bestScoreKey] || 0;
|
||||
//返回当前选择的音乐ID的最好成绩
|
||||
return score;
|
||||
}
|
||||
//存储游戏的UUID
|
||||
public static set bestScore(value) {
|
||||
//获取当前选择的音乐ID
|
||||
let bestScoreKey = GlobalData.selectedMusicID;
|
||||
//从本地获取最好成绩的映射
|
||||
let scoreMap = Utils.getCache(GameStorageKeyConfig.BestScore) || {};
|
||||
//更新当前选择的音乐ID的最好成绩(转换为整数)
|
||||
scoreMap[bestScoreKey] = parseInt(value);
|
||||
//存储到本地缓存中
|
||||
Utils.setCache(GameStorageKeyConfig.BestScore, scoreMap);
|
||||
}
|
||||
|
||||
// 重置游戏数据
|
||||
public static resetGameData() {
|
||||
// 重置当前这一局的得分
|
||||
GlobalData.curRoundTotalScore = 0;
|
||||
// 重置生命值
|
||||
GlobalData.lifeNumPerRound = 2;
|
||||
// 重置当前这一句的下落速速率
|
||||
GlobalData.speedRate = 1;
|
||||
}
|
||||
}
|
||||
9
assets/seripts/config/GlobalData.ts.meta
Normal file
9
assets/seripts/config/GlobalData.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "38bda874-cfc2-493d-afe2-10ecb753c60d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/seripts/effects.meta
Normal file
9
assets/seripts/effects.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "75a040f4-90c5-4543-8bf1-dab3e1396647",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
93
assets/seripts/effects/BombEffectControl.ts
Normal file
93
assets/seripts/effects/BombEffectControl.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { _decorator, Component, Label, Node, Animation, tween, Vec3 } from "cc";
|
||||
import { EventDispatcher } from "../libs/EventDispatcher";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("BombEffect")
|
||||
export class BombEffect extends Component {
|
||||
// 提示语数组
|
||||
private bombTips: string[] = [
|
||||
"完美",
|
||||
"真棒",
|
||||
"棒呆",
|
||||
"太棒了",
|
||||
"无敌",
|
||||
"超棒",
|
||||
"天才",
|
||||
];
|
||||
// 爆炸标题 Lable
|
||||
@property(Label)
|
||||
private bombLabel: Label = null;
|
||||
|
||||
// 爆炸图片节点
|
||||
@property(Node)
|
||||
private bombImageNode: Node = null;
|
||||
|
||||
// 动画组件
|
||||
private bombAnimation: Animation = null;
|
||||
|
||||
onLoad() {
|
||||
// 初始化动画组件
|
||||
this.bombAnimation = this.bombImageNode.getComponent(Animation);
|
||||
// 监听动画完成事件
|
||||
this.bombAnimation.on(
|
||||
Animation.EventType.FINISHED,
|
||||
this.onAnimationFinished,
|
||||
this,
|
||||
);
|
||||
// 设置随机的提示语
|
||||
this.setBombTip();
|
||||
// 监听小恐龙吃到音符事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.DINO_EAT_NOTE,
|
||||
this.hideBombTip,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
// 动画完成事件
|
||||
private onAnimationFinished() {
|
||||
// 隐藏爆炸图片
|
||||
this.bombImageNode.active = false;
|
||||
// 标题缩小到 0.1
|
||||
tween(this.bombLabel.node)
|
||||
.to(0.2, { scale: new Vec3(0.1, 0.1, 1) })
|
||||
.call(() => {
|
||||
// 销毁整个特效节点
|
||||
this.node.destroy();
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
// 播放爆炸特效
|
||||
public playBombEffect() {
|
||||
// 播放动画
|
||||
this.bombAnimation.play("bomb");
|
||||
// 显示爆炸标题
|
||||
this.bombLabel.node.active = true;
|
||||
// 放大标题1.1倍速
|
||||
tween(this.bombLabel.node)
|
||||
.to(0.5, { scale: new Vec3(1.1, 1.1, 1) })
|
||||
.start();
|
||||
}
|
||||
|
||||
// 设置随机的提示语
|
||||
private setBombTip() {
|
||||
let randomIndex = Math.floor(Math.random() * this.bombTips.length);
|
||||
// 设置爆炸标题的文本
|
||||
this.bombLabel.string = this.bombTips[randomIndex];
|
||||
}
|
||||
|
||||
// 隐藏爆炸标题
|
||||
private hideBombTip() {
|
||||
// 隐藏爆炸标题
|
||||
this.bombLabel.node.active = false;
|
||||
}
|
||||
|
||||
// 隐藏爆炸特效
|
||||
private hideBombEffect() {
|
||||
// 隐藏爆炸特效
|
||||
this.node.active = false;
|
||||
}
|
||||
|
||||
update(deltaTime: number) {}
|
||||
}
|
||||
9
assets/seripts/effects/BombEffectControl.ts.meta
Normal file
9
assets/seripts/effects/BombEffectControl.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "4835d6f8-1253-49a0-9d8f-4f1758fa2fea",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/seripts/libs.meta
Normal file
9
assets/seripts/libs.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "29562e3e-c67f-4642-9920-a90ed552b9d8",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
30
assets/seripts/libs/AudioEffect.ts
Normal file
30
assets/seripts/libs/AudioEffect.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { AudioManager } from "./AudioManager";
|
||||
|
||||
//音效管理类
|
||||
export default class AudioEffect {
|
||||
//播放通用的音效
|
||||
static playCommonAudio(audioUrl: string): void {
|
||||
if (!audioUrl) {
|
||||
return;
|
||||
}
|
||||
//播放音效
|
||||
AudioManager.inst.playOneShot(audioUrl);
|
||||
}
|
||||
//播放点击音效
|
||||
public static playClickAudio() {
|
||||
let audioUrl = "audios/common/click";
|
||||
AudioEffect.playCommonAudio(audioUrl);
|
||||
}
|
||||
|
||||
//播放撒金币的音效
|
||||
public static playGoldAudio() {
|
||||
let audioUrl = "audios/common/gold";
|
||||
AudioEffect.playCommonAudio(audioUrl);
|
||||
}
|
||||
|
||||
//播放金币入账的音效
|
||||
public static playCoinsEntryAudio() {
|
||||
let audioUrl = "audios/common/coins_entry";
|
||||
AudioEffect.playCommonAudio(audioUrl);
|
||||
}
|
||||
}
|
||||
9
assets/seripts/libs/AudioEffect.ts.meta
Normal file
9
assets/seripts/libs/AudioEffect.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "444d38fc-633e-43bb-824b-192c2ee2730e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
110
assets/seripts/libs/AudioManager.ts
Normal file
110
assets/seripts/libs/AudioManager.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { Node, AudioSource, AudioClip, resources, director } from "cc";
|
||||
/**
|
||||
* @en
|
||||
* this is a sington class for audio play, can be easily called from anywhere in you project.
|
||||
* @zh
|
||||
* 这是一个用于播放音频的单件类,可以很方便地在项目的任何地方调用。
|
||||
*/
|
||||
export class AudioManager {
|
||||
private static _inst: AudioManager;
|
||||
public static get inst(): AudioManager {
|
||||
if (this._inst == null) {
|
||||
this._inst = new AudioManager();
|
||||
}
|
||||
return this._inst;
|
||||
}
|
||||
private _audioSource: AudioSource;
|
||||
constructor() {
|
||||
//@en create a node as audioMgr
|
||||
//@zh 创建一个节点作为 audioMgr
|
||||
let audioMgr = new Node();
|
||||
audioMgr.name = "__audioMgr__";
|
||||
|
||||
//@en add to the scene.
|
||||
//@zh 添加节点到场景
|
||||
director.getScene().addChild(audioMgr);
|
||||
|
||||
//@en make it as a persistent node, so it won't be destroied when scene change.
|
||||
//@zh 标记为常驻节点,这样场景切换的时候就不会被销毁了
|
||||
director.addPersistRootNode(audioMgr);
|
||||
|
||||
//@en add AudioSource componrnt to play audios.
|
||||
//@zh 添加 AudioSource 组件,用于播放音频。
|
||||
this._audioSource = audioMgr.addComponent(AudioSource);
|
||||
}
|
||||
|
||||
public get audioSource() {
|
||||
return this._audioSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @en
|
||||
* play short audio, such as strikes,explosions
|
||||
* @zh
|
||||
* 播放短音频,比如 打击音效,爆炸音效等
|
||||
* @param sound clip or url for the audio
|
||||
* @param volume
|
||||
*/
|
||||
playOneShot(sound: AudioClip | string, volume: number = 1.0) {
|
||||
if (sound instanceof AudioClip) {
|
||||
this._audioSource.playOneShot(sound, volume);
|
||||
} else {
|
||||
resources.load(sound, (err, clip: AudioClip) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
this._audioSource.playOneShot(clip, volume);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @en
|
||||
* play long audio, such as the bg music
|
||||
* @zh
|
||||
* 播放长音频,比如 背景音乐
|
||||
* @param sound clip or url for the sound
|
||||
* @param volume
|
||||
*/
|
||||
play(sound: AudioClip | string, volume: number = 1.0) {
|
||||
if (sound instanceof AudioClip) {
|
||||
this._audioSource.stop();
|
||||
this._audioSource.clip = sound;
|
||||
this._audioSource.play();
|
||||
this.audioSource.volume = volume;
|
||||
} else {
|
||||
resources.load(sound, (err, clip: AudioClip) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
this._audioSource.stop();
|
||||
this._audioSource.clip = clip;
|
||||
this._audioSource.play();
|
||||
this.audioSource.volume = volume;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stop the audio play
|
||||
*/
|
||||
stop() {
|
||||
this._audioSource.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* pause the audio play
|
||||
*/
|
||||
pause() {
|
||||
this._audioSource.pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* resume the audio play
|
||||
*/
|
||||
resume() {
|
||||
this._audioSource.play();
|
||||
}
|
||||
}
|
||||
9
assets/seripts/libs/AudioManager.ts.meta
Normal file
9
assets/seripts/libs/AudioManager.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "3b9035fb-99e0-4028-9bc0-84149d0ba3bf",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
48
assets/seripts/libs/EventDispatcher.ts
Normal file
48
assets/seripts/libs/EventDispatcher.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { _decorator, Component, EventTarget } from "cc";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
const event_target = new EventTarget();
|
||||
|
||||
export class EventDispatcher {
|
||||
private static data: EventDispatcher;
|
||||
|
||||
// 小恐龙吃到音符事件
|
||||
public static readonly DINO_EAT_NOTE = "dino_eat_note";
|
||||
// 音符碰到线事件
|
||||
public static readonly UPDATE_LIFE = "update_life";
|
||||
// 开始游戏事件
|
||||
public static readonly GAME_STAET = "game_start";
|
||||
|
||||
// 游戏结束事件
|
||||
public static readonly GAME_OVER = "game_over";
|
||||
// 游戏闯关成功事件
|
||||
public static readonly PASS_LEVLE = "pass_levle";
|
||||
|
||||
// 暂停事件
|
||||
public static readonly GAME_PAUSE = "game_pause";
|
||||
// 继续游戏事件
|
||||
public static readonly GAME_RESUME = "game_resume";
|
||||
// 清除所有音符事件
|
||||
public static readonly CLEAR_ALL_NOTE = "clear_all_note";
|
||||
// 清楚可视区域的音符事件
|
||||
public static readonly CLEAR_SCREEN_NOTE = "clear_screen_note";
|
||||
// 移动小恐龙到线的位置事件
|
||||
public static readonly MOVE_DINO_TO_LINE = "move_dino_to_line";
|
||||
// 显示结果弹窗事件
|
||||
public static readonly SHOW_RESULT_MODAL = "show_result_modal";
|
||||
// 显示暂停弹窗事件
|
||||
public static readonly SHOW_PAUSE_MODAL = "show_pause_modal";
|
||||
// 显示继续游戏弹窗事件
|
||||
public static readonly SHOW_CONTINUE_MODAL = "show_continue_modal";
|
||||
|
||||
static getTarget() {
|
||||
if (EventDispatcher.data == null) {
|
||||
EventDispatcher.data = new EventDispatcher();
|
||||
}
|
||||
return EventDispatcher.data.getEventTarget();
|
||||
}
|
||||
|
||||
private getEventTarget(): EventTarget {
|
||||
return event_target;
|
||||
}
|
||||
}
|
||||
9
assets/seripts/libs/EventDispatcher.ts.meta
Normal file
9
assets/seripts/libs/EventDispatcher.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "aa122590-0731-48e3-890b-886796d95b60",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
506
assets/seripts/libs/SpeedAudioPlayer.ts
Normal file
506
assets/seripts/libs/SpeedAudioPlayer.ts
Normal file
@@ -0,0 +1,506 @@
|
||||
import { _decorator, AudioClip, sys } from "cc";
|
||||
const { ccclass } = _decorator;
|
||||
|
||||
// 声明微信小游戏全局变量(避免 TypeScript 报错)
|
||||
declare const wx: any;
|
||||
|
||||
@ccclass("SpeedAudioPlayer")
|
||||
export class SpeedAudioPlayer {
|
||||
// H5 平台核心对象
|
||||
private audioElement: HTMLAudioElement | null = null;
|
||||
// 微信小游戏平台核心对象
|
||||
private innerAudioContext: any = null;
|
||||
// 跨平台统一播放状态
|
||||
private currentPlayRate: number = 1.0;
|
||||
private volume: number = 1.0;
|
||||
private isPlaying: boolean = false;
|
||||
private isPaused: boolean = false;
|
||||
// 跨平台统一状态缓存(切换音频/恢复播放复用)
|
||||
private lastPlayState: { rate: number; position: number } = {
|
||||
rate: 1.0,
|
||||
position: 0,
|
||||
};
|
||||
// 缓存当前音频剪辑
|
||||
private currentAudioClip: AudioClip | null = null;
|
||||
|
||||
/**
|
||||
* 初始化音频(跨平台自动适配)
|
||||
* @param audioClip Cocos 音频剪辑
|
||||
*/
|
||||
async init(audioClip: AudioClip) {
|
||||
if (!audioClip) {
|
||||
console.error("SpeedAudioPlayer: 音频剪辑不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存当前音频剪辑
|
||||
this.currentAudioClip = audioClip;
|
||||
|
||||
// 1. H5 平台(浏览器/支持 DOM 的环境)
|
||||
if (this.isH5Platform()) {
|
||||
await this.initH5(audioClip);
|
||||
}
|
||||
// 2. 微信小游戏平台(原生 API 环境)
|
||||
else if (this.isWxGamePlatform()) {
|
||||
await this.initWxGame(audioClip);
|
||||
}
|
||||
// 3. 不支持的平台
|
||||
else {
|
||||
console.error("SpeedAudioPlayer: 暂不支持当前运行平台");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨平台核心:切换音频文件(保留/重置状态)
|
||||
* @param newAudioClip 新的音频剪辑
|
||||
* @param keepState 是否保留原播放状态(速率/暂停位置),默认true
|
||||
*/
|
||||
async changeAudioClip(newAudioClip: AudioClip, keepState: boolean = true) {
|
||||
if (!newAudioClip) {
|
||||
console.error("SpeedAudioPlayer: 切换音频失败,音频剪辑不能为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. 记录当前状态(跨平台统一)
|
||||
if (keepState) {
|
||||
this.lastPlayState.rate = this.currentPlayRate;
|
||||
this.lastPlayState.position = this.getCurrentPlayTime();
|
||||
} else {
|
||||
this.lastPlayState = { rate: 1.0, position: 0 };
|
||||
}
|
||||
|
||||
// 2. 停止当前音频(跨平台统一)
|
||||
this.stop();
|
||||
|
||||
// 3. 初始化新音频(自动适配平台)
|
||||
await this.init(newAudioClip);
|
||||
|
||||
// 4. 恢复播放状态(跨平台统一)
|
||||
if (keepState) {
|
||||
this.currentPlayRate = this.lastPlayState.rate;
|
||||
this.setPlayRate(this.currentPlayRate); // 同步速率到当前平台
|
||||
|
||||
// 定位到缓存位置,根据原状态恢复播放/暂停
|
||||
if (this.isPaused) {
|
||||
this.seek(this.lastPlayState.position);
|
||||
console.log(
|
||||
`SpeedAudioPlayer: 切换音频后保留暂停状态,位置:${this.lastPlayState.position.toFixed(
|
||||
3,
|
||||
)}秒`,
|
||||
);
|
||||
} else if (this.isPlaying) {
|
||||
this.play(this.currentPlayRate, this.lastPlayState.position);
|
||||
}
|
||||
}
|
||||
|
||||
console.log("SpeedAudioPlayer: 音频文件切换成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放音频(跨平台统一接口,支持指定速率/起始位置)
|
||||
* @param playRate 播放速率(0.5~2.0)
|
||||
* @param startAt 起始位置(秒,默认0)
|
||||
*/
|
||||
play(playRate: number = 1.0, startAt: number = 0) {
|
||||
// 限制参数范围
|
||||
this.currentPlayRate = Math.max(0.5, Math.min(playRate, 2.0));
|
||||
startAt = Math.max(0, Math.min(startAt, this.getTotalDuration()));
|
||||
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.playbackRate = this.currentPlayRate;
|
||||
this.audioElement.currentTime = startAt;
|
||||
this.audioElement
|
||||
.play()
|
||||
.then(() => {
|
||||
this.updatePlayState(true, false);
|
||||
console.log(
|
||||
`SpeedAudioPlayer: H5 播放开始,速率:${
|
||||
this.currentPlayRate
|
||||
},起始位置:${startAt.toFixed(3)}秒`,
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("SpeedAudioPlayer: H5 播放失败(需用户交互触发):", e);
|
||||
});
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.playbackRate = this.currentPlayRate;
|
||||
this.innerAudioContext.seek(startAt);
|
||||
this.innerAudioContext.play();
|
||||
this.updatePlayState(true, false);
|
||||
this.lastPlayState.position = startAt;
|
||||
console.log(
|
||||
`SpeedAudioPlayer: 微信小游戏播放开始,速率:${
|
||||
this.currentPlayRate
|
||||
},起始位置:${startAt.toFixed(3)}秒`,
|
||||
);
|
||||
}
|
||||
// 未初始化
|
||||
else {
|
||||
console.warn("SpeedAudioPlayer: 音频未初始化完成,无法播放");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停播放(跨平台统一接口,精准保留当前位置)
|
||||
*/
|
||||
pause() {
|
||||
if (!this.isPlaying || this.isPaused) {
|
||||
console.warn("SpeedAudioPlayer: 音频未播放或已暂停,无法暂停");
|
||||
return;
|
||||
}
|
||||
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.pause();
|
||||
const pausePos = this.audioElement.currentTime;
|
||||
this.cachePausePosition(pausePos);
|
||||
this.updatePlayState(false, true);
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.pause();
|
||||
const pausePos = this.innerAudioContext.currentTime;
|
||||
this.cachePausePosition(pausePos);
|
||||
this.updatePlayState(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复播放(跨平台统一接口,从暂停位置继续)
|
||||
*/
|
||||
resume() {
|
||||
if (!this.isPaused) {
|
||||
console.warn("SpeedAudioPlayer: 音频未暂停,无法恢复播放");
|
||||
return;
|
||||
}
|
||||
|
||||
const resumePos = this.lastPlayState.position;
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.currentTime = resumePos;
|
||||
this.audioElement
|
||||
.play()
|
||||
.then(() => {
|
||||
this.updatePlayState(true, false);
|
||||
console.log(
|
||||
`SpeedAudioPlayer: H5 恢复播放,从${resumePos.toFixed(3)}秒开始`,
|
||||
);
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error("SpeedAudioPlayer: H5 恢复播放失败:", e);
|
||||
});
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
// 恢复播放前确保应用当前设置的速率
|
||||
this.innerAudioContext.playbackRate = this.currentPlayRate;
|
||||
this.innerAudioContext.seek(resumePos);
|
||||
this.innerAudioContext.play();
|
||||
this.updatePlayState(true, false);
|
||||
console.log(
|
||||
`SpeedAudioPlayer: 微信小游戏恢复播放,速率:${
|
||||
this.currentPlayRate
|
||||
},从${resumePos.toFixed(3)}秒开始`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止播放(跨平台统一接口,重置位置到开头)
|
||||
*/
|
||||
stop() {
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.pause();
|
||||
this.audioElement.currentTime = 0;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.pause();
|
||||
this.innerAudioContext.seek(0);
|
||||
}
|
||||
|
||||
// 跨平台统一重置状态
|
||||
this.updatePlayState(false, false);
|
||||
this.lastPlayState.position = 0;
|
||||
console.log("SpeedAudioPlayer: 停止播放,状态已重置");
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态调节播放速率(跨平台统一接口,播放中实时生效)
|
||||
* @param playRate 新速率
|
||||
*/
|
||||
setPlayRate(playRate: number) {
|
||||
this.currentPlayRate = Math.max(0.5, Math.min(playRate, 2.0));
|
||||
this.lastPlayState.rate = this.currentPlayRate;
|
||||
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.playbackRate = this.currentPlayRate;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.playbackRate = this.currentPlayRate;
|
||||
|
||||
// 微信小游戏真机环境下,设置playbackRate后需要重启播放才能生效
|
||||
// 保存当前播放位置,暂停后重新播放
|
||||
if (this.isPlaying) {
|
||||
const currentPosition = this.getCurrentPlayTime();
|
||||
this.innerAudioContext.pause();
|
||||
this.innerAudioContext.seek(currentPosition);
|
||||
this.innerAudioContext.play();
|
||||
console.log(
|
||||
`SpeedAudioPlayer: 微信小游戏重启播放以应用新速率,当前位置:${currentPosition.toFixed(
|
||||
3,
|
||||
)}秒`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`SpeedAudioPlayer: 速率已调整为:${this.currentPlayRate}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置音量(跨平台统一接口,0~1)
|
||||
* @param volume 音量值
|
||||
*/
|
||||
setVolume(volume: number) {
|
||||
this.volume = Math.max(0, Math.min(volume, 1));
|
||||
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.volume = this.volume;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.volume = this.volume;
|
||||
}
|
||||
|
||||
console.log(`SpeedAudioPlayer: 音量已设置为:${this.volume}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取音频总时长(跨平台统一接口)
|
||||
*/
|
||||
getTotalDuration(): number {
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
return this.audioElement.duration || 0;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
return this.innerAudioContext.duration || 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前播放位置(跨平台统一接口,秒)
|
||||
*/
|
||||
getCurrentPlayTime(): number {
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
return this.audioElement.currentTime || 0;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
return this.innerAudioContext.currentTime || 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前播放状态(跨平台统一接口)
|
||||
*/
|
||||
getPlayState() {
|
||||
return {
|
||||
isPlaying: this.isPlaying,
|
||||
isPaused: this.isPaused,
|
||||
currentPlayRate: this.currentPlayRate,
|
||||
currentPlayTime: this.getCurrentPlayTime(),
|
||||
totalDuration: this.getTotalDuration(),
|
||||
volume: this.volume,
|
||||
platform: this.getCurrentPlatform(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁资源(跨平台统一接口,释放内存)
|
||||
*/
|
||||
destroy() {
|
||||
// H5 平台
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.stop();
|
||||
this.audioElement.remove();
|
||||
this.audioElement = null;
|
||||
}
|
||||
// 微信小游戏平台
|
||||
else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.stop();
|
||||
this.innerAudioContext.offCanplay();
|
||||
this.innerAudioContext.offEnded();
|
||||
this.innerAudioContext.offError();
|
||||
this.innerAudioContext.destroy();
|
||||
this.innerAudioContext = null;
|
||||
}
|
||||
|
||||
this.currentAudioClip = null;
|
||||
console.log(`SpeedAudioPlayer: ${this.getCurrentPlatform()} 资源已销毁`);
|
||||
}
|
||||
|
||||
// ---------------------- 内部辅助方法(跨平台适配核心)----------------------
|
||||
/**
|
||||
* 初始化 H5 平台音频(HTML5 Audio)
|
||||
*/
|
||||
private async initH5(audioClip: AudioClip) {
|
||||
// 首次创建 Audio 元素
|
||||
if (!this.audioElement) {
|
||||
this.audioElement = document.createElement("audio");
|
||||
this.audioElement.preload = "auto";
|
||||
// 监听播放结束
|
||||
this.audioElement.onended = () => {
|
||||
this.updatePlayState(false, false);
|
||||
this.lastPlayState.position = 0;
|
||||
console.log("SpeedAudioPlayer: H5 音频播放结束");
|
||||
};
|
||||
}
|
||||
|
||||
// 设置音频源并等待加载
|
||||
this.audioElement.src = audioClip.nativeUrl;
|
||||
await new Promise((resolve, reject) => {
|
||||
const onCanplayCallback = () => {
|
||||
this.audioElement!.removeEventListener(
|
||||
"canplaythrough",
|
||||
onCanplayCallback,
|
||||
);
|
||||
resolve(true);
|
||||
};
|
||||
this.audioElement!.addEventListener("canplaythrough", onCanplayCallback);
|
||||
this.audioElement!.addEventListener("error", (e) => {
|
||||
reject(new Error(`H5 音频加载失败:${e}`));
|
||||
});
|
||||
});
|
||||
|
||||
// 恢复缓存状态
|
||||
this.audioElement.volume = this.volume;
|
||||
this.audioElement.playbackRate = this.currentPlayRate;
|
||||
console.log(
|
||||
"SpeedAudioPlayer: H5 音频初始化成功,总时长:",
|
||||
this.getTotalDuration().toFixed(2),
|
||||
"秒",
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 微信小游戏 平台音频(wx.createInnerAudioContext)
|
||||
*/
|
||||
private async initWxGame(audioClip: AudioClip) {
|
||||
// 首次创建微信原生音频上下文
|
||||
if (!this.innerAudioContext) {
|
||||
this.innerAudioContext = wx.createInnerAudioContext();
|
||||
this.innerAudioContext.volume = this.volume;
|
||||
this.innerAudioContext.playbackRate = this.currentPlayRate;
|
||||
this.innerAudioContext.loop = false;
|
||||
|
||||
// 监听原生事件
|
||||
this.innerAudioContext.onCanplay(() => {
|
||||
console.log(
|
||||
"SpeedAudioPlayer: 微信小游戏音频加载完成,总时长:",
|
||||
this.getTotalDuration().toFixed(2),
|
||||
"秒",
|
||||
);
|
||||
});
|
||||
|
||||
this.innerAudioContext.onEnded(() => {
|
||||
this.updatePlayState(false, false);
|
||||
this.lastPlayState.position = 0;
|
||||
console.log("SpeedAudioPlayer: 微信小游戏音频播放结束");
|
||||
});
|
||||
|
||||
this.innerAudioContext.onError((err: any) => {
|
||||
console.error("SpeedAudioPlayer: 微信小游戏音频发生错误:", err);
|
||||
this.updatePlayState(false, false);
|
||||
});
|
||||
}
|
||||
|
||||
// 设置音频源并等待加载(核心修复:移除 onCanplayOnce,改用 onCanplay + 手动解除监听)
|
||||
this.innerAudioContext.src = audioClip.nativeUrl;
|
||||
await new Promise((resolve) => {
|
||||
// 定义一次性监听函数
|
||||
const onCanplayCallback = () => {
|
||||
// 手动移除监听,实现「一次性触发」的效果,避免重复回调
|
||||
this.innerAudioContext.offCanplay(onCanplayCallback);
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
// 先判断音频是否已加载完成
|
||||
if (this.innerAudioContext.readyState === "canplay") {
|
||||
resolve(true);
|
||||
} else {
|
||||
// 绑定普通 onCanplay 监听,触发后手动移除,替代不存在的 onCanplayOnce
|
||||
this.innerAudioContext.onCanplay(onCanplayCallback);
|
||||
}
|
||||
});
|
||||
|
||||
// 恢复缓存状态
|
||||
this.innerAudioContext.volume = this.volume;
|
||||
this.innerAudioContext.playbackRate = this.currentPlayRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 H5 平台(浏览器/支持 DOM 的环境)
|
||||
*/
|
||||
private isH5Platform(): boolean {
|
||||
return sys.isBrowser && sys.platform !== sys.Platform.WECHAT_GAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为 微信小游戏 平台
|
||||
*/
|
||||
private isWxGamePlatform(): boolean {
|
||||
return sys.platform === sys.Platform.WECHAT_GAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前平台名称(用于日志/调试)
|
||||
*/
|
||||
private getCurrentPlatform(): string {
|
||||
if (this.isH5Platform()) return "H5";
|
||||
if (this.isWxGamePlatform()) return "微信小游戏";
|
||||
return "未知平台";
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨平台统一更新播放状态
|
||||
*/
|
||||
private updatePlayState(isPlaying: boolean, isPaused: boolean) {
|
||||
this.isPlaying = isPlaying;
|
||||
this.isPaused = isPaused;
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨平台统一缓存暂停位置
|
||||
*/
|
||||
private cachePausePosition(position: number) {
|
||||
const clampPos = Math.max(0, Math.min(position, this.getTotalDuration()));
|
||||
this.lastPlayState.position = clampPos;
|
||||
console.log(
|
||||
`SpeedAudioPlayer: 暂停成功,当前位置:${clampPos.toFixed(3)}秒`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 跨平台统一定位音频位置
|
||||
*/
|
||||
private seek(position: number) {
|
||||
const clampPos = Math.max(0, Math.min(position, this.getTotalDuration()));
|
||||
if (this.isH5Platform() && this.audioElement) {
|
||||
this.audioElement.currentTime = clampPos;
|
||||
} else if (this.isWxGamePlatform() && this.innerAudioContext) {
|
||||
this.innerAudioContext.seek(clampPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
9
assets/seripts/libs/SpeedAudioPlayer.ts.meta
Normal file
9
assets/seripts/libs/SpeedAudioPlayer.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "15d63406-5748-40e4-a743-a45bc228d712",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
34
assets/seripts/libs/Utils.ts
Normal file
34
assets/seripts/libs/Utils.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { sys } from "cc";
|
||||
|
||||
export default class Utils {
|
||||
/**
|
||||
*
|
||||
* @param {缓存key} key
|
||||
* @param {需要存储的缓存值} value
|
||||
* @param {过期时间} expire
|
||||
*/
|
||||
static setCache(key: string, value: any, expire: number = 0): void {
|
||||
let obj = {
|
||||
data: value, //存储的数据
|
||||
time: Date.now() / 1000, //记录存储的时间戳
|
||||
expire: expire, //记录过期时间,单位秒
|
||||
};
|
||||
sys.localStorage.setItem(key, JSON.stringify(obj));
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param {缓存key} key
|
||||
*/
|
||||
static getCache(key: string): any {
|
||||
let val: any = sys.localStorage.getItem(key);
|
||||
if (!val) {
|
||||
return null;
|
||||
}
|
||||
val = JSON.parse(val);
|
||||
if (val.expire && Date.now() / 1000 - val.time > val.expire) {
|
||||
sys.localStorage.removeItem(key);
|
||||
return null;
|
||||
}
|
||||
return val.data;
|
||||
}
|
||||
}
|
||||
9
assets/seripts/libs/Utils.ts.meta
Normal file
9
assets/seripts/libs/Utils.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "ee787eae-f32a-4d63-9fa4-b609ca06be8e",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/seripts/managers.meta
Normal file
9
assets/seripts/managers.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "c198366b-298e-4a4d-9c46-69c68a447c02",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
87
assets/seripts/managers/MusicManager.ts
Normal file
87
assets/seripts/managers/MusicManager.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 游戏开发者:程序员加菲猫
|
||||
* 游戏名称:Like A Dino!
|
||||
*/
|
||||
|
||||
import { _decorator, Component, AudioClip } from "cc";
|
||||
import { EventDispatcher } from "../libs/EventDispatcher";
|
||||
import { SpeedAudioPlayer } from "../libs/SpeedAudioPlayer";
|
||||
import { GlobalData } from "../config/GlobalData";
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("MusicManager")
|
||||
export class MusicManager extends Component {
|
||||
//音频播放器
|
||||
private audioPlayer: SpeedAudioPlayer = null;
|
||||
|
||||
//音频剪辑列表
|
||||
@property({
|
||||
type: [AudioClip],
|
||||
tooltip: "音频剪辑列表",
|
||||
})
|
||||
audioClips: AudioClip[] = [];
|
||||
|
||||
//当前选择的音频剪辑索引
|
||||
private currentAudioClipIndex: number = 0;
|
||||
|
||||
async onLoad() {
|
||||
//设置当前选择的音频剪辑
|
||||
this.currentAudioClipIndex = GlobalData.selectedMusicID - 1;
|
||||
//初始化音频播放器
|
||||
this.audioPlayer = new SpeedAudioPlayer();
|
||||
//初始化音频
|
||||
await this.audioPlayer.init(this.audioClips[this.currentAudioClipIndex]);
|
||||
}
|
||||
|
||||
start() {
|
||||
//监听游戏暂停事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.GAME_PAUSE,
|
||||
this.pause,
|
||||
this,
|
||||
);
|
||||
//监听游戏继续事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.GAME_RESUME,
|
||||
this.resume,
|
||||
this,
|
||||
);
|
||||
//监听游戏结束事件
|
||||
EventDispatcher.getTarget().on(EventDispatcher.GAME_OVER, this.stop, this);
|
||||
}
|
||||
|
||||
//播放音乐
|
||||
async play() {
|
||||
//播放之前判断是否需要切换音频
|
||||
if (this.currentAudioClipIndex !== GlobalData.selectedMusicID - 1) {
|
||||
//切换音频
|
||||
this.currentAudioClipIndex = GlobalData.selectedMusicID - 1;
|
||||
//初始化音频
|
||||
await this.audioPlayer.changeAudioClip(
|
||||
this.audioClips[this.currentAudioClipIndex],
|
||||
);
|
||||
}
|
||||
// 初始速率播放
|
||||
this.audioPlayer.play(GlobalData.speedRate);
|
||||
// 设置初始音量
|
||||
this.audioPlayer.setVolume(1.0);
|
||||
}
|
||||
|
||||
//暂停音乐
|
||||
pause() {
|
||||
this.audioPlayer.pause();
|
||||
}
|
||||
|
||||
//停止音乐
|
||||
stop() {
|
||||
this.audioPlayer.stop();
|
||||
}
|
||||
|
||||
//继续播放音乐
|
||||
resume() {
|
||||
this.audioPlayer.resume();
|
||||
}
|
||||
|
||||
update(deltaTime: number) {}
|
||||
}
|
||||
9
assets/seripts/managers/MusicManager.ts.meta
Normal file
9
assets/seripts/managers/MusicManager.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "d4834116-9657-4a4e-8a40-4f562e85b280",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
1330
assets/seripts/managers/NoteManager.ts
Normal file
1330
assets/seripts/managers/NoteManager.ts
Normal file
File diff suppressed because it is too large
Load Diff
9
assets/seripts/managers/NoteManager.ts.meta
Normal file
9
assets/seripts/managers/NoteManager.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "f2244da7-be8d-4fbe-80d3-3e01758d7c74",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/seripts/scenes.meta
Normal file
9
assets/seripts/scenes.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "e97362e9-74f8-449b-8da5-adcbe192068a",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
125
assets/seripts/scenes/GameSceneControl.ts
Normal file
125
assets/seripts/scenes/GameSceneControl.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { _decorator, Component, google, Label, Node } from "cc";
|
||||
import { GlobalData } from "../config/GlobalData";
|
||||
import { EventDispatcher } from "../libs/EventDispatcher";
|
||||
import { GameState } from "../config/Config";
|
||||
import { MusicManager } from "../managers/MusicManager";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("GameSceneControl")
|
||||
export class GameSceneControl extends Component {
|
||||
// 当前这一局得分label
|
||||
@property(Label)
|
||||
curRoundScoreLabel: Label = null;
|
||||
|
||||
// 当前这句剩余生命值label
|
||||
@property(Label)
|
||||
lifeNumLabel: Label = null;
|
||||
|
||||
// ui 节点
|
||||
@property(Node)
|
||||
uiNode: Node = null;
|
||||
|
||||
// 音频管理器
|
||||
musicManager: MusicManager = null;
|
||||
|
||||
onLoad() {
|
||||
// 获取音频管理器
|
||||
this.musicManager = this.node.getComponent(MusicManager);
|
||||
|
||||
// 监听小恐龙吃到音符事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.DINO_EAT_NOTE,
|
||||
this.updateCurRoundScoreLabel,
|
||||
this,
|
||||
);
|
||||
// 监听音符碰到线事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.UPDATE_LIFE,
|
||||
this.updateLifeNumLabel,
|
||||
this,
|
||||
);
|
||||
// 监听游戏结束事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.GAME_OVER,
|
||||
this.handleEndGame,
|
||||
this,
|
||||
);
|
||||
// 监听过关事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.PASS_LEVLE,
|
||||
this.handlePassLevel,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
start() {
|
||||
// 重置游戏数据
|
||||
GlobalData.resetGameData();
|
||||
this.startGame();
|
||||
}
|
||||
|
||||
startGame() {
|
||||
// 开始游戏
|
||||
this.uiNode.active = true;
|
||||
// 更新 ui 节点相关数据
|
||||
this.updateUi();
|
||||
// 设置游戏状态为进行中
|
||||
GlobalData.gameState = GameState.PLAYING;
|
||||
// 开始游戏提示动画
|
||||
|
||||
// 1 s 后正式开始游戏
|
||||
this.scheduleOnce(() => {
|
||||
// 开始播放背景音乐
|
||||
this.playMusic();
|
||||
// 发送开始游戏事件
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.GAME_STAET);
|
||||
}, 1);
|
||||
}
|
||||
|
||||
// 更新 ui
|
||||
updateUi() {
|
||||
// 更新本局总得分
|
||||
this.updateCurRoundScoreLabel();
|
||||
// 更新生命值
|
||||
this.updateLifeNumLabel();
|
||||
// 更新当前速度的显示
|
||||
this.updateSpeed();
|
||||
}
|
||||
|
||||
// 更新当前这一局得分
|
||||
updateCurRoundScoreLabel() {
|
||||
this.curRoundScoreLabel.string = GlobalData.curRoundTotalScore.toString();
|
||||
}
|
||||
|
||||
// 更新当前这句剩余生命值
|
||||
updateLifeNumLabel() {
|
||||
this.lifeNumLabel.string = "x" + GlobalData.lifeNumPerRound.toString();
|
||||
}
|
||||
|
||||
// 更新速度显示
|
||||
updateSpeed() {}
|
||||
|
||||
// 暂停游戏
|
||||
pauseGame() {
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.SHOW_PAUSE_MODAL);
|
||||
}
|
||||
|
||||
// 处理游戏结束时
|
||||
handleEndGame() {
|
||||
// 游戏结束,弹出游戏界面
|
||||
this.uiNode.active = false;
|
||||
}
|
||||
|
||||
// 闯过当前关卡时 重新开始下一轮
|
||||
handlePassLevel() {
|
||||
// 增加速率
|
||||
GlobalData.speedRate += GlobalData.speedRateAdd;
|
||||
// 继续开始游戏
|
||||
this.startGame();
|
||||
}
|
||||
|
||||
// 播放音乐
|
||||
playMusic() {
|
||||
this.musicManager.play();
|
||||
}
|
||||
}
|
||||
9
assets/seripts/scenes/GameSceneControl.ts.meta
Normal file
9
assets/seripts/scenes/GameSceneControl.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "60d28f0e-e562-4a8a-9321-551fa0e9ab1c",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
48
assets/seripts/scenes/HomeSceneControl.ts
Normal file
48
assets/seripts/scenes/HomeSceneControl.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { _decorator, Component, director, Node } from "cc";
|
||||
import AudioEffect from "../libs/AudioEffect";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("HomeSceneCon")
|
||||
export class HomeSceneCon extends Component {
|
||||
// 滑动区域节点
|
||||
@property(Node)
|
||||
swipeNode: Node = null;
|
||||
|
||||
start() {
|
||||
// 监听滑动区域事件
|
||||
this.swipeNode.on(Node.EventType.TOUCH_START, this.goToGameScene, this);
|
||||
}
|
||||
|
||||
// 跳转到游戏场景
|
||||
goToGameScene() {
|
||||
director.preloadScene("game", () => {
|
||||
director.loadScene("game");
|
||||
});
|
||||
}
|
||||
|
||||
// 跳转到 love-story 场景
|
||||
goToLoveStoryScene() {
|
||||
AudioEffect.playClickAudio();
|
||||
director.preloadScene("love-story", () => {
|
||||
director.loadScene("love-story");
|
||||
});
|
||||
}
|
||||
|
||||
// 跳转到 mood 场景
|
||||
goToMoodScene() {
|
||||
AudioEffect.playClickAudio();
|
||||
director.preloadScene("mood", () => {
|
||||
director.loadScene("mood");
|
||||
});
|
||||
}
|
||||
|
||||
// 跳转到作者场景
|
||||
goToAuthorScene() {
|
||||
AudioEffect.playClickAudio();
|
||||
director.preloadScene("author", () => {
|
||||
director.loadScene("author");
|
||||
});
|
||||
}
|
||||
|
||||
update(deltaTime: number) {}
|
||||
}
|
||||
9
assets/seripts/scenes/HomeSceneControl.ts.meta
Normal file
9
assets/seripts/scenes/HomeSceneControl.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "fd8f17a5-c483-4f69-8a79-bdeca9aa1946",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
9
assets/seripts/ui.meta
Normal file
9
assets/seripts/ui.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "4550d8cf-6b43-46f5-a29e-33332da2db14",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
104
assets/seripts/ui/ContonueModal.ts
Normal file
104
assets/seripts/ui/ContonueModal.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { _decorator, Component, Node, tween, UITransform, Vec3 } from "cc";
|
||||
import { EventDispatcher } from "../libs/EventDispatcher";
|
||||
import { GlobalData } from "../config/GlobalData";
|
||||
import { GameState } from "../config/Config";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("ContonueModal")
|
||||
export class ContonueModal extends Component {
|
||||
// 滑动区域的节点
|
||||
@property(Node)
|
||||
private swipeNode: Node = null;
|
||||
|
||||
// 提示 1 节点
|
||||
@property(Node)
|
||||
private tipNode1: Node = null;
|
||||
|
||||
// 提示 2 节点
|
||||
@property(Node)
|
||||
private tipNode2: Node = null;
|
||||
|
||||
start() {
|
||||
// 隐藏继续游戏弹窗
|
||||
this.node.active = false;
|
||||
// 初始化位置
|
||||
this.node.setPosition(0, 0, 0);
|
||||
// 监听继续游戏事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.SHOW_CONTINUE_MODAL,
|
||||
this.showModal,
|
||||
this,
|
||||
);
|
||||
// 监听滑动事件
|
||||
this.swipeNode.on(Node.EventType.TOUCH_START, this.onSwipeStart, this);
|
||||
}
|
||||
|
||||
// 显示继续游戏弹窗
|
||||
showModal() {
|
||||
// 游戏暂停
|
||||
GlobalData.gameState = GameState.PAUSED;
|
||||
// 显示继续游戏弹窗
|
||||
this.node.active = true;
|
||||
// 操作提示动画
|
||||
if (GlobalData.lifeNumPerRound > 0) {
|
||||
// 执行提示1的动画
|
||||
this.playTipAnimation(this.tipNode1);
|
||||
} else {
|
||||
// 执行提示2的动画
|
||||
this.playTipAnimation(this.tipNode2);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭继续游戏弹窗
|
||||
closeModal() {
|
||||
// 关闭继续游戏弹窗
|
||||
this.node.active = false;
|
||||
}
|
||||
|
||||
// 滑动开始事件
|
||||
onSwipeStart() {
|
||||
// 关闭继续游戏弹窗
|
||||
this.closeModal();
|
||||
// 等 2 s之后恢复游戏
|
||||
this.scheduleOnce(() => {
|
||||
GlobalData.gameState = GameState.PLAYING;
|
||||
// 发送继续游戏事件
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.GAME_RESUME);
|
||||
}, 2);
|
||||
}
|
||||
|
||||
// 暂停游戏
|
||||
onPauseGame() {
|
||||
console.log(111, "暂停游戏");
|
||||
// 发送暂停事件
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.SHOW_PAUSE_MODAL);
|
||||
}
|
||||
|
||||
// 播放提示1的动画
|
||||
playTipAnimation(tNode: Node) {
|
||||
// 显示提示1
|
||||
tNode.active = true;
|
||||
// 获取提示节点的位置
|
||||
const oriPos = tNode.getPosition().clone();
|
||||
// 获取提示的宽度
|
||||
const tipWidth = tNode.getComponent(UITransform).width + 5;
|
||||
// 执行动画
|
||||
tween(tNode)
|
||||
.to(0.3, {
|
||||
position: new Vec3(oriPos.x - tipWidth, oriPos.y, 0),
|
||||
})
|
||||
.delay(0.8)
|
||||
.to(0.3, {
|
||||
position: oriPos,
|
||||
})
|
||||
.call(() => {
|
||||
// 隐藏提示1
|
||||
tNode.active = false;
|
||||
// 重置节点位置
|
||||
tNode.setPosition(oriPos);
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
update(deltaTime: number) {}
|
||||
}
|
||||
9
assets/seripts/ui/ContonueModal.ts.meta
Normal file
9
assets/seripts/ui/ContonueModal.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "6ae5f280-b903-4537-b1d2-b9bbccba26b8",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
53
assets/seripts/ui/PauseModal.ts
Normal file
53
assets/seripts/ui/PauseModal.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { _decorator, Component, director, Node } from "cc";
|
||||
import { EventDispatcher } from "../libs/EventDispatcher";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("PauseModal")
|
||||
export class PauseModal extends Component {
|
||||
start() {
|
||||
// 隐藏暂停游戏弹窗
|
||||
this.node.active = false;
|
||||
// 初始化位置
|
||||
this.node.setPosition(0, 0, 0);
|
||||
// 监听继续游戏事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.SHOW_PAUSE_MODAL,
|
||||
this.showModal,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
// 显示弹窗
|
||||
showModal() {
|
||||
this.node.active = true;
|
||||
// 暂停游戏
|
||||
director.pause();
|
||||
// 发送暂停游戏事件
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.GAME_PAUSE);
|
||||
// 发送继续游戏弹窗
|
||||
EventDispatcher.getTarget().emit(EventDispatcher.SHOW_CONTINUE_MODAL);
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
closeModal() {
|
||||
this.node.active = false;
|
||||
// 恢复游戏继续
|
||||
director.resume();
|
||||
}
|
||||
|
||||
// 回到首页
|
||||
backToHome() {
|
||||
// 恢复游戏继续
|
||||
director.resume();
|
||||
// 跳转到首页
|
||||
director.loadScene("home");
|
||||
}
|
||||
|
||||
// 继续游戏
|
||||
resumeGame() {
|
||||
// 关闭弹窗
|
||||
this.closeModal();
|
||||
}
|
||||
|
||||
update(deltaTime: number) {}
|
||||
}
|
||||
9
assets/seripts/ui/PauseModal.ts.meta
Normal file
9
assets/seripts/ui/PauseModal.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "976546b4-b786-4c64-b005-ca9feafa8dbd",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
193
assets/seripts/ui/ResultModal.ts
Normal file
193
assets/seripts/ui/ResultModal.ts
Normal file
@@ -0,0 +1,193 @@
|
||||
import {
|
||||
_decorator,
|
||||
Component,
|
||||
director,
|
||||
Label,
|
||||
Node,
|
||||
tween,
|
||||
UITransform,
|
||||
Vec3,
|
||||
} from "cc";
|
||||
import { EventDispatcher } from "../libs/EventDispatcher";
|
||||
import { GlobalData } from "../config/GlobalData";
|
||||
import AudioEffect from "../libs/AudioEffect";
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
@ccclass("ResultModal")
|
||||
export class ResultModal extends Component {
|
||||
// 本局得分的 label
|
||||
@property(Label)
|
||||
scoreLabel: Label = null;
|
||||
|
||||
// 最佳得分 label
|
||||
@property(Label)
|
||||
bestScoreLabel: Label = null;
|
||||
|
||||
// 总的金币数 label
|
||||
@property(Label)
|
||||
totalScoreLabel: Label = null;
|
||||
|
||||
// 回到首页的按钮节点
|
||||
@property(Node)
|
||||
backToHomeBtn: Node = null;
|
||||
|
||||
// 提示节点
|
||||
@property(Node)
|
||||
tipNode: Node = null;
|
||||
|
||||
start() {
|
||||
// 隐藏结果弹窗
|
||||
this.node.active = false;
|
||||
// 设置位置
|
||||
this.node.setPosition(0, 0);
|
||||
// 注册打开结果弹窗的事件
|
||||
EventDispatcher.getTarget().on(
|
||||
EventDispatcher.SHOW_RESULT_MODAL,
|
||||
this.openModal,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
// 打开结果弹窗
|
||||
openModal() {
|
||||
// 显示结果弹窗
|
||||
this.node.active = true;
|
||||
// 设置当前得分
|
||||
this.scoreLabel.string = GlobalData.curRoundTotalScore.toString();
|
||||
// 累计总的金币数
|
||||
GlobalData.totalScore += GlobalData.curRoundTotalScore;
|
||||
// 设置总的金币数
|
||||
this.setTotalScoreLabel();
|
||||
// 设置当前用户最好的得分成绩
|
||||
this.setCurUserBestScoreLabel();
|
||||
// 执行相关动效
|
||||
this.scheduleOnce(async () => {
|
||||
await this.playGoldEffect();
|
||||
// 再执行提示信息的动画
|
||||
this.playTipAni();
|
||||
// 显示回到首页的按钮
|
||||
this.backToHomeBtn.active = true;
|
||||
}, 1);
|
||||
}
|
||||
|
||||
// 关闭游戏结束弹窗方法
|
||||
closeModal() {
|
||||
this.node.active = false;
|
||||
// 隐藏回到首页的按钮
|
||||
this.backToHomeBtn.active = false;
|
||||
// 重置分数
|
||||
this.scoreLabel.string = "0";
|
||||
}
|
||||
|
||||
setTotalScoreLabel() {
|
||||
this.totalScoreLabel.string = GlobalData.totalScore.toString();
|
||||
}
|
||||
|
||||
setCurUserBestScoreLabel() {
|
||||
const bestScore = GlobalData.bestScore;
|
||||
// 如果当前成绩大于最好成绩 更新最好成绩
|
||||
if (GlobalData.curRoundTotalScore > bestScore) {
|
||||
GlobalData.bestScore = GlobalData.curRoundTotalScore;
|
||||
}
|
||||
// 设置最佳得分 label
|
||||
this.bestScoreLabel.string = GlobalData.bestScore.toString();
|
||||
}
|
||||
|
||||
setBestScoreLabel() {}
|
||||
|
||||
// 播放金币特效
|
||||
async playGoldEffect() {
|
||||
// 获取 gold coins 节点
|
||||
const goldCoinsNode = this.node
|
||||
.getChildByName("panel")
|
||||
.getChildByName("gold_coins");
|
||||
// 开启此节点
|
||||
goldCoinsNode.active = true;
|
||||
|
||||
// 播放撒金币音效
|
||||
AudioEffect.playGoldAudio();
|
||||
// 0.5 s 后执行入账音效
|
||||
this.scheduleOnce(() => {
|
||||
AudioEffect.playCoinsEntryAudio();
|
||||
}, 0.5);
|
||||
// 获取所有金币子节点
|
||||
const goldCoinsList = goldCoinsNode.children;
|
||||
// 获取显示金币数量的标签节点
|
||||
const goldCoinsNumNode = this.node
|
||||
.getChildByName("gold_label")
|
||||
.getChildByName("gold_num");
|
||||
// 获取目标节点的位置
|
||||
const targetPos = goldCoinsNumNode.getWorldPosition();
|
||||
// 遍历金币子节点
|
||||
for (let i = 0; i < goldCoinsList.length; i++) {
|
||||
// 克隆位置
|
||||
const targetPosColne = targetPos.clone();
|
||||
// 执行单个金币的动画
|
||||
const goldCoin = goldCoinsList[i];
|
||||
await this.playSingleGoldAni(goldCoin, targetPosColne);
|
||||
}
|
||||
}
|
||||
|
||||
// 执行单个金币的动画
|
||||
async playSingleGoldAni(goldCoin: Node, targetPos: Vec3) {
|
||||
return new Promise((resolve) => {
|
||||
tween(goldCoin)
|
||||
.by(0.2, {
|
||||
position: new Vec3(0, -30, 0),
|
||||
})
|
||||
.call(() => {
|
||||
// 开始执行下一个
|
||||
resolve(true);
|
||||
// 获取金币的世界坐标
|
||||
const goleCoinWorldPos = goldCoin.getWorldPosition();
|
||||
// 计算金币节点与目标距离的差值
|
||||
const diffPost = targetPos.subtract(goleCoinWorldPos);
|
||||
// 再缓动到目标位置
|
||||
tween(goldCoin)
|
||||
.by(0.5, { position: diffPost })
|
||||
.call(() => {
|
||||
// 金币消失
|
||||
goldCoin.active = false;
|
||||
})
|
||||
.start();
|
||||
})
|
||||
.start();
|
||||
});
|
||||
}
|
||||
|
||||
// 执行提示动画
|
||||
playTipAni() {
|
||||
// 开启提示
|
||||
this.tipNode.active = true;
|
||||
// 获取提示的位置
|
||||
const oriPos = this.tipNode.getPosition().clone();
|
||||
// 获取提示的宽度
|
||||
const tipWidth = this.tipNode.getComponent(UITransform).width + 5;
|
||||
|
||||
// 执行动画
|
||||
tween(this.tipNode)
|
||||
.to(0.3, {
|
||||
position: new Vec3(oriPos.x - tipWidth, oriPos.y, 0),
|
||||
})
|
||||
.delay(0.8)
|
||||
.to(0.3, {
|
||||
position: oriPos,
|
||||
})
|
||||
.call(() => {
|
||||
// 隐藏提示1
|
||||
this.tipNode.active = false;
|
||||
// 重置节点位置
|
||||
this.tipNode.setPosition(oriPos);
|
||||
})
|
||||
.start();
|
||||
}
|
||||
|
||||
// 回到首页
|
||||
backToHome() {
|
||||
// 播放点击音效
|
||||
AudioEffect.playClickAudio();
|
||||
director.loadScene("home");
|
||||
}
|
||||
|
||||
update(deltaTime: number) {}
|
||||
}
|
||||
9
assets/seripts/ui/ResultModal.ts.meta
Normal file
9
assets/seripts/ui/ResultModal.ts.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "44c80c77-90c5-4293-84b6-4c2d541d2f1b",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
Reference in New Issue
Block a user