feat: 寻路优化

This commit is contained in:
richarjiang
2025-10-10 14:40:28 +08:00
parent dbdec71d0d
commit 455cca40b0
5 changed files with 743 additions and 523 deletions

View File

@@ -41,12 +41,6 @@ export class PlayerController extends Component {
@property
mapHeight: number = 2560; // 地图高度
@property({ tooltip: '玩家与怪物进入战斗时的理想距离' })
attackPreferredDistance: number = 10;
@property({ tooltip: '允许的距离误差范围,超出后会进行位置调整' })
attackDistanceTolerance: number = 10;
private isMoving: boolean = false;
private isAttacking: boolean = false;
private currentPath: Vec3[] = [];
@@ -59,10 +53,11 @@ export class PlayerController extends Component {
private isWin: boolean = false; // 游戏是否胜利(到达终点)
private currentDirection: number = 5; // 当前玩家朝向3表示左/上5表示右/下默认为5
private hasWinTimes = 0
// 平滑移动相关变量
private moveTween: any = null; // 当前移动的tween对象
private lastPosition: Vec3 = new Vec3(); // 上一帧位置
private readonly _tempVec3A: Vec3 = new Vec3();
private readonly _tempVec3B: Vec3 = new Vec3();
private hasWinTimes = 0
// 道具列表
private props: Node[] = [];
@@ -225,7 +220,7 @@ export class PlayerController extends Component {
if (!this.player || !this.pathfinder) return;
// 停止当前移动
this.stopMovement();
// this.stopMovement();
// 限制目标位置在地图边界内
const clampedPos = this.clampPlayerPosition(worldPos);
@@ -262,7 +257,8 @@ export class PlayerController extends Component {
// 切换到对应的动画
this.switchAnimation(animationName);
this.moveToNextWaypoint();
// 使用平滑路径移动
this.startSmoothPathMovement();
}
// 限制玩家位置在地图边界内
@@ -374,12 +370,15 @@ export class PlayerController extends Component {
return;
}
// 停止当前的移动tween
if (this.moveTween) {
this.moveTween.stop();
this.moveTween = null;
}
const targetPos = this.currentPath[this.currentPathIndex];
const currentPos = this.player.position;
// 计算移动距离和时间
const distance = Vec3.distance(currentPos, targetPos);
const moveTime = distance / this.moveSpeed;
@@ -388,10 +387,16 @@ export class PlayerController extends Component {
// 记录目标位置用于方向判断
this.lastTargetPosition.set(targetPos);
this.lastPosition.set(currentPos);
// 使用缓动移动到目标位置
tween(this.player)
this.moveTween = tween(this.player)
.to(moveTime, { position: targetPos }, {
easing: 'linear', // 使用线性插值,保持匀速移动
onUpdate: (target: Node) => {
// 在移动过程中更新动画方向
this.updateMovementDirection(target.position);
},
onComplete: () => {
this.currentPathIndex++;
this.moveToNextWaypoint();
@@ -400,10 +405,153 @@ export class PlayerController extends Component {
.start();
}
/**
* 开始平滑路径移动
*/
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)}`);
// 创建连续的路径移动
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.updateMovementDirection(currentPos);
}
},
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 updateMovementDirection(currentPos: Vec3) {
if (!this.player || this.currentPath.length === 0) {
return;
}
// 计算移动方向(基于下一路径点)
let targetPos: Vec3;
if (this.currentPathIndex < this.currentPath.length - 1) {
targetPos = this.currentPath[this.currentPathIndex + 1];
} else {
targetPos = this.currentPath[this.currentPath.length - 1];
}
const deltaX = targetPos.x - currentPos.x;
const deltaY = targetPos.y - currentPos.y;
// 如果移动距离很小,不更新动画
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < 1) {
return;
}
// 计算主要移动方向
const absX = Math.abs(deltaX);
const absY = Math.abs(deltaY);
// 根据移动方向选择动画
let animationName = 'walk';
if (absX > absY) {
// 水平移动为主
this.currentDirection = deltaX < 0 ? 3 : 5;
animationName = deltaX < 0 ? 'walk3' : 'walk5';
} else {
// 垂直移动为主
this.currentDirection = deltaY < 0 ? 3 : 5;
animationName = deltaY < 0 ? 'walk3' : 'walk5';
}
// 切换到对应的动画
this.switchAnimation(animationName);
}
/**
* 停止当前移动
*/
private stopMovement() {
// 停止当前的移动tween
if (this.moveTween) {
this.moveTween.stop();
this.moveTween = null;
}
if (this.player) {
tween(this.player).stop();
}
@@ -447,12 +595,6 @@ export class PlayerController extends Component {
this.stopMovement();
// this.scheduleOnce(() => {
// this.adjustPositionsForAttack(otherCollider.node);
// }, 0);
// 获取玩家和怪物的生命值
const playerHpLabel = this.player.getChildByName('hp');
const monsterHpLabel = otherCollider.node.getChildByName('hp');
@@ -643,76 +785,6 @@ export class PlayerController extends Component {
}
}
private adjustPositionsForAttack(monsterNode: Node) {
if (!this.player || !monsterNode) {
return;
}
const desiredDistance = Math.max(0, this.attackPreferredDistance);
const tolerance = Math.max(0, this.attackDistanceTolerance);
if (desiredDistance === 0) {
return;
}
const playerWorldPos = this._tempVec3A;
const monsterWorldPos = this._tempVec3B;
this.player.getWorldPosition(playerWorldPos);
monsterNode.getWorldPosition(monsterWorldPos);
const playerTarget = playerWorldPos.clone();
const monsterTarget = monsterWorldPos.clone();
const deltaX = playerWorldPos.x - monsterWorldPos.x;
const horizontalDistance = Math.abs(deltaX);
const verticalDifference = Math.abs(playerWorldPos.y - monsterWorldPos.y);
const targetY = (playerWorldPos.y + monsterWorldPos.y) * 0.5;
let adjusted = false;
if (verticalDifference > 1e-3) {
playerTarget.y = targetY;
monsterTarget.y = targetY;
adjusted = true;
}
const orderCorrect = playerWorldPos.x > monsterWorldPos.x;
const distanceOutOfRange = horizontalDistance < desiredDistance - tolerance || horizontalDistance > desiredDistance + tolerance;
if (!orderCorrect || distanceOutOfRange) {
const midX = (playerWorldPos.x + monsterWorldPos.x) * 0.5;
const halfDistance = desiredDistance * 0.5;
playerTarget.x = midX + halfDistance;
monsterTarget.x = midX - halfDistance;
adjusted = true;
}
if (!adjusted) {
return;
}
// 确保最终目标仍然满足玩家在右侧的要求
if (playerTarget.x <= monsterTarget.x) {
const swapMidX = (playerTarget.x + monsterTarget.x) * 0.5;
const halfDistance = desiredDistance * 0.5;
playerTarget.x = swapMidX + halfDistance;
monsterTarget.x = swapMidX - halfDistance;
}
const clampedPlayer = this.clampPositionWithinMap(playerTarget);
const clampedMonster = this.clampPositionWithinMap(monsterTarget);
this.setNodeWorldPosition(this.player, clampedPlayer);
this.setNodeWorldPosition(monsterNode, clampedMonster);
this.player.getWorldPosition(playerWorldPos);
monsterNode.getWorldPosition(monsterWorldPos);
const finalHorizontalDistance = Math.abs(playerWorldPos.x - monsterWorldPos.x);
const finalVerticalOffset = Math.abs(playerWorldPos.y - monsterWorldPos.y);
console.log(`战斗位置已调整,水平距离: ${finalHorizontalDistance.toFixed(2)}, 垂直误差: ${finalVerticalOffset.toFixed(2)}`);
if (playerWorldPos.x >= monsterWorldPos.x) {
this.currentDirection = 3;
}
}
private setNodeWorldPosition(node: Node, worldPos: Vec3) {
const parent = node.parent;