first commit
This commit is contained in:
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": {}
|
||||
}
|
||||
Reference in New Issue
Block a user