Files
climb/assets/scripts/PlayerController.ts
2025-10-21 18:11:19 +08:00

1713 lines
53 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { _decorator, Component, Node, Vec3, input, Input, EventTouch, Camera, view, tween, Animation, Collider2D, BoxCollider2D, Contact2DType, Label, Color, Canvas, UITransform, AudioSource, director, PhysicsSystem2D, EPhysics2DDrawFlags, sys } from 'cc';
import { TiledMapPathfinder } from './TiledMapPathfinder';
const { ccclass, property } = _decorator;
// PhysicsSystem2D.instance.debugDrawFlags =
// EPhysics2DDrawFlags.Aabb |
// EPhysics2DDrawFlags.Shape;
enum PlayerDirection {
LeftUp = 'LeftUp',
LeftDown = 'LeftDown',
RightUp = 'RightUp',
RightDown = 'RightDown',
}
@ccclass('PlayerController')
export class PlayerController extends Component {
@property(Canvas)
canvas: Canvas | null = null;
@property(Node)
player: Node | null = null; // 玩家节点
@property(Node)
bonus: Node | null = null;
@property(Node)
failedDialog: Node | null = null;
@property(Camera)
camera: Camera | null = null; // 主摄像机
@property(TiledMapPathfinder)
pathfinder: TiledMapPathfinder | null = null; // 寻路组件
@property(Node)
attackAudio: Node | null = null; // 攻击音效节点
@property({ range: [1, 300] })
moveSpeed: number = 300; // 移动速度(像素/秒)
private isMoving: boolean = false;
private isAttacking: boolean = false;
private currentPath: Vec3[] = [];
private currentPathIndex: number = 0;
private originalPosition: Vec3 = new Vec3();
private currentAnimation: string | null = null; // 当前播放的动画剪辑名称
private lastTargetPosition: Vec3 = new Vec3(); // 上一个目标位置,用于方向判断
private isUpgraded: boolean = false; // 玩家是否已升级
private isGameOver: boolean = false; // 游戏是否结束(玩家死亡)
private isWin: boolean = false; // 游戏是否胜利(到达终点)
private currentDirection: PlayerDirection = PlayerDirection.RightDown; // 当前玩家朝向:四象限(左上/左下/右上/右下)
// 平滑移动相关变量
private moveTween: any = null; // 当前移动的tween对象
private lastPosition: Vec3 = new Vec3(); // 上一帧位置
private hasWinTimes = 0
private readonly attackAlignGap = 20; // 玩家与怪物对阵时的额外左右间距,单位:像素
private readonly attackVerticalOffset = 25; // 玩家攻击时相对于怪物的垂直偏移量(玩家更高),单位:像素
// 道具列表
private props: Node[] = [];
// 行走方向相关参数
private readonly directionLookAheadSteps = 1; // 更新方向时向前查看的路径节点数
private readonly minDirectionUpdateDistance = 1; // 小于该距离时不刷新方向,避免抖动
private guideNode: Node | null = null;
private activePopup: Node | null = null;
private activePopupName: string | null = null;
private pendingPopupHide: (() => void) | null = null;
onLoad() {
this.guideNode = this.canvas.node.getChildByName('Guide');
// 注册触摸事件
input.on(Input.EventType.TOUCH_START, this.onTouchStart, this);
let collider = this.player.getComponent(Collider2D);
if (collider) {
// 监听碰撞事件
collider.on(Contact2DType.BEGIN_CONTACT, this.onBeginContact, this);
}
this.initProps();
// this.showBonusPopup()
// this.scheduleOnce(() => {
// this.showBonusPopup()
// }, 5);
}
onDestroy() {
// 移除触摸事件
input.off(Input.EventType.TOUCH_START, this.onTouchStart, this);
this.clearPopupHideSchedule();
}
start() {
if (this.player) {
this.originalPosition.set(this.player.position);
// 初始化时设置站立动画和正确的方向
this.switchAnimation('stand');
this.updatePlayerScale();
}
}
initProps() {
if (!this.canvas) {
console.warn('Canvas未设置无法初始化道具');
return;
}
// 查找Canvas下的Props节点
const propsNode = this.canvas.node.getChildByName('Props');
if (!propsNode) {
console.warn('未找到Props节点请确保Canvas下有名为"Props"的节点');
return;
}
// 清空现有的道具列表
this.props.length = 0;
// 获取Props节点下的所有子节点
for (let i = 0; i < propsNode.children.length; i++) {
const child = propsNode.children[i];
this.props.push(child);
console.log(`添加道具: ${child.name}`);
// 为每个道具添加悬浮动画
this.addFloatingAnimation(child);
}
console.log(`初始化道具完成,共找到 ${this.props.length} 个道具`);
}
/**
* 为道具添加悬浮动画
*/
private addFloatingAnimation(propNode: Node) {
if (!propNode) return;
// 保存原始位置
const originalY = propNode.position.y;
const floatHeight = 10; // 悬浮高度(像素)
const floatDuration = 2; // 悬浮周期(秒)
// 创建上下浮动的动画
tween(propNode)
.to(floatDuration / 2, { position: new Vec3(propNode.position.x, originalY + floatHeight, propNode.position.z) }, {
easing: 'sineInOut'
})
.to(floatDuration / 2, { position: new Vec3(propNode.position.x, originalY, propNode.position.z) }, {
easing: 'sineInOut'
})
.union() // 将动画串联起来
.repeatForever() // 无限重复
.start();
console.log(`为道具 ${propNode.name} 添加悬浮动画`);
}
private onTouchStart(event: EventTouch) {
if (this.activePopup) {
return;
}
if (!this.player || !this.camera || !this.pathfinder || this.isAttacking || this.isGameOver || this.isWin) return;
this.guideNode.active = false;
// 获取触摸点的UI坐标
const touchLocation = event.getUILocation();
// 将UI坐标转换为世界坐标
const worldPos = this.screenToWorldPoint(touchLocation);
console.log(`触摸UI坐标: (${touchLocation.x}, ${touchLocation.y})`);
console.log(`转换后世界坐标: (${worldPos.x.toFixed(2)}, ${worldPos.y.toFixed(2)})`);
this.moveToPositionWithPathfinding(worldPos);
}
private screenToWorldPoint(screenPos: { x: number, y: number }): Vec3 {
if (!this.camera) {
console.error('Camera未设置无法进行坐标转换');
return new Vec3(screenPos.x, screenPos.y, 0);
}
// 获取可见区域大小
const visibleSize = view.getVisibleSize();
// 计算屏幕中心点
const centerX = visibleSize.width * 0.5;
const centerY = visibleSize.height * 0.5;
// 将屏幕坐标转换为以屏幕中心为原点的坐标
const normalizedX = screenPos.x - centerX;
const normalizedY = screenPos.y - centerY;
// 获取相机的正交高度
const orthoHeight = this.camera.orthoHeight;
// 计算屏幕坐标到世界坐标的缩放比例
// 屏幕高度的一半对应 orthoHeight 的世界单位
const scaleY = orthoHeight / (visibleSize.height * 0.5);
const scaleX = scaleY; // 保持宽高比一致
// 将屏幕坐标转换为世界坐标(相对于相机中心)
const worldOffsetX = normalizedX * scaleX;
const worldOffsetY = normalizedY * scaleY;
// 考虑相机的位置偏移
const cameraPos = this.camera.node.position;
// 计算最终的世界坐标
const worldX = worldOffsetX + cameraPos.x;
const worldY = worldOffsetY + cameraPos.y;
return new Vec3(worldX, worldY, 0);
}
private moveToPositionWithPathfinding(worldPos: Vec3) {
if (!this.player || !this.pathfinder) return;
// 停止当前移动
// this.stopMovement();
// 限制目标位置在地图边界内
const clampedPos = this.clampPlayerPosition(worldPos);
// 检查目标位置是否可行走
if (!this.pathfinder.isWorldPositionWalkable(clampedPos)) {
console.log('目标位置不可行走,寻找最近的可行走位置');
const closestWalkable = this.pathfinder.getClosestWalkablePosition(clampedPos);
if (!closestWalkable) {
console.warn('找不到可行走的位置');
return;
}
clampedPos.set(closestWalkable);
}
// 检查玩家当前位置是否可行走
let startPos = this.player.position;
if (!this.pathfinder.isWorldPositionWalkable(startPos)) {
console.log('玩家当前位置不可行走,寻找最近的可行走位置作为起点');
const closestPlayerWalkable = this.pathfinder.getClosestWalkablePosition(startPos);
if (!closestPlayerWalkable) {
console.warn('找不到玩家附近的可行走位置');
return;
}
startPos = closestPlayerWalkable;
console.log(`将玩家移动到最近的可行走位置: (${startPos.x.toFixed(2)}, ${startPos.y.toFixed(2)})`);
// 直接将玩家传送到最近的可行走位置
this.player.setPosition(startPos);
}
// 使用寻路算法计算路径
this.currentPath = this.pathfinder.findPath(startPos, clampedPos);
if (this.currentPath.length === 0) {
console.warn('无法找到路径');
return;
}
console.log(`找到路径,包含${this.currentPath.length}个点`);
// 开始沿路径移动
this.currentPathIndex = 0;
this.isMoving = true;
// 使用平滑路径移动
this.startSmoothPathMovement();
}
// 限制玩家位置在地图边界内
private clampPlayerPosition(position: Vec3): Vec3 {
return this.clampPositionWithinMap(position);
}
private clampPositionWithinMap(position: Vec3): Vec3 {
// 限制位置
const clampedPosition = position.clone();
return clampedPosition;
}
/**
* 切换动画,避免不必要的切换
* 根据 this.currentDirection 来决定 player 的 scale 是否要取反,不再需要通过动画名称进行区分
*/
private switchAnimation(actionName: string) {
if (!this.player) {
console.warn('Player节点未设置无法切换动画');
return;
}
const animNode = this.player.getChildByName('Anim');
if (!animNode) {
console.warn('未找到Anim子节点无法切换动画');
return;
}
const animation = animNode.getComponent(Animation);
if (!animation) {
console.warn('未找到Animation组件无法播放动画');
return;
}
const candidates = this.getAnimationNameCandidates(actionName);
let finalAnimationName: string | null = null;
for (const candidate of candidates) {
if (!candidate) {
continue;
}
const state = animation.getState(candidate);
if (state) {
finalAnimationName = candidate;
break;
}
}
if (!finalAnimationName) {
const fallback = this.isUpgraded ? 'stand_2' : 'stand5';
if (!this.currentAnimation || this.currentAnimation !== fallback) {
console.warn(`未找到可用的动画 ${actionName},使用兜底动画 ${fallback}`);
}
const fallbackState = animation.getState(fallback);
if (!fallbackState) {
console.error('无法找到兜底动画,动画切换失败');
return;
}
finalAnimationName = fallback;
}
if (this.currentAnimation === finalAnimationName) {
this.updatePlayerScale();
return;
}
animation.play(finalAnimationName);
this.currentAnimation = finalAnimationName;
console.log(`切换动画: ${finalAnimationName}`);
this.updatePlayerScale();
}
/**
* 根据当前方向更新玩家Anim子节点的scale
* 左向LeftUp/LeftDown时需要翻转Anim节点scale.x 取反)
*/
private updatePlayerScale() {
if (!this.player) return;
// 获取Anim子节点
const animNode = this.player.getChildByName('Anim');
if (!animNode) {
console.warn('未找到Anim子节点无法更新方向');
return;
}
const currentScale = animNode.scale.clone();
const faceLeft = this.currentDirection === PlayerDirection.LeftUp || this.currentDirection === PlayerDirection.LeftDown;
const desiredScaleX = faceLeft ? -Math.abs(currentScale.x) : Math.abs(currentScale.x);
if (currentScale.x !== desiredScaleX) {
animNode.setScale(desiredScaleX, currentScale.y, currentScale.z);
}
}
private normalizeAnimationAction(animationName: string): string {
if (!animationName) {
return animationName;
}
if (animationName.startsWith('walk')) {
return 'walk';
}
if (animationName.startsWith('attack')) {
return 'attack';
}
if (animationName.startsWith('stand')) {
return 'stand';
}
if (animationName.startsWith('die')) {
return 'die';
}
return animationName;
}
private resolveDirectionFromDelta(
deltaX: number,
deltaY: number,
overrides?: { horizontal?: 'Left' | 'Right', vertical?: 'Up' | 'Down' }
): PlayerDirection {
const horizontalThreshold = 0.1;
const verticalThreshold = 0.1;
let horizontal: 'Left' | 'Right' = overrides?.horizontal ?? (this.isFacingLeft() ? 'Left' : 'Right');
if (!overrides?.horizontal && Math.abs(deltaX) > horizontalThreshold) {
horizontal = deltaX < 0 ? 'Left' : 'Right';
}
let vertical: 'Up' | 'Down' = overrides?.vertical ?? (this.isFacingUp() ? 'Up' : 'Down');
if (!overrides?.vertical && Math.abs(deltaY) > verticalThreshold) {
vertical = deltaY > 0 ? 'Up' : 'Down';
}
return this.composeDirection(horizontal, vertical);
}
private composeDirection(horizontal: 'Left' | 'Right', vertical: 'Up' | 'Down'): PlayerDirection {
if (horizontal === 'Left') {
return vertical === 'Up' ? PlayerDirection.LeftUp : PlayerDirection.LeftDown;
}
return vertical === 'Up' ? PlayerDirection.RightUp : PlayerDirection.RightDown;
}
private isFacingLeft(): boolean {
return this.currentDirection === PlayerDirection.LeftUp || this.currentDirection === PlayerDirection.LeftDown;
}
private isFacingUp(): boolean {
return this.currentDirection === PlayerDirection.LeftUp || this.currentDirection === PlayerDirection.RightUp;
}
private getVerticalAnimationSuffix(): '3' | '5' {
return this.isFacingUp() ? '3' : '5';
}
private getAnimationNameCandidates(requestedName: string): string[] {
const action = this.normalizeAnimationAction(requestedName);
const suffix = this.getVerticalAnimationSuffix();
const oppositeSuffix = suffix === '3' ? '5' : '3';
const candidates: string[] = [];
const pushUnique = (name?: string) => {
if (!name) {
return;
}
if (candidates.indexOf(name) === -1) {
candidates.push(name);
}
};
if (action === 'walk' || action === 'attack') {
if (this.isUpgraded) {
pushUnique(`${action}${suffix}_2`);
pushUnique(`${action}_2`);
}
pushUnique(`${action}${suffix}`);
pushUnique(action);
pushUnique(`${action}${oppositeSuffix}`);
} else if (action === 'stand') {
if (this.isUpgraded) {
pushUnique(`stand${suffix}_2`);
pushUnique('stand_2');
}
pushUnique(`stand${suffix}`);
pushUnique(`stand${oppositeSuffix}`);
pushUnique('stand');
} else {
if (this.isUpgraded) {
pushUnique(`${action}_2`);
}
pushUnique(action);
}
return candidates;
}
/**
* 开始平滑路径移动
*/
private startSmoothPathMovement() {
if (!this.player || this.currentPath.length === 0) {
this.isMoving = false;
this.switchAnimation('stand');
return;
}
// 停止当前的移动tween
if (this.moveTween) {
this.moveTween.stop();
this.moveTween = null;
}
// 计算总路径长度
let totalDistance = 0;
for (let i = 0; i < this.currentPath.length - 1; i++) {
totalDistance += Vec3.distance(this.currentPath[i], this.currentPath[i + 1]);
}
// 计算总移动时间
const totalTime = totalDistance / this.moveSpeed;
console.log(`开始平滑路径移动,总距离: ${totalDistance.toFixed(2)}, 总时间: ${totalTime.toFixed(2)}`);
// 初始化时先设置第一个路径点的方向
if (this.currentPath.length > 1) {
const startPos = this.player.position.clone();
const nextPos = this.currentPath[1]; // 第二个路径点
this.updateMovementDirectionOnce(startPos, nextPos);
} else {
// 如果只有一个路径点,直接朝向它
const startPos = this.player.position.clone();
const finalTargetPos = this.currentPath[0];
this.updateMovementDirectionOnce(startPos, finalTargetPos);
}
// 创建连续的路径移动
this.moveTween = tween(this.player)
.to(totalTime, { position: this.currentPath[this.currentPath.length - 1] }, {
easing: 'linear',
onUpdate: (target: Node, ratio: number) => {
// 根据进度计算当前位置
const currentPos = this.getPositionOnPath(ratio);
if (currentPos) {
target.position = currentPos;
// 在移动过程中动态更新方向
this.updateDirectionDuringMovement(currentPos, ratio);
}
},
onComplete: () => {
this.isMoving = false;
this.switchAnimation('stand');
console.log('平滑路径移动完成');
}
})
.start();
}
/**
* 根据路径进度获取当前位置
*/
private getPositionOnPath(ratio: number): Vec3 | null {
if (!this.player || this.currentPath.length === 0) {
return null;
}
// 计算总路径长度
const segmentLengths: number[] = [];
let totalLength = 0;
for (let i = 0; i < this.currentPath.length - 1; i++) {
const length = Vec3.distance(this.currentPath[i], this.currentPath[i + 1]);
segmentLengths.push(length);
totalLength += length;
}
// 计算目标距离
const targetDistance = totalLength * ratio;
// 找到对应的路径段
let currentDistance = 0;
for (let i = 0; i < segmentLengths.length; i++) {
if (currentDistance + segmentLengths[i] >= targetDistance) {
// 在当前段内
const segmentRatio = (targetDistance - currentDistance) / segmentLengths[i];
const startPos = this.currentPath[i];
const endPos = this.currentPath[i + 1];
// 线性插值计算当前位置
return new Vec3(
startPos.x + (endPos.x - startPos.x) * segmentRatio,
startPos.y + (endPos.y - startPos.y) * segmentRatio,
startPos.z + (endPos.z - startPos.z) * segmentRatio
);
}
currentDistance += segmentLengths[i];
}
// 如果超出范围,返回终点
return this.currentPath[this.currentPath.length - 1];
}
/**
* 算法:优先判断水平方向(左/右),只有当水平方向不明显时才判断垂直方向
*/
private updateMovementDirectionOnce(startPos: Vec3, targetPos: Vec3) {
if (!this.player) {
return;
}
// 计算移动方向(基于起始位置和目标位置)
const deltaX = targetPos.x - startPos.x;
const deltaY = targetPos.y - startPos.y;
// 如果移动距离很小,不更新动画
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 1) {
return;
}
this.currentDirection = this.resolveDirectionFromDelta(deltaX, deltaY);
// 切换到对应的动画(只传递基础动画名称)
this.switchAnimation('walk');
}
/**
* 在移动过程中动态更新方向
* 根据当前位置在路径中的位置,计算朝向下一个路径点的方向
*/
private updateDirectionDuringMovement(currentPos: Vec3, ratio: number) {
if (!this.player || this.currentPath.length <= 1) {
return;
}
// 计算当前在路径中的大致位置
const pathLength = this.currentPath.length;
const approxIndex = Math.floor(ratio * (pathLength - 1));
if (approxIndex >= pathLength - 1) {
return;
}
const currentPathIndex = Math.max(0, approxIndex);
const nextIndex = currentPathIndex + 1;
const maxIndex = Math.min(pathLength - 1, nextIndex + this.directionLookAheadSteps);
let weightedDirX = 0;
let weightedDirY = 0;
let totalWeight = 0;
let farthestDistance = 0;
// 聚合未来若干路径节点的方向信息,减缓转向突变
for (let i = nextIndex; i <= maxIndex; i++) {
const samplePoint = this.currentPath[i];
const deltaX = samplePoint.x - currentPos.x;
const deltaY = samplePoint.y - currentPos.y;
const distanceToPoint = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
farthestDistance = Math.max(farthestDistance, distanceToPoint);
if (distanceToPoint < 0.0001) {
continue;
}
const weight = i - currentPathIndex;
weightedDirX += (deltaX / distanceToPoint) * weight;
weightedDirY += (deltaY / distanceToPoint) * weight;
totalWeight += weight;
}
if (totalWeight === 0 || farthestDistance < this.minDirectionUpdateDistance) {
return;
}
const averagedDirX = weightedDirX / totalWeight;
const averagedDirY = weightedDirY / totalWeight;
const averagedMagnitude = Math.sqrt(averagedDirX * averagedDirX + averagedDirY * averagedDirY);
if (averagedMagnitude < 0.1) {
return;
}
// 计算新的方向
const newDirection = this.resolveDirectionFromDelta(averagedDirX, averagedDirY);
// 只有当方向发生显著变化时才更新
if (newDirection !== this.currentDirection) {
const previousDirection = this.currentDirection;
this.currentDirection = newDirection;
// 更新玩家缩放(翻转)
this.updatePlayerScale();
// 根据朝向改变更新移动动画
this.updateMovementAnimation(previousDirection, newDirection);
console.log(`移动过程中更新方向: ${previousDirection} -> ${newDirection}`);
}
}
/**
* 根据朝向改变更新移动动画
* @param previousDirection 之前的方向
* @param newDirection 新的方向
*/
private updateMovementAnimation(previousDirection: PlayerDirection, newDirection: PlayerDirection) {
if (!this.isMoving) {
return; // 只有在移动状态下才更新动画
}
// 检查是否需要切换动画(垂直方向改变时)
const previousVertical = this.isDirectionUp(previousDirection);
const newVertical = this.isDirectionUp(newDirection);
if (previousVertical !== newVertical) {
// 垂直方向改变,需要切换动画
this.switchAnimation('walk');
console.log(`垂直方向改变,切换移动动画: ${previousVertical ? '上' : '下'} -> ${newVertical ? '上' : '下'}`);
}
}
/**
* 判断方向是否向上
* @param direction 玩家方向
* @returns 是否向上
*/
private isDirectionUp(direction: PlayerDirection): boolean {
return direction === PlayerDirection.LeftUp || direction === PlayerDirection.RightUp;
}
/**
* 停止当前移动
*/
private stopMovement() {
// 停止当前的移动tween
if (this.moveTween) {
this.moveTween.stop();
this.moveTween = null;
}
if (this.player) {
tween(this.player).stop();
}
this.isMoving = false;
this.currentPath = [];
this.currentPathIndex = 0;
// 停止移动时播放站立动画
this.switchAnimation('stand');
}
/**
* 统一处理碰撞回调的启停,确保在逻辑失败时能恢复碰撞器状态
*/
private async processColliderCollision(otherCollider: Collider2D, handler: () => Promise<boolean | void> | boolean | void) {
if (!otherCollider || !otherCollider.node || !otherCollider.node.isValid) {
return;
}
if (!otherCollider.enabled) {
return;
}
otherCollider.enabled = false;
let shouldKeepDisabled = false;
try {
const keepDisabled = await Promise.resolve(handler());
shouldKeepDisabled = keepDisabled === true;
} finally {
if (!shouldKeepDisabled && otherCollider && otherCollider.isValid && otherCollider.node && otherCollider.node.isValid) {
otherCollider.enabled = true;
}
}
}
update(deltaTime: number) {
// 更新逻辑现在主要由缓动系统处理
// 这里可以添加其他需要每帧更新的逻辑
}
onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D) {
console.log('碰撞检测', selfCollider.node.name, otherCollider.node.name);
if (!otherCollider || !otherCollider.node || !otherCollider.node.isValid) {
return;
}
if (this.isAttacking || this.isGameOver || this.isWin) {
return;
}
const nodeName = otherCollider.node.name || '';
const isMonster = nodeName.startsWith('guai_');
const isBox = nodeName.startsWith('box_');
if (!isMonster && !isBox) {
return;
}
if (isMonster) {
void this.processColliderCollision(otherCollider, () => this.handleAttack(selfCollider, otherCollider));
return;
}
void this.processColliderCollision(otherCollider, () => this.handleBoxCollision(selfCollider, otherCollider));
}
/**
* 将玩家移动到怪物正对位置,确保攻击前双方站位合理
*/
private alignPlayerForAttack(selfCollider: Collider2D, monsterCollider: Collider2D): Promise<void> {
return new Promise((resolve) => {
if (!this.player || !selfCollider || !monsterCollider || !monsterCollider.node || !monsterCollider.node.isValid) {
resolve();
return;
}
const playerNode = this.player;
const monsterNode = monsterCollider.node;
const playerWorldPos = playerNode.worldPosition.clone();
const monsterWorldPos = monsterNode.worldPosition.clone();
const playerBox = selfCollider instanceof BoxCollider2D ? selfCollider : null;
const monsterBox = monsterCollider instanceof BoxCollider2D ? monsterCollider : null;
const playerScale = playerNode.worldScale;
const monsterScale = monsterNode.worldScale;
const playerHalfWidth = playerBox ? (playerBox.size.x * Math.abs(playerScale.x)) / 2 : 40;
const monsterHalfWidth = monsterBox ? (monsterBox.size.x * Math.abs(monsterScale.x)) / 2 : 60;
const playerOffsetX = playerBox ? playerBox.offset.x * playerScale.x : 0;
const playerOffsetY = playerBox ? playerBox.offset.y * playerScale.y : 0;
const monsterOffsetX = monsterBox ? monsterBox.offset.x * monsterScale.x : 0;
const monsterOffsetY = monsterBox ? monsterBox.offset.y * monsterScale.y : 0;
const playerCenterX = playerWorldPos.x + playerOffsetX;
const monsterCenterX = monsterWorldPos.x + monsterOffsetX;
const standOnLeft = playerCenterX <= monsterCenterX;
const totalHalfWidth = playerHalfWidth + monsterHalfWidth + this.attackAlignGap;
const directionMultiplier = standOnLeft ? -1 : 1;
const targetWorldPos = new Vec3(
monsterWorldPos.x + monsterOffsetX + directionMultiplier * totalHalfWidth - playerOffsetX,
monsterWorldPos.y + monsterOffsetY - playerOffsetY + (this.isUpgraded ? 0 : this.attackVerticalOffset),
playerWorldPos.z
);
const targetLocalPos = this.convertWorldToParentSpace(playerNode, targetWorldPos);
const currentLocalPos = playerNode.position.clone();
const distance = Vec3.distance(currentLocalPos, targetLocalPos);
const targetPlayerCenterY = targetWorldPos.y + playerOffsetY;
const monsterCenterY = monsterWorldPos.y + monsterOffsetY;
const verticalDeltaToMonster = monsterCenterY - targetPlayerCenterY;
const verticalOverride = Math.abs(verticalDeltaToMonster) <= 0.5 ? undefined : (verticalDeltaToMonster > 0 ? 'Up' : 'Down');
const desiredDirection = this.resolveDirectionFromDelta(
targetWorldPos.x - playerWorldPos.x,
targetWorldPos.y - playerWorldPos.y,
{
horizontal: standOnLeft ? 'Right' : 'Left',
vertical: verticalOverride,
}
);
this.currentDirection = desiredDirection;
if (distance < 1) {
playerNode.setPosition(targetLocalPos);
this.updatePlayerScale();
resolve();
return;
}
// 只传递基础动画名称,方向由 currentDirection 控制
this.switchAnimation('walk');
const baseDuration = this.moveSpeed > 0 ? distance / this.moveSpeed : 0.2;
const duration = Math.min(Math.max(baseDuration, 0.12), 0.45);
tween(playerNode)
.to(duration, { position: targetLocalPos }, {
easing: 'smooth',
onComplete: () => {
playerNode.setPosition(targetLocalPos);
this.currentDirection = desiredDirection;
this.updatePlayerScale();
resolve();
}
})
.start();
});
}
private convertWorldToParentSpace(node: Node, worldPos: Vec3): Vec3 {
const parent = node.parent;
if (!parent) {
return worldPos.clone();
}
const parentTransform = parent.getComponent(UITransform);
if (parentTransform) {
return parentTransform.convertToNodeSpaceAR(worldPos);
}
const fallback = worldPos.clone();
fallback.subtract(parent.worldPosition);
return fallback;
}
/**
* 处理攻击逻辑
*/
private async handleAttack(selfCollider: Collider2D, otherCollider: Collider2D) {
if (this.isAttacking) {
return;
}
if (!this.player || !otherCollider || !otherCollider.node || !otherCollider.node.isValid) {
return;
}
this.isAttacking = true;
this.stopMovement();
await this.alignPlayerForAttack(selfCollider, otherCollider);
if (!this.player || !otherCollider.node || !otherCollider.node.isValid) {
this.isAttacking = false;
return;
}
console.log('开始攻击,怪物名称:', otherCollider.node.name);
// 获取玩家和怪物的生命值
const playerHpLabel = this.player.getChildByName('hp');
const monsterHpLabel = otherCollider.node.getChildByName('hp');
if (!playerHpLabel || !monsterHpLabel) {
console.warn('未找到生命值标签玩家hp:', playerHpLabel, '怪物hp:', monsterHpLabel);
this.isAttacking = false;
return;
}
// 获取生命值数值
const playerLabel = playerHpLabel.getComponent(Label);
const monsterLabel = monsterHpLabel.getComponent(Label);
if (!playerLabel || !monsterLabel) {
console.warn('未找到Label组件');
this.isAttacking = false;
return;
}
const playerHp = parseInt(playerLabel.string) || 0;
const monsterHp = parseInt(monsterLabel.string) || 0;
console.log('玩家生命值:', playerHp, '怪物生命值:', monsterHp);
// 播放攻击音效
if (this.attackAudio) {
this.scheduleOnce(() => {
const audioSource = this.attackAudio.getComponent(AudioSource);
if (audioSource) {
audioSource.play();
console.log('播放攻击音效');
}
}, 0.5)
}
// 获取玩家动画组件
const playerAnimNode = this.player.getChildByName('Anim');
const playerAnimation = playerAnimNode ? playerAnimNode.getComponent(Animation) : null;
if (!playerAnimation) {
console.warn('未找到玩家动画组件');
this.isAttacking = false;
return;
}
// 检查是否是 guai_10如果是则使用特殊战斗逻辑
if (otherCollider.node.name === 'guai_10') {
await this.handleGuai10SpecialAttack(selfCollider, otherCollider, playerLabel, monsterLabel, playerHp, monsterHp, playerAnimation);
} else {
// 原有的普通战斗逻辑
await this.handleNormalAttack(otherCollider, playerLabel, monsterLabel, playerHp, monsterHp, playerAnimation);
}
this.isAttacking = false;
// 停止攻击音效
// if (this.attackAudio) {
// const audioSource = this.attackAudio.getComponent(AudioSource);
// if (audioSource) {
// audioSource.stop();
// console.log('停止攻击音效');
// }
// }
return true;
}
/**
* 处理普通怪物的攻击逻辑
*/
private async handleNormalAttack(otherCollider: Collider2D, playerLabel: Label, monsterLabel: Label, playerHp: number, monsterHp: number, playerAnimation: Animation) {
// 播放玩家攻击动画(只传递基础动画名称)
this.switchAnimation('attack');
// 监听玩家攻击动画结束事件
return new Promise<void>((resolve) => {
playerAnimation.once(Animation.EventType.FINISHED, async () => {
if (!this.player || !playerLabel.isValid || !monsterLabel.isValid) {
resolve();
return;
}
// 比较生命值,判断输赢
console.log('判定攻击结果玩家HP:', playerHp, '怪物HP:', monsterHp);
if (playerHp >= monsterHp) {
// 玩家获胜,直接执行后续流程,不需要播放怪物攻击动画
await this.handlePlayerWin(otherCollider, playerLabel, monsterHp);
} else {
// 玩家输了,需要播放怪物攻击动画
await this.handleMonsterAttack(otherCollider, playerLabel, monsterHp);
}
resolve();
});
});
}
/**
* 处理 guai_10 的特殊攻击逻辑:玩家攻击 → 怪物还击 → 玩家再攻击 → 最终判定
*/
private async handleGuai10SpecialAttack(selfCollider: Collider2D, otherCollider: Collider2D, playerLabel: Label, monsterLabel: Label, playerHp: number, monsterHp: number, playerAnimation: Animation) {
console.log('开始 guai_10 特殊战斗逻辑');
// 第一轮:玩家攻击
console.log('第一轮:玩家攻击');
this.switchAnimation('attack');
// 等待玩家攻击动画结束
await new Promise<void>((resolve) => {
playerAnimation.once(Animation.EventType.FINISHED, () => {
console.log('玩家第一轮攻击完成');
resolve();
});
});
// 检查节点是否仍然有效
if (!this.player || !otherCollider.node || !otherCollider.node.isValid || !playerLabel.isValid || !monsterLabel.isValid) {
return;
}
// 第二轮:怪物还击
console.log('第二轮:怪物还击');
await this.playMonsterAttackAnimation(otherCollider);
// 检查节点是否仍然有效
if (!this.player || !otherCollider.node || !otherCollider.node.isValid || !playerLabel.isValid || !monsterLabel.isValid) {
return;
}
// 确保玩家在怪物攻击后回到站立状态,然后再进行第三轮攻击
this.switchAnimation('stand');
// 添加短暂延迟,让玩家站立动画播放一下
await new Promise<void>((resolve) => {
this.scheduleOnce(() => {
resolve();
}, 0.2);
});
// 第三轮:玩家再次攻击
console.log('第三轮:玩家再次攻击');
this.switchAnimation('attack');
// 等待玩家攻击动画结束
await new Promise<void>((resolve) => {
playerAnimation.once(Animation.EventType.FINISHED, () => {
console.log('玩家第三轮攻击完成');
resolve();
});
});
// 检查节点是否仍然有效
if (!this.player || !otherCollider.node || !otherCollider.node.isValid || !playerLabel.isValid || !monsterLabel.isValid) {
return;
}
// 最终判定:比较生命值
console.log('最终判定玩家HP:', playerHp, '怪物HP:', monsterHp);
if (playerHp >= monsterHp) {
// 玩家获胜
await this.handlePlayerWin(otherCollider, playerLabel, monsterHp);
} else {
// 玩家失败
this.executePlayerDefeat(otherCollider, playerLabel);
}
}
/**
* 播放怪物攻击动画
*/
private async playMonsterAttackAnimation(otherCollider: Collider2D): Promise<void> {
const animNode = otherCollider.node.getChildByName('Anim');
const monsterAnimation = animNode ? animNode.getComponent(Animation) : null;
if (monsterAnimation) {
console.log(`尝试播放怪物攻击动画: ${otherCollider.node.name}_attack`);
// 检查动画是否存在
const attackAnimState = monsterAnimation.getState(`${otherCollider.node.name}_attack`);
if (!attackAnimState) {
console.warn(`怪物攻击动画 ${otherCollider.node.name}_attack 不存在,跳过怪物攻击动画`);
return;
}
return new Promise<void>((resolve) => {
// 添加超时机制,防止动画卡住
const timeout = setTimeout(() => {
console.log('怪物攻击动画超时,强制继续');
// 确保怪物切换回站立状态
this.switchMonsterToStand(otherCollider.node.name, monsterAnimation);
resolve();
}, 3000); // 3秒超时
monsterAnimation.play(`${otherCollider.node.name}_attack`);
console.log('开始播放怪物攻击动画');
// 监听怪物攻击动画结束
monsterAnimation.once(Animation.EventType.FINISHED, () => {
clearTimeout(timeout);
console.log('怪物攻击动画完成,切换回站立状态');
// 怪物攻击完成后切换回站立动画
this.switchMonsterToStand(otherCollider.node.name, monsterAnimation);
resolve();
});
});
} else {
console.warn('未找到怪物动画组件,跳过怪物攻击动画');
// 如果没有怪物动画组件,直接返回
return;
}
}
/**
* 将怪物切换到站立动画
*/
private switchMonsterToStand(monsterName: string, monsterAnimation: Animation) {
const standAnimName = `${monsterName}_stand`;
const standAnimState = monsterAnimation.getState(standAnimName);
if (standAnimState) {
monsterAnimation.play(standAnimName);
console.log(`怪物切换到站立动画: ${standAnimName}`);
} else {
console.warn(`怪物站立动画 ${standAnimName} 不存在`);
}
}
/**
* 处理玩家获胜的情况
*/
private async handlePlayerWin(otherCollider: Collider2D, playerLabel: Label, monsterHp: number) {
const hit = otherCollider.node.getChildByName('Hit');
if (hit) {
hit.active = true;
}
this.hasWinTimes++;
// 玩家获胜
console.log('玩家获胜!更新玩家生命值为:', parseInt(playerLabel.string) + monsterHp);
// 玩家生命值增加怪物生命值
const playerHp = parseInt(playerLabel.string) || 0;
const newPlayerHp = playerHp + monsterHp;
playerLabel.string = newPlayerHp.toString();
// 播放生命值标签的强调动画
this.playLabelEmphasisAnimation(playerLabel);
// 播放怪物死亡动画
const animNode = otherCollider.node.getChildByName('Anim');
const monsterAnimation = animNode ? animNode.getComponent(Animation) : null;
if (monsterAnimation) {
monsterAnimation.play(`${otherCollider.node.name}_die`);
}
// 如果是攻击 guai_2 并且成功,创建道具飞向 player 的动画
if (otherCollider.node.name === 'guai_2') {
this.switchAnimation('stand'); // 玩家站立
await this.createPropsFlyToPlayerAnimation();
}
// 1秒后怪物消失
this.scheduleOnce(() => {
if (!otherCollider.node || !otherCollider.node.isValid) {
return;
}
console.log('怪物已消失');
otherCollider.node.destroy();
if (this.hasWinTimes === 7) {
this.isWin = true;
this.showBonusPopup();
}
}, 1);
this.switchAnimation('stand'); // 玩家站立
}
/**
* 处理怪物攻击的情况
*/
private async handleMonsterAttack(otherCollider: Collider2D, playerLabel: Label, monsterHp: number) {
// 播放怪物攻击动画
const animNode = otherCollider.node.getChildByName('Anim');
const monsterAnimation = animNode ? animNode.getComponent(Animation) : null;
if (monsterAnimation) {
// 创建Promise来监听怪物攻击动画结束
return new Promise<void>((resolve) => {
monsterAnimation.play(`${otherCollider.node.name}_attack`);
// 监听怪物攻击动画结束
monsterAnimation.once(Animation.EventType.FINISHED, () => {
// 怪物攻击动画结束后,执行玩家死亡逻辑
this.executePlayerDefeat(otherCollider, playerLabel);
resolve();
});
});
} else {
// 如果没有怪物动画组件,直接执行玩家死亡逻辑
this.executePlayerDefeat(otherCollider, playerLabel);
}
}
/**
* 执行玩家失败逻辑
*/
private executePlayerDefeat(otherCollider: Collider2D, playerLabel: Label) {
// 怪物获胜
console.log('怪物获胜玩家生命值变为0');
// 玩家生命值变为0
playerLabel.string = '0';
// 播放生命值标签的失败动画
this.playLabelFailAnimation(playerLabel);
// 玩家死亡动画(只传递基础动画名称)
this.switchAnimation('die');
// 怪物站立动画
const animNode = otherCollider.node.getChildByName('Anim');
const monsterAnimation = animNode ? animNode.getComponent(Animation) : null;
if (monsterAnimation) {
monsterAnimation.play(`${otherCollider.node.name}_stand`);
}
// 设置游戏结束标志,禁止后续寻路
this.isGameOver = true;
console.log('游戏结束,禁止寻路');
// 显示失败弹窗
this.scheduleOnce(() => {
this.showFailedDialog();
}, 1); // 延迟1秒显示失败弹窗让玩家死亡动画播放完成
}
private async handleBoxCollision(selfCollider: Collider2D, otherCollider: Collider2D) {
if (!this.player) {
return;
}
const boxNode = otherCollider.node;
if (!boxNode || !boxNode.isValid) {
return;
}
this.stopMovement();
await this.alignPlayerForAttack(selfCollider, otherCollider);
if (!this.player || !boxNode.isValid) {
return;
}
this.switchAnimation('stand');
const playerHpNode = this.player.getChildByName('hp');
const boxHpNode = boxNode.getChildByName('hp');
if (!playerHpNode || !boxHpNode) {
console.warn('未找到玩家或宝箱的hp节点', playerHpNode, boxHpNode);
return;
}
const playerLabel = playerHpNode.getComponent(Label);
const boxLabel = boxHpNode.getComponent(Label);
if (!playerLabel || !boxLabel) {
console.warn('未找到玩家或宝箱的hp标签组件');
return;
}
const rewardValue = parseInt(boxLabel.string) || 0;
const animNode = boxNode.getChildByName('Anim');
const animation = animNode ? animNode.getComponent(Animation) : null;
const finalizeBoxOpen = () => {
if (!playerLabel.isValid) {
return;
}
const currentPlayerHp = parseInt(playerLabel.string) || 0;
const updatedPlayerHp = currentPlayerHp + rewardValue;
playerLabel.string = updatedPlayerHp.toString();
this.playLabelEmphasisAnimation(playerLabel);
if (boxNode && boxNode.isValid) {
boxNode.destroy();
}
};
if (animation) {
animation.play('open');
animation.once(Animation.EventType.FINISHED, () => {
finalizeBoxOpen();
});
} else {
finalizeBoxOpen();
}
return true;
}
/**
* 播放生命值标签强调动画(成功时)
*/
private playLabelEmphasisAnimation(label: Label) {
if (!label) return;
const originalScale = label.node.scale.clone();
const originalColor = label.color.clone();
// 创建强调动画序列
tween(label.node)
.to(0.1, { scale: new Vec3(originalScale.x * 1.2, originalScale.y * 1.2, originalScale.z) })
.to(0.1, { scale: originalScale })
.to(0.1, { scale: new Vec3(originalScale.x * 1.1, originalScale.y * 1.1, originalScale.z) })
.to(0.1, { scale: originalScale })
.start();
// 颜色闪烁效果
tween(label)
.to(0.1, { color: new Color(255, 255, 0) }) // 黄色
.to(0.1, { color: new Color(0, 255, 0) }) // 绿色
.to(0.1, { color: originalColor })
.start();
}
/**
* 播放生命值标签失败动画(失败时)
*/
private playLabelFailAnimation(label: Label) {
if (!label) return;
const originalScale = label.node.scale.clone();
const originalColor = label.color.clone();
// 创建失败动画序列 - 震动效果
tween(label.node)
.to(0.05, { position: new Vec3(label.node.position.x - 5, label.node.position.y, label.node.position.z) })
.to(0.05, { position: new Vec3(label.node.position.x + 5, label.node.position.y, label.node.position.z) })
.to(0.05, { position: new Vec3(label.node.position.x - 5, label.node.position.y, label.node.position.z) })
.to(0.05, { position: new Vec3(label.node.position.x + 5, label.node.position.y, label.node.position.z) })
.to(0.05, { position: label.node.position })
.start();
// 颜色变红效果
tween(label)
.to(0.1, { color: new Color(255, 0, 0) }) // 红色
.to(0.1, { color: new Color(128, 0, 0) }) // 暗红色
.to(0.1, { color: originalColor })
.start();
}
/**
* 创建道具飞向玩家的动画(同步方法)
*/
private async createPropsFlyToPlayerAnimation(): Promise<void> {
if (!this.player || this.props.length === 0) {
console.warn('玩家或道具不存在,无法创建飞行动画');
return;
}
console.log('创建道具飞向玩家的动画');
// 获取玩家节点的世界坐标(中心位置)
const playerWorldPos = this.player.worldPosition.clone();
// 创建所有道具的飞行动画承诺
const flyPromises: Promise<void>[] = [];
// 为每个道具创建飞行动画
this.props.forEach((prop, index) => {
if (!prop || !prop.isValid) return;
// 保存道具原始位置
const originalPos = prop.position.clone();
// 计算飞行时间(根据距离调整)
const distance = Vec3.distance(originalPos, playerWorldPos);
const flyDuration = Math.max(0.5, distance / 500); // 最少0.5秒速度500像素/秒
// 添加延迟,让道具依次飞向玩家
const delay = index * 0.1;
// 停止道具的悬浮动画
tween(prop).stop();
// 创建飞行动画的承诺
const flyPromise = new Promise<void>((resolve) => {
this.scheduleOnce(() => {
// 将玩家的世界坐标转换为道具父节点的本地坐标
const propParent = prop.parent;
let targetPos: Vec3;
if (propParent) {
const parentTransform = propParent.getComponent(UITransform);
if (parentTransform) {
targetPos = parentTransform.convertToNodeSpaceAR(playerWorldPos);
} else {
// 如果没有UITransform组件使用简单的坐标转换
targetPos = new Vec3(
playerWorldPos.x - propParent.worldPosition.x,
playerWorldPos.y - propParent.worldPosition.y,
playerWorldPos.z - propParent.worldPosition.z
);
}
} else {
// 如果道具没有父节点,直接使用世界坐标
targetPos = playerWorldPos.clone();
}
tween(prop)
.to(flyDuration, {
position: targetPos
}, {
easing: 'quadOut',
onComplete: () => {
prop.active = false;
resolve();
}
})
.start();
}, delay);
});
flyPromises.push(flyPromise);
});
// 等待所有道具飞行动画完成
await Promise.all(flyPromises);
// 所有动画完成后,播放升级动画并设置升级状态
this.playLevelUpAnimation();
this.isUpgraded = true;
this.showWeaponBonusPopup();
console.log('所有道具飞行动画完成,玩家已升级,后续动画将使用升级版本');
}
/**
* 播放玩家升级动画
*/
private playLevelUpAnimation() {
if (!this.player) {
console.warn('玩家节点不存在,无法播放升级动画');
return;
}
// 查找levelUp子节点
const levelUpNode = this.player.getChildByName('levelUp');
if (!levelUpNode) {
console.warn('未找到levelUp子节点');
return;
}
// 获取动画组件
const levelUpAnimation = levelUpNode.getComponent(Animation);
if (!levelUpAnimation) {
console.warn('levelUp节点未找到Animation组件');
return;
}
console.log('播放玩家升级动画');
// 播放升级动画
levelUpAnimation.play('levelUp');
}
/**
* 显示奖励弹窗
* 根据当前镜头位置和正交高度,将奖励节点正确缩放并移动到画面正中间
*/
public showBonusPopup() {
this.showPopupAtCameraCenter(this.bonus, '奖励弹窗');
this.setupEnterButtonListener();
}
/**
* 弹出武器奖励
*/
public showWeaponBonusPopup() {
// this.showPopupAtCameraCenter(this.bonusWuqi, '武器奖励');
}
private showPopupAtCameraCenter(popup: Node | null, nameForLog: string) {
if (!popup || !this.camera || !this.canvas) {
console.warn(`${nameForLog}节点、相机或画布未设置,无法显示${nameForLog}`);
return;
}
this.clearPopupHideSchedule();
popup.active = true;
const cameraPos = this.camera.node.position;
const orthoHeight = this.camera.orthoHeight;
popup.setPosition(cameraPos.x, cameraPos.y, 0);
const baseScale = 0.8;
const standardOrthoHeight = 500;
const scaleRatio = standardOrthoHeight / orthoHeight;
const finalScale = baseScale * scaleRatio;
popup.setScale(finalScale, finalScale, 1);
this.activePopup = popup;
this.activePopupName = nameForLog;
this.playPopupAppearAnimation(popup, nameForLog);
this.pendingPopupHide = () => {
this.pendingPopupHide = null;
this.hideActivePopup();
};
// this.scheduleOnce(this.pendingPopupHide, 3);
}
/**
* 播放奖励弹窗出现动画
*/
private playPopupAppearAnimation(popup: Node, nameForLog: string) {
const originalScale = popup.scale.clone();
popup.setScale(0.1, 0.1, 1);
tween(popup)
.to(0.3, {
scale: new Vec3(originalScale.x * 1.2, originalScale.y * 1.2, originalScale.z)
}, {
easing: 'backOut'
})
.to(0.1, {
scale: originalScale
}, {
easing: 'sineInOut'
})
.call(() => {
console.log(`${nameForLog}显示完成`);
})
.start();
}
/**
* 隐藏奖励弹窗
*/
public hideBonusPopup() {
if (!this.bonus) return;
if (this.activePopup === this.bonus) {
this.hideActivePopup();
return;
}
if (!this.bonus.active) return;
this.hidePopupWithAnimation(this.bonus, '奖励弹窗');
}
private hideActivePopup() {
if (!this.activePopup) {
return;
}
const popupToHide = this.activePopup;
const nameForLog = this.activePopupName || '弹窗';
this.clearPopupHideSchedule();
this.activePopup = null;
this.activePopupName = null;
this.hidePopupWithAnimation(popupToHide, nameForLog);
}
private hidePopupWithAnimation(popup: Node, nameForLog: string) {
tween(popup).stop();
tween(popup)
.to(0.2, {
scale: new Vec3(0.1, 0.1, 1)
}, {
easing: 'backIn'
})
.call(() => {
popup.active = false;
console.log(`${nameForLog}已隐藏`);
})
.start();
}
private clearPopupHideSchedule() {
if (this.pendingPopupHide) {
this.unschedule(this.pendingPopupHide);
this.pendingPopupHide = null;
}
}
/**
* 显示失败弹窗
*/
public showFailedDialog() {
this.showPopupAtCameraCenter(this.failedDialog, '失败弹窗');
this.setupRetryButtonListener();
}
/**
* 设置重试按钮监听器
*/
private setupRetryButtonListener() {
if (!this.failedDialog) {
console.warn('失败弹窗节点未设置,无法监听重试按钮');
return;
}
// 查找Retry按钮节点
const retryButton = this.failedDialog.getChildByName('Retry');
if (!retryButton) {
console.warn('未找到Retry按钮节点');
return;
}
// 移除之前的监听器(如果存在)
retryButton.off(Node.EventType.TOUCH_END, this.onRetryButtonClick, this);
// 添加新的监听器
retryButton.on(Node.EventType.TOUCH_END, this.onRetryButtonClick, this);
console.log('已设置重试按钮监听器');
}
/**
* 重试按钮点击事件处理
*/
private onRetryButtonClick() {
console.log('重试按钮被点击,重新加载当前场景');
// 隐藏失败弹窗
if (this.failedDialog) {
this.hidePopupWithAnimation(this.failedDialog, '失败弹窗');
}
// 重新加载当前场景
this.scheduleOnce(() => {
// 使用Cocos Creator的场景管理器重新加载当前场景
const sceneName = director.getScene().name;
director.loadScene(sceneName);
}, 0.3); // 延迟0.3秒,让弹窗消失动画完成
}
/**
* 设置Enter按钮监听器
*/
private setupEnterButtonListener() {
if (!this.bonus) {
console.warn('奖励弹窗节点未设置无法监听Enter按钮');
return;
}
// 查找Enter按钮节点
const enterButton = this.bonus.getChildByName('Enter');
if (!enterButton) {
console.warn('未找到Enter按钮节点');
return;
}
// 移除之前的监听器(如果存在)
enterButton.off(Node.EventType.TOUCH_END, this.onEnterButtonClick, this);
// 添加新的监听器
enterButton.on(Node.EventType.TOUCH_END, this.onEnterButtonClick, this);
console.log('已设置Enter按钮监听器');
}
/**
* Enter按钮点击事件处理
*/
private onEnterButtonClick() {
console.log('Enter按钮被点击执行微信小游戏状态通知');
// 隐藏奖励弹窗
if (this.bonus) {
this.hidePopupWithAnimation(this.bonus, '奖励弹窗');
}
// 执行微信小游戏状态通知代码
if (sys.platform === sys.Platform.WECHAT_GAME && (window as any).wx?.notifyMiniProgramPlayableStatus) {
(window as any).wx.notifyMiniProgramPlayableStatus({ isEnd: true });
console.log('已发送微信小游戏结束状态通知');
}
}
}