Files
climb/assets/scripts/PlayerController.ts
2025-09-22 16:41:31 +08:00

290 lines
8.5 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, 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');
}
}
}