first commit

This commit is contained in:
zzc
2026-04-30 00:13:37 +08:00
commit 493b4709bc
189 changed files with 24791 additions and 0 deletions

View 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) {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "3af5932a-d1bb-4429-b815-ab2215ac75e8",
"files": [],
"subMetas": {},
"userData": {}
}

View 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) {}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "2ae0bd15-7a12-4d46-825c-b0d40fc8db6b",
"files": [],
"subMetas": {},
"userData": {}
}

View 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,
);
}
}

View File

@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "0b44dfc7-4b5b-4e74-a075-6fb71b84fb64",
"files": [],
"subMetas": {},
"userData": {}
}