import { _decorator, Component, Node, Vec3, input, Input, EventTouch, Camera, view, tween, Animation, Collider2D, Contact2DType } from 'cc'; import { TiledMapPathfinder } from './TiledMapPathfinder'; const { ccclass, property } = _decorator; @ccclass('PlayerController') export class PlayerController extends Component { @property(Node) player: Node | null = null; // 玩家节点 @property(Camera) camera: Camera | null = null; // 主摄像机 @property(TiledMapPathfinder) pathfinder: TiledMapPathfinder | null = null; // 寻路组件 @property({ range: [1, 300] }) moveSpeed: number = 300; // 移动速度(像素/秒) @property mapWidth: number = 1080; // 地图宽度 @property mapHeight: number = 2560; // 地图高度 private isMoving: boolean = false; private isAttacking: boolean = false; private currentPath: Vec3[] = []; private currentPathIndex: number = 0; private originalPosition: Vec3 = new Vec3(); private currentAnimation: string = 'stand'; // 当前播放的动画 private lastTargetPosition: Vec3 = new Vec3(); // 上一个目标位置,用于方向判断 onLoad() { // 注册触摸事件 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); } } onDestroy() { // 移除触摸事件 input.off(Input.EventType.TOUCH_START, this.onTouchStart, this); } start() { if (this.player) { this.originalPosition.set(this.player.position); } } private onTouchStart(event: EventTouch) { if (!this.player || !this.camera || !this.pathfinder || this.isAttacking) return; // 获取触摸点的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 cameraPos = this.camera.node.position; // 计算世界坐标 const worldX = normalizedX + cameraPos.x; const worldY = normalizedY + 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); } // 使用寻路算法计算路径 const startPos = this.player.position; 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.moveToNextWaypoint(); } // 限制玩家位置在地图边界内 private clampPlayerPosition(position: Vec3): Vec3 { // 计算地图边界(地图锚点为0.5,0.5,所以范围是-mapWidth/2到+mapWidth/2) const mapHalfWidth = this.mapWidth * 0.5; const mapHalfHeight = this.mapHeight * 0.5; // 限制玩家位置 const clampedPosition = position.clone(); clampedPosition.x = Math.max(-mapHalfWidth, Math.min(mapHalfWidth, position.x)); clampedPosition.y = Math.max(-mapHalfHeight, Math.min(mapHalfHeight, position.y)); return clampedPosition; } /** * 根据移动方向获取对应的动画名称 */ private getAnimationNameByDirection(currentPos: Vec3, targetPos: Vec3): string { const deltaX = targetPos.x - currentPos.x; const deltaY = targetPos.y - currentPos.y; // 如果移动距离很小,保持当前动画 const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (distance < 1) { return this.currentAnimation; } // 计算主要移动方向 const absX = Math.abs(deltaX); const absY = Math.abs(deltaY); // 添加角度判断,更精确地确定方向 const angle = Math.atan2(deltaY, deltaX) * 180 / Math.PI; // 转换为角度 if (absX > absY) { // 水平移动为主 return deltaX > 0 ? 'walk3' : 'walk5'; } else { // 垂直移动为主 return deltaY > 0 ? 'walk3' : 'walk5'; // 上移用walk3,下移用walk5 } } /** * 切换动画,避免不必要的切换 */ private switchAnimation(animationName: string) { if (!this.player) { console.warn('Player节点未设置,无法切换动画'); return; } if (this.currentAnimation === animationName) { return; // 已经是目标动画,不需要切换 } const animation = this.player.getComponent(Animation); if (animation) { // 检查动画是否存在 const state = animation.getState(animationName); if (!state) { console.warn(`动画 ${animationName} 不存在,使用默认动画`); this.currentAnimation = 'stand'; animation.play('stand'); return; } this.currentAnimation = animationName; animation.play(animationName); console.log(`切换动画: ${animationName}`); } else { console.warn('未找到Animation组件,无法播放动画'); } } /** * 移动到路径中的下一个路径点 */ private moveToNextWaypoint() { if (this.currentAnimation === 'attack') { return } if (!this.player || this.currentPath.length === 0 || this.currentPathIndex >= this.currentPath.length) { this.isMoving = false; this.switchAnimation('stand'); console.log('路径移动完成'); return; } const targetPos = this.currentPath[this.currentPathIndex]; const currentPos = this.player.position; // 根据移动方向选择动画 const animationName = this.getAnimationNameByDirection(currentPos, targetPos); // 切换到对应的动画 this.switchAnimation(animationName); // 计算移动距离和时间 const distance = Vec3.distance(currentPos, targetPos); const moveTime = distance / this.moveSpeed; console.log(`移动到路径点${this.currentPathIndex}: (${targetPos.x.toFixed(2)}, ${targetPos.y.toFixed(2)})`); // 记录目标位置用于方向判断 this.lastTargetPosition.set(targetPos); // 使用缓动移动到目标位置 tween(this.player) .to(moveTime, { position: targetPos }, { onComplete: () => { this.currentPathIndex++; this.moveToNextWaypoint(); } }) .start(); } /** * 停止当前移动 */ private stopMovement() { if (this.player) { tween(this.player).stop(); } this.isMoving = false; this.currentPath = []; this.currentPathIndex = 0; // 停止移动时播放站立动画 this.switchAnimation('stand'); } update(deltaTime: number) { // 更新逻辑现在主要由缓动系统处理 // 这里可以添加其他需要每帧更新的逻辑 } onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D) { if (otherCollider.node.name.startsWith('guai_')) { this.isAttacking = true; // 怪物攻击 const animation = otherCollider.node.getComponent(Animation); if (animation) { animation.play(`${otherCollider.node.name}_attack`); } // player 攻击 this.switchAnimation('attack'); this.scheduleOnce(() => { animation.play(`${otherCollider.node.name}_stand`); this.isAttacking = false; this.switchAnimation('die'); }, 3); } } }