import { _decorator, Animation, animation, AudioClip, AudioSource, Collider2D, Color, Component, Contact2DType, director, ERigidBody2DType, game, Input, input, instantiate, Label, Node, Prefab, RigidBody2D, SceneAsset, sp, Sprite, Tween, tween, UITransform, Vec2, Vec3 } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('Controller2')
export class Controller extends Component {
// 接方块
@property(Node) finishBoard: Node = null;
@property(sp.Skeleton) finishSkeleton: sp.Skeleton = null;
@property(Node) SliderDistrict: Node = null;
@property(Node) Slider: Node = null;
@property(sp.Skeleton) clockSkeleton: sp.Skeleton = null;
@property(Label) clockLabel: Label = null;
@property(Node) greenLine: Node = null;
@property(Node) Plate: Node = null;
@property(Prefab) RubikCubePrefab: Prefab = null;
@property(Node) RubikCubeParent: Node = null;
@property(sp.Skeleton) machineSkeleton: sp.Skeleton = null;
@property(Node) machine: Node = null;
@property(Node) Ground: Node = null;
@property(Node) star_1: Node = null;
@property(Node) star_2: Node = null;
@property(Node) Countdown: Node = null;
@property(Node) x1: Node = null;
@property(Animation) countdownAnimation: Animation = null;
@property(Node) star: Node = null;
@property(sp.Skeleton) satrSkeleton: sp.Skeleton = null;
@property(AudioSource) audioSource: AudioSource = null;
@property(AudioClip) countDownAudio: AudioClip = null;
@property(AudioClip) settlementClip: AudioClip = null;
@property(AudioSource) machineAudioSource: AudioSource = null;
@property(AudioClip) machineClip: AudioClip = null;
@property(AudioSource) rubikAudioSource: AudioSource = null;
@property(AudioClip) rubikRightClip: AudioClip = null;
@property(AudioClip) rubikWrongClip: AudioClip = null;
@property(AudioSource) disappearAudioSource: AudioSource = null;
@property(AudioClip) disappearClip: AudioClip = null;
@property(AudioSource) starAppearAudioSource: AudioSource = null;
@property(AudioClip) starAppearClip: AudioClip = null;
// 核心状态变量
isPressed: boolean = false;
isDragging: boolean = false;
sliderX: number = 0;
isMoving: boolean = false;
deadZone: number = 5; // 死区,滑杆在这个范围内不移动
isRunning: boolean = false; // 是否正在运行游戏
totalTime: number = 120; // 总时间
currentTime: number = 0; // 剩余时间
currentStage: "01" | "02" | "03" = "03"; // 当前关卡
colllidercount: number = 0; // 碰撞计数器
PlatePos: Vec3 = null;
rubikCubeSkins: string[] = [
"Hong",
"Huang",
"Lv",
];
speed: number = 100;
starNum: number = 0;
startGame: boolean = false;
stackedCubes: Node[] = []; // 记录已堆叠的魔方
start() {
this.Animation();
}
Animation(){
//先暂停游戏,等待动画播放完毕
this.countdownAnimation.play();
this.audioSource.clip = this.countDownAudio;
this.audioSource.play();
console.log("🎮 开始播放动画");
// 使用事件类型常量
this.countdownAnimation.once(Animation.EventType.FINISHED, () => {
this.Countdown.active = false;
this.onStart();
}, this);
}
onStart(){
// 初始化变量
this.startGame = true;
console.log("🎮 Controller组件初始化完毕");
this.SliderMove();
this.currentTime = this.totalTime;
this.isRunning = true;
this.schedule(this.generate, 4);
}
SliderMove(){
//全局监听TOUCH_START,通过坐标判断是否在滑块上
input.on(Input.EventType.TOUCH_START, this.onGlobalTouchStart, this);
input.on(Input.EventType.TOUCH_MOVE, this.onGlobalTouchMove, this);
input.on(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
}
private onGlobalTouchStart(event: any): void {
if(this.isPressed){
return; // 已经按下了,忽略
}
const touchPos = event.touch.getUILocation();//手指坐标,UI location相当于世界坐标
const sliderPos = this.SliderDistrict.getWorldPosition();//滑杆节点世界坐标
if(touchPos.x > sliderPos.x - 138 && touchPos.x < sliderPos.x + 138 && touchPos.y > sliderPos.y - 59 && touchPos.y < sliderPos.y + 59){
//console.log("✅ 触摸在滑块上,可以开始拖拽");
this.isPressed = true;
this.isDragging = false;
}
else{
console.log("❌ 触摸不在滑块上,忽略");
}
}
private onGlobalTouchMove(event: any): void {
// 只有先按下滑块才会响应移动
if (!this.isPressed) {
console.log("❌ 没有按下滑块,不能拖拽");
return; // 没按下滑块,忽略移动
}
//console.log("⏳ 拖拽中");
this.isDragging = true; // 标记为正在拖拽
let touchPos = event.touch.getUILocation();//手指坐标,UI location相当于世界坐标
let sliderPos = this.SliderDistrict.getWorldPosition();//滑杆节点世界坐标
this.sliderX = touchPos.x - sliderPos.x;//横坐标差
let sliderScope = 97;
//限制滑杆在父节点的宽度的一半范围内
if (this.sliderX < -sliderScope) {
this.sliderX = -sliderScope;
} else if (this.sliderX > sliderScope) {
this.sliderX = sliderScope;
}
this.Slider.setPosition(this.sliderX, 0, 0);
}
private onGlobalTouchEnd(event: any): void {
// 只有在拖拽状态才需要处理松开事件
if (!this.isPressed) {
return; // 没有在拖拽,忽略松开
}
//console.log("🔚 松开鼠标,拖拽结束");
this.Slider.setPosition(0, 0, 0);
this.sliderX = 0;
// 重置所有状态,下次必须重新在滑块上按下才能拖拽
this.isPressed = false;
this.isDragging = false;
this.isMoving = false;
// this PlateSkeleton.setAnimation(0, "idle_01", true);
}
private clock(deltaTime: number){
// ✅ 安全的Label更新
if (this.clockLabel) {
this.clockLabel.string = `${this.currentTime.toFixed(0)}s`;
//console.log(this.clockLabel.string);
}
// ✅ 安全的greenLine更新
const line = this.greenLine.getComponent(UITransform);
const color = this.greenLine.getComponent(Sprite);
if (line) {
line.setContentSize(86 * this.currentTime / this.totalTime, 12);
}
let stage: '01' | '02' | '03';
if(!this.isRunning){
//console.log("游戏结束");
return;
}
this.currentTime -= deltaTime;
// console.log(this.currentTime);
if(this.currentTime >= this.totalTime / 2 && this.currentTime <= this.totalTime){
stage = '01';
//color.color = Color.GREEN;标准绿
color.color = new Color(0, 190, 0, 255);
//this.clockSkeleton.setAnimation(0, "02_YunSu", true);
}
else if(this.currentTime > 0.01667 && this.currentTime < this.totalTime / 2){
stage = '02';
color.color = new Color(255, 0, 0, 255);
//this.clockSkeleton.setAnimation(0, "03_JiaSu", true);
}
else{
stage = '03';
this.isRunning = false;
//console.log("游戏结束");
this.gameOver();
}
// 只有当目标状态与当前状态不同时才切换动画
if (stage !== this.currentStage) {
this.switchTimeAnimation(stage);
}
}
private switchTimeAnimation(stage: '01' | '02' | '03'): void {
this.currentStage = stage;
switch (stage) {
case '01':
console.log("🕒 闹钟:01");
this.clockSkeleton.setAnimation(0, "02_YunSu", true);
break;
case '02':
console.log("🕒 闹钟:02");
this.clockSkeleton.setAnimation(0, "03_JiaSu", true);
break;
case '03':
console.log("🕒 闹钟:03");
this.clockSkeleton.setAnimation(0, "01_DaiJi", true);
break;
}
}
private PlateMove(deltaTime: number){
// 计算当前移动方向和速度
//const moveDirection = this.getCurrentMoveDirection();
const moveSpeed = this.getCurrentMoveSpeed();
// 移动人物
if (this.sliderX > this.deadZone){
const currentPos = this.Plate.getPosition();
this.Plate.setPosition(currentPos.x + moveSpeed * deltaTime, currentPos.y, currentPos.z);
}
else if (this.sliderX < -this.deadZone){
const currentPos = this.Plate.getPosition();
this.Plate.setPosition(currentPos.x - moveSpeed * deltaTime, currentPos.y, currentPos.z);
}
else{
return;
}
// this.updateAnimation();
const PlateX = this.Plate.getPosition().x;
console.log(PlateX);
if(PlateX < -650){
this.Plate.setPosition(-650, -269, 0);
}
else if(PlateX > 650){
this.Plate.setPosition(650, -269, 0);
}
}
private getCurrentMoveSpeed(): number {
// 应用死区
if (Math.abs(this.sliderX) < this.deadZone) {
return 0; // 在死区内,不移动
}
// 太快了const speed: number = Math.pow(this.sliderX, 2); // 速度随滑杆位置变化而变化
const speed: number = 5 * Math.abs(this.sliderX); // 速度随滑杆位置变化而变化
return speed;
}
generate() {
this.machineAudioSource.clip = this.machineClip;
this.machineAudioSource.play();
this.machineSkeleton.setAnimation(0, "fashe_01", false);
let RubikCube = instantiate(this.RubikCubePrefab);
// 直接添加到魔方容器
RubikCube.setParent(this.RubikCubeParent);
// 记录生成时的世界坐标
const machineWorldPos = this.RubikCubeParent.getWorldPosition();
const initialX = machineWorldPos.x; // 记录初始 x 坐标
const initialZ = machineWorldPos.z; // 记录初始 z 坐标
const initialY = machineWorldPos.y; // 记录初始 y 坐标
// 设置初始位置
RubikCube.setWorldPosition(initialX, initialY, initialZ);
// 🎨 设置随机皮肤
const skeleton = RubikCube.getComponent(sp.Skeleton);
const randomSkin = this.getRandomSkin();
skeleton.setSkin(randomSkin);
const RubikCubeCollider = RubikCube.getComponent(Collider2D);
RubikCubeCollider.on(Contact2DType.BEGIN_CONTACT, this.beginContact, this);
// 开始垂直下落动画
tween(RubikCube)
.to(5, {
// 使用回调函数持续更新世界坐标,保持 x 和 z 不变,只改变 y
worldPosition: new Vec3(initialX, initialY - 1000, initialZ)
})
.call(() => {
// 下落完成后清理
RubikCubeCollider.off(Contact2DType.BEGIN_CONTACT, this.beginContact, this);
})
.start();
}
// 获取随机皮肤
private getRandomSkin(): string {
const randomIndex = Math.floor(Math.random() * this.rubikCubeSkins.length);
return this.rubikCubeSkins[randomIndex];
}
private beginContact(selfCollider: Collider2D, otherCollider: Collider2D, contact: any) {
selfCollider.off(Contact2DType.BEGIN_CONTACT, this.beginContact, this);
const rubicube = selfCollider.node; // 获取碰撞的魔方节点
// 停止该节点的所有 tween 动画
Tween.stopAllByTarget(rubicube);
if (otherCollider.tag === 1){
console.log("🎉 碰撞成功");
this.rubikAudioSource.clip = this.rubikRightClip;
this.rubikAudioSource.play();
// 使用 scheduleOnce 延迟一帧执行,确保物理系统已经停止
this.scheduleOnce(() => {
// 设置父节点
rubicube.setParent(this.Plate);
// 设置位置
rubicube.setPosition(0, 72 + 56 * (this.colllidercount + 1), 0);
this.colllidercount++;
// 添加到数组中
this.stackedCubes.push(rubicube);
// 重新启用刚体和碰撞器
const rigidBody = rubicube.getComponent(RigidBody2D);
rigidBody.enabled = true;
rigidBody.type = ERigidBody2DType.Static; // 设置为静态刚体
const collider = rubicube.getComponent(Collider2D);
collider.enabled = true;
collider.tag = 1; // 确保标签正确
if(this.colllidercount == 5){
this.colllidercount = 0;
this.starNum++;
// 执行动画
this.eliminateFiveCubes();
// 动画完毕亮出星星
this.StarShow();
}
if(this.colllidercount){
this.x1.active = true;
this.x1.setPosition(0, 72 + 56 * (this.colllidercount + 1) + 50, 0);
}
else{
this.x1.active = false;
}
}, 0);
}
else{
this.rubikAudioSource.clip = this.rubikWrongClip;
this.rubikAudioSource.play();
const rigidBody = rubicube.getComponent(RigidBody2D);
rigidBody.enabled = false;
const collider = rubicube.getComponent(Collider2D);
collider.enabled = false;
// 先记录当前的世界坐标
const currentWorldPos = rubicube.getWorldPosition();
// 设置父节点
rubicube.setParent(this.Ground);
// 恢复到原来的世界坐标位置,避免因为父节点改变导致位置偏移
rubicube.setWorldPosition(currentWorldPos);
// 获取 Spine 组件并播放动画
const spine = rubicube.getComponent(sp.Skeleton);
spine.setAnimation(0, "xiaoshi_01", false);
}
}
machineMove(deltaTime: number){
const currentPos = this.machine.getPosition();
this.machine.setPosition(currentPos.x + this.speed * deltaTime, currentPos.y, currentPos.z);
if(this.machine.getPosition().x > 698){
this.speed = -this.speed;
}
else if(this.machine.getPosition().x < -698){
this.speed = -this.speed;
}
else
return;
}
StarShow(){
// 显示星星
if(this.starNum == 1){
// 显示一颗星星
this.starAnimation(this.starNum);
}
else if(this.starNum == 2){
this.starAnimation(this.starNum);
// 显示两颗星星
// 延迟 1 秒执行
this.scheduleOnce(() => {
console.log("1秒后执行");
this.gameOver();
}, 1);
}
else {
return;
}
}
// 消除5个魔方的方法
private eliminateFiveCubes() {
console.log("消除5个魔方!");
this.disappearAudioSource.clip = this.disappearClip;
this.disappearAudioSource.play();
// 取出最底下的5个魔方
const cubesToEliminate = this.stackedCubes.splice(0, 5);
// 反转数组,让第5个在前面
cubesToEliminate.reverse();
// 播放消失动画并销毁
cubesToEliminate.forEach((cube, index) => {
// 可以添加一些延迟让消失效果更好看
this.scheduleOnce(() => {
cube.destroy();
}, 0); // 每个魔方延迟0.1秒消失
});
}
starAnimation(num: number){
this.starAppearAudioSource.clip = this.starAppearClip;
this.starAppearAudioSource.play();
const currentPos = this.Plate.getWorldPosition();
this.star.setWorldPosition(currentPos.x, currentPos.y + 200, currentPos.z);
this.star.active = true;
this.satrSkeleton.setAnimation(0, "01_ChuXian", false);
this.satrSkeleton.setCompleteListener((trackEntry) => {
// 修改这里:判断正确的动画名称
if (trackEntry.animation.name === "01_ChuXian") {
console.log("星星出现动画播放完成");
this.starMove(num);
}
});
}
starMove(num: number){
this.satrSkeleton.setAnimation(0, "02_XunHuan", false);
const star1Pos = this.star_1.getWorldPosition();
const star2Pos = this.star_2.getWorldPosition();
if(num == 1){
tween(this.star)
.to(0.3, {
worldPosition: star1Pos,
scale: new Vec3(0.35, 0.35, 1)
})
.call(() => {
this.star.active = false;
this.star_1.active = true;
this.star.setScale(1, 1, 1);
})
.start();
}
else if(num == 2){
tween(this.star)
.to(0.3, {
worldPosition: star2Pos,
scale: new Vec3(0.35, 0.35, 1)
})
.call(() => {
this.star.active = false;
this.star_2.active = true;
this.star_1.setScale(1, 1, 1);
})
.start();
}
else{
return;
}
}
gameOver() {
this.audioSource.clip = this.settlementClip;
this.audioSource.play();
this.finishBoard.active = true;
this.finishSkeleton.setAnimation(0, "Chu_DanCiKa", false);
this.stopGame();
}
stopGame() {
console.log("停止游戏");
// 停止游戏逻辑
this.isRunning = false;
this.startGame = false;
// 停止所有定时器
this.unschedule(this.generate);
// 暂停所有音频(保持音频源可用)
if (this.audioSource && this.audioSource.playing) {
this.audioSource.pause();
}
if (this.machineAudioSource && this.machineAudioSource.playing) {
this.machineAudioSource.pause();
}
if (this.rubikAudioSource && this.rubikAudioSource.playing) {
this.rubikAudioSource.pause();
}
if (this.disappearAudioSource && this.disappearAudioSource.playing) {
this.disappearAudioSource.pause();
}
if (this.starAppearAudioSource && this.starAppearAudioSource.playing) {
this.starAppearAudioSource.pause();
}
// 清理物理组件和动画
this.cleanupGameObjects();
// 停止输入监听
this.stopInputListening();
// 重置状态
this.resetGameState();
}
private cleanupGameObjects() {
// 停止所有魔方的动画和物理
const children = this.RubikCubeParent.children.slice();
children.forEach(child => {
Tween.stopAllByTarget(child);
const rigidBody = child.getComponent(RigidBody2D);
const collider = child.getComponent(Collider2D);
if (collider) {
collider.off(Contact2DType.BEGIN_CONTACT, this.beginContact, this);
collider.enabled = false;
}
if (rigidBody) {
rigidBody.enabled = false;
}
});
// 清理盘子上的魔方
this.stackedCubes.forEach(cube => {
Tween.stopAllByTarget(cube);
const rigidBody = cube.getComponent(RigidBody2D);
const collider = cube.getComponent(Collider2D);
if (rigidBody) rigidBody.enabled = false;
if (collider) collider.enabled = false;
});
// 停止机器和星星的动画
Tween.stopAllByTarget(this.machine);
Tween.stopAllByTarget(this.star);
// 停止倒计时动画
if (this.countdownAnimation && this.countdownAnimation.isValid) {
this.countdownAnimation.stop();
}
}
private stopInputListening() {
input.off(Input.EventType.TOUCH_START, this.onGlobalTouchStart, this);
input.off(Input.EventType.TOUCH_MOVE, this.onGlobalTouchMove, this);
input.off(Input.EventType.TOUCH_END, this.onGlobalTouchEnd, this);
}
private resetGameState() {
this.isPressed = false;
this.isDragging = false;
this.isMoving = false;
this.sliderX = 0;
this.colllidercount = 0;
this.starNum = 0;
this.currentStage = "03";
this.stackedCubes = [];
// 重置滑杆位置
this.Slider.setPosition(0, 0, 0);
}
update(deltaTime: number) {
if(this.startGame){
this.clock(deltaTime);
this.PlateMove(deltaTime);
this.machineMove(deltaTime);
}
}
}