feat(player): 改进移动方向系统和碰撞处理机制

- 优化玩家移动时的方向计算,现在基于下一个路径点而非最终目标点
- 添加移动过程中动态方向更新功能,提供更流畅的动画过渡
- 重构碰撞处理逻辑,统一管理碰撞器状态避免重复触发
- 调整攻击对齐参数,增加垂直偏移量提升战斗视觉效果
- 增强碰撞检测的健壮性,添加节点有效性验证和状态检查
This commit is contained in:
richarjiang
2025-10-20 11:04:26 +08:00
parent 972334f786
commit 77ced55142

View File

@@ -57,7 +57,8 @@ export class PlayerController extends Component {
private lastPosition: Vec3 = new Vec3(); // 上一帧位置 private lastPosition: Vec3 = new Vec3(); // 上一帧位置
private hasWinTimes = 0 private hasWinTimes = 0
private readonly attackAlignGap = 10; // 玩家与怪物对阵时的额外左右间距,单位:像素 private readonly attackAlignGap = 20; // 玩家与怪物对阵时的额外左右间距,单位:像素
private readonly attackVerticalOffset = 25; // 玩家攻击时相对于怪物的垂直偏移量(玩家更高),单位:像素
// 道具列表 // 道具列表
private props: Node[] = []; private props: Node[] = [];
@@ -268,12 +269,6 @@ export class PlayerController extends Component {
this.currentPathIndex = 0; this.currentPathIndex = 0;
this.isMoving = true; this.isMoving = true;
// 根据移动方向选择动画
const animationName = this.getAnimationNameByDirection(startPos, clampedPos);
// 切换到对应的动画
this.switchAnimation(animationName);
// 使用平滑路径移动 // 使用平滑路径移动
this.startSmoothPathMovement(); this.startSmoothPathMovement();
} }
@@ -537,8 +532,15 @@ export class PlayerController extends Component {
this.lastTargetPosition.set(targetPos); this.lastTargetPosition.set(targetPos);
this.lastPosition.set(currentPos); this.lastPosition.set(currentPos);
// 在移动前计算并设置方向(只计算一次) // 计算朝向下一个路径点的方向,而不是当前目标点
this.updateMovementDirectionOnce(currentPos, targetPos); let nextPosForDirection = targetPos;
if (this.currentPathIndex < this.currentPath.length - 1) {
// 如果还有下一个路径点,使用下一个路径点作为方向参考
nextPosForDirection = this.currentPath[this.currentPathIndex + 1];
}
// 在移动前计算并设置方向(基于下一个路径点)
this.updateMovementDirectionOnce(currentPos, nextPosForDirection);
// 使用缓动移动到目标位置 // 使用缓动移动到目标位置
this.moveTween = tween(this.player) this.moveTween = tween(this.player)
@@ -579,10 +581,17 @@ export class PlayerController extends Component {
console.log(`开始平滑路径移动,总距离: ${totalDistance.toFixed(2)}, 总时间: ${totalTime.toFixed(2)}`); console.log(`开始平滑路径移动,总距离: ${totalDistance.toFixed(2)}, 总时间: ${totalTime.toFixed(2)}`);
// 在移动前计算并设置方向(只计算一次) // 初始化时先设置第一个路径点的方向
const startPos = this.player.position.clone(); if (this.currentPath.length > 1) {
const finalTargetPos = this.currentPath[this.currentPath.length - 1]; const startPos = this.player.position.clone();
this.updateMovementDirectionOnce(startPos, finalTargetPos); 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) this.moveTween = tween(this.player)
@@ -593,6 +602,9 @@ export class PlayerController extends Component {
const currentPos = this.getPositionOnPath(ratio); const currentPos = this.getPositionOnPath(ratio);
if (currentPos) { if (currentPos) {
target.position = currentPos; target.position = currentPos;
// 在移动过程中动态更新方向
this.updateDirectionDuringMovement(currentPos, ratio);
} }
}, },
onComplete: () => { onComplete: () => {
@@ -673,6 +685,52 @@ export class PlayerController extends Component {
this.switchAnimation('walk'); this.switchAnimation('walk');
} }
/**
* 在移动过程中动态更新方向
* 根据当前位置在路径中的位置,计算朝向下一个路径点的方向
*/
private updateDirectionDuringMovement(currentPos: Vec3, ratio: number) {
if (!this.player || this.currentPath.length <= 1) {
return;
}
// 计算当前在路径中的大致位置
const pathLength = this.currentPath.length;
const currentPathIndex = Math.floor(ratio * (pathLength - 1));
// 确保不超出路径范围
const nextIndex = Math.min(currentPathIndex + 1, pathLength - 1);
// 如果已经到达最后一个路径点,不再更新方向
if (currentPathIndex >= pathLength - 1) {
return;
}
// 获取当前路径点和下一个路径点
const currentPathPoint = this.currentPath[currentPathIndex];
const nextPathPoint = this.currentPath[nextIndex];
// 计算到下一个路径点的方向
const deltaX = nextPathPoint.x - currentPos.x;
const deltaY = nextPathPoint.y - currentPos.y;
// 如果移动距离很小,不更新动画
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 1) {
return;
}
// 计算新的方向
const newDirection = this.resolveDirectionFromDelta(deltaX, deltaY);
// 只有当方向发生显著变化时才更新
if (newDirection !== this.currentDirection) {
this.currentDirection = newDirection;
this.updatePlayerScale();
console.log(`移动过程中更新方向: ${newDirection}`);
}
}
/** /**
* 停止当前移动 * 停止当前移动
*/ */
@@ -694,6 +752,31 @@ export class PlayerController extends Component {
this.switchAnimation('stand'); 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) { update(deltaTime: number) {
// 更新逻辑现在主要由缓动系统处理 // 更新逻辑现在主要由缓动系统处理
// 这里可以添加其他需要每帧更新的逻辑 // 这里可以添加其他需要每帧更新的逻辑
@@ -703,13 +786,28 @@ export class PlayerController extends Component {
onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D) { onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D) {
console.log('碰撞检测', selfCollider.node.name, otherCollider.node.name); console.log('碰撞检测', selfCollider.node.name, otherCollider.node.name);
// 禁用碰撞器,防止重复触发 if (!otherCollider || !otherCollider.node || !otherCollider.node.isValid) {
otherCollider.enabled = false; return;
if (otherCollider.node.name.startsWith('guai_')) {
void this.handleAttack(selfCollider, otherCollider);
} else if (otherCollider.node.name.startsWith('box_')) {
void this.handleBoxCollision(selfCollider, otherCollider);
} }
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));
} }
/** /**
@@ -751,7 +849,7 @@ export class PlayerController extends Component {
const targetWorldPos = new Vec3( const targetWorldPos = new Vec3(
monsterWorldPos.x + monsterOffsetX + directionMultiplier * totalHalfWidth - playerOffsetX, monsterWorldPos.x + monsterOffsetX + directionMultiplier * totalHalfWidth - playerOffsetX,
monsterWorldPos.y + monsterOffsetY - playerOffsetY, monsterWorldPos.y + monsterOffsetY - playerOffsetY + (this.isUpgraded ? 0 : this.attackVerticalOffset),
playerWorldPos.z playerWorldPos.z
); );
@@ -922,6 +1020,8 @@ export class PlayerController extends Component {
} }
} }
}); });
return true;
} }
/** /**
@@ -1045,9 +1145,6 @@ export class PlayerController extends Component {
return; return;
} }
// 防止重复触发
otherCollider.enabled = false;
this.stopMovement(); this.stopMovement();
await this.alignPlayerForAttack(selfCollider, otherCollider); await this.alignPlayerForAttack(selfCollider, otherCollider);
@@ -1102,6 +1199,8 @@ export class PlayerController extends Component {
} else { } else {
finalizeBoxOpen(); finalizeBoxOpen();
} }
return true;
} }