From 77ced551427e009ebe24eed0116e0a5bf960f667 Mon Sep 17 00:00:00 2001 From: richarjiang Date: Mon, 20 Oct 2025 11:04:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(player):=20=E6=94=B9=E8=BF=9B=E7=A7=BB?= =?UTF-8?q?=E5=8A=A8=E6=96=B9=E5=90=91=E7=B3=BB=E7=BB=9F=E5=92=8C=E7=A2=B0?= =?UTF-8?q?=E6=92=9E=E5=A4=84=E7=90=86=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 优化玩家移动时的方向计算,现在基于下一个路径点而非最终目标点 - 添加移动过程中动态方向更新功能,提供更流畅的动画过渡 - 重构碰撞处理逻辑,统一管理碰撞器状态避免重复触发 - 调整攻击对齐参数,增加垂直偏移量提升战斗视觉效果 - 增强碰撞检测的健壮性,添加节点有效性验证和状态检查 --- assets/scripts/PlayerController.ts | 145 ++++++++++++++++++++++++----- 1 file changed, 122 insertions(+), 23 deletions(-) diff --git a/assets/scripts/PlayerController.ts b/assets/scripts/PlayerController.ts index 9da1542..519c7bb 100644 --- a/assets/scripts/PlayerController.ts +++ b/assets/scripts/PlayerController.ts @@ -57,7 +57,8 @@ export class PlayerController extends Component { private lastPosition: Vec3 = new Vec3(); // 上一帧位置 private hasWinTimes = 0 - private readonly attackAlignGap = 10; // 玩家与怪物对阵时的额外左右间距,单位:像素 + private readonly attackAlignGap = 20; // 玩家与怪物对阵时的额外左右间距,单位:像素 + private readonly attackVerticalOffset = 25; // 玩家攻击时相对于怪物的垂直偏移量(玩家更高),单位:像素 // 道具列表 private props: Node[] = []; @@ -268,12 +269,6 @@ export class PlayerController extends Component { this.currentPathIndex = 0; this.isMoving = true; - // 根据移动方向选择动画 - const animationName = this.getAnimationNameByDirection(startPos, clampedPos); - - // 切换到对应的动画 - this.switchAnimation(animationName); - // 使用平滑路径移动 this.startSmoothPathMovement(); } @@ -537,8 +532,15 @@ export class PlayerController extends Component { this.lastTargetPosition.set(targetPos); 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) @@ -579,10 +581,17 @@ export class PlayerController extends Component { console.log(`开始平滑路径移动,总距离: ${totalDistance.toFixed(2)}, 总时间: ${totalTime.toFixed(2)}秒`); - // 在移动前计算并设置方向(只计算一次) - const startPos = this.player.position.clone(); - const finalTargetPos = this.currentPath[this.currentPath.length - 1]; - this.updateMovementDirectionOnce(startPos, finalTargetPos); + // 初始化时先设置第一个路径点的方向 + 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) @@ -593,6 +602,9 @@ export class PlayerController extends Component { const currentPos = this.getPositionOnPath(ratio); if (currentPos) { target.position = currentPos; + + // 在移动过程中动态更新方向 + this.updateDirectionDuringMovement(currentPos, ratio); } }, onComplete: () => { @@ -673,6 +685,52 @@ export class PlayerController extends Component { 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'); } + /** + * 统一处理碰撞回调的启停,确保在逻辑失败时能恢复碰撞器状态 + */ + private async processColliderCollision(otherCollider: Collider2D, handler: () => Promise | 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) { // 更新逻辑现在主要由缓动系统处理 // 这里可以添加其他需要每帧更新的逻辑 @@ -703,13 +786,28 @@ export class PlayerController extends Component { onBeginContact(selfCollider: Collider2D, otherCollider: Collider2D) { console.log('碰撞检测', selfCollider.node.name, otherCollider.node.name); - // 禁用碰撞器,防止重复触发 - otherCollider.enabled = false; - if (otherCollider.node.name.startsWith('guai_')) { - void this.handleAttack(selfCollider, otherCollider); - } else if (otherCollider.node.name.startsWith('box_')) { - void this.handleBoxCollision(selfCollider, otherCollider); + 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)); } /** @@ -751,7 +849,7 @@ export class PlayerController extends Component { const targetWorldPos = new Vec3( monsterWorldPos.x + monsterOffsetX + directionMultiplier * totalHalfWidth - playerOffsetX, - monsterWorldPos.y + monsterOffsetY - playerOffsetY, + monsterWorldPos.y + monsterOffsetY - playerOffsetY + (this.isUpgraded ? 0 : this.attackVerticalOffset), playerWorldPos.z ); @@ -922,6 +1020,8 @@ export class PlayerController extends Component { } } }); + + return true; } /** @@ -1045,9 +1145,6 @@ export class PlayerController extends Component { return; } - // 防止重复触发 - otherCollider.enabled = false; - this.stopMovement(); await this.alignPlayerForAttack(selfCollider, otherCollider); @@ -1102,6 +1199,8 @@ export class PlayerController extends Component { } else { finalizeBoxOpen(); } + + return true; }