perf(player): 优化移动方向计算和道具飞行动画

- 改进移动方向计算逻辑,从移动过程中持续更新改为移动开始前计算一次,提升性能
- 优化道具飞向玩家的动画,使用世界坐标和正确的坐标转换
- 移除触摸开始时自动隐藏弹窗的逻辑
- 调整方向判断算法,优先判断水平方向,更符合梦幻西游风格
This commit is contained in:
richarjiang
2025-10-15 16:21:10 +08:00
parent 27c30713a9
commit 0e803bc5f0
2 changed files with 421 additions and 387 deletions

View File

@@ -152,7 +152,6 @@ export class PlayerController extends Component {
private onTouchStart(event: EventTouch) {
if (this.activePopup) {
this.hideActivePopup();
return;
}
@@ -422,14 +421,13 @@ export class PlayerController extends Component {
this.lastTargetPosition.set(targetPos);
this.lastPosition.set(currentPos);
// 在移动前计算并设置方向(只计算一次)
this.updateMovementDirectionOnce(currentPos, targetPos);
// 使用缓动移动到目标位置
this.moveTween = tween(this.player)
.to(moveTime, { position: targetPos }, {
easing: 'linear', // 使用线性插值,保持匀速移动
onUpdate: (target: Node) => {
// 在移动过程中更新动画方向
this.updateMovementDirection(target.position);
},
onComplete: () => {
this.currentPathIndex++;
this.moveToNextWaypoint();
@@ -465,6 +463,11 @@ 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);
// 创建连续的路径移动
this.moveTween = tween(this.player)
.to(totalTime, { position: this.currentPath[this.currentPath.length - 1] }, {
@@ -474,7 +477,6 @@ export class PlayerController extends Component {
const currentPos = this.getPositionOnPath(ratio);
if (currentPos) {
target.position = currentPos;
this.updateMovementDirection(currentPos);
}
},
onComplete: () => {
@@ -531,23 +533,17 @@ export class PlayerController extends Component {
}
/**
* 在移动过程中更新动画方向
* 在移动开始前计算一次方向(梦幻西游风格:点击一次屏幕只计算一次方向)
* 算法:优先判断水平方向(左/右),只有当水平方向不明显时才判断垂直方向
*/
private updateMovementDirection(currentPos: Vec3) {
if (!this.player || this.currentPath.length === 0) {
private updateMovementDirectionOnce(startPos: Vec3, targetPos: Vec3) {
if (!this.player) {
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 deltaX = targetPos.x - startPos.x;
const deltaY = targetPos.y - startPos.y;
// 如果移动距离很小,不更新动画
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
@@ -555,17 +551,19 @@ export class PlayerController extends Component {
return;
}
// 计算主要移动方向
const absX = Math.abs(deltaX);
const absY = Math.abs(deltaY);
// 梦幻西游方向判断逻辑:
// 1. 优先判断水平方向:左上、左下、左都算左边;右上、右下、右都算右边
// 2. 只有当水平方向不明显时(接近垂直),才判断垂直方向
// 更新当前方向
if (absX > absY) {
// 水平移动为主
this.currentDirection = deltaX < 0 ? 3 : 5;
// 设置一个阈值,当水平方向的绝对值大于这个阈值时,优先判断水平方向
const horizontalThreshold = 0.5; // 可以根据需要调整这个值
if (Math.abs(deltaX) > horizontalThreshold) {
// 水平方向明显,优先判断左右
this.currentDirection = deltaX < 0 ? 3 : 5; // 3表示左5表示右
} else {
// 垂直移动为主
this.currentDirection = deltaY < 0 ? 3 : 5;
// 水平方向不明显,判断垂直方向
this.currentDirection = deltaY < 0 ? 3 : 5; // 3表示上5表示下
}
// 切换到对应的动画(只传递基础动画名称)
@@ -1067,8 +1065,8 @@ export class PlayerController extends Component {
console.log('创建道具飞向玩家的动画');
// 获取玩家位置
const playerPos = this.player.position.clone();
// 获取玩家节点的世界坐标(中心位置
const playerWorldPos = this.player.worldPosition.clone();
// 创建所有道具的飞行动画承诺
const flyPromises: Promise<void>[] = [];
@@ -1081,7 +1079,7 @@ export class PlayerController extends Component {
const originalPos = prop.position.clone();
// 计算飞行时间(根据距离调整)
const distance = Vec3.distance(originalPos, playerPos);
const distance = Vec3.distance(originalPos, playerWorldPos);
const flyDuration = Math.max(0.5, distance / 500); // 最少0.5秒速度500像素/秒
// 添加延迟,让道具依次飞向玩家
@@ -1093,9 +1091,30 @@ export class PlayerController extends Component {
// 创建飞行动画的承诺
const flyPromise = new Promise<void>((resolve) => {
this.scheduleOnce(() => {
// 将玩家的世界坐标转换为道具父节点的本地坐标
const propParent = prop.parent;
let targetPos: Vec3;
if (propParent) {
const parentTransform = propParent.getComponent(UITransform);
if (parentTransform) {
targetPos = parentTransform.convertToNodeSpaceAR(playerWorldPos);
} else {
// 如果没有UITransform组件使用简单的坐标转换
targetPos = new Vec3(
playerWorldPos.x - propParent.worldPosition.x,
playerWorldPos.y - propParent.worldPosition.y,
playerWorldPos.z - propParent.worldPosition.z
);
}
} else {
// 如果道具没有父节点,直接使用世界坐标
targetPos = playerWorldPos.clone();
}
tween(prop)
.to(flyDuration, {
position: new Vec3(playerPos.x, playerPos.y, playerPos.z)
position: targetPos
}, {
easing: 'quadOut',
onComplete: () => {