feat: 寻路优化
This commit is contained in:
@@ -1472,7 +1472,7 @@
|
|||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"tag": 0,
|
"tag": 0,
|
||||||
"_group": 1,
|
"_group": 2,
|
||||||
"_density": 1,
|
"_density": 1,
|
||||||
"_sensor": false,
|
"_sensor": false,
|
||||||
"_friction": 0.2,
|
"_friction": 0.2,
|
||||||
@@ -1912,8 +1912,6 @@
|
|||||||
"moveSpeed": 300,
|
"moveSpeed": 300,
|
||||||
"mapWidth": 1080,
|
"mapWidth": 1080,
|
||||||
"mapHeight": 1920,
|
"mapHeight": 1920,
|
||||||
"attackPreferredDistance": 220,
|
|
||||||
"attackDistanceTolerance": 10,
|
|
||||||
"_id": "c1AuAU3IlKnLOzgk9vsBr4"
|
"_id": "c1AuAU3IlKnLOzgk9vsBr4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -3696,7 +3694,7 @@
|
|||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"tag": 0,
|
"tag": 0,
|
||||||
"_group": 1,
|
"_group": 4,
|
||||||
"_density": 1,
|
"_density": 1,
|
||||||
"_sensor": false,
|
"_sensor": false,
|
||||||
"_friction": 0.2,
|
"_friction": 0.2,
|
||||||
@@ -3896,7 +3894,7 @@
|
|||||||
},
|
},
|
||||||
"_lscale": {
|
"_lscale": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 4.172,
|
"x": -4.172,
|
||||||
"y": 1.768,
|
"y": 1.768,
|
||||||
"z": 2.116
|
"z": 2.116
|
||||||
},
|
},
|
||||||
@@ -4330,20 +4328,20 @@
|
|||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"tag": 0,
|
"tag": 0,
|
||||||
"_group": 1,
|
"_group": 4,
|
||||||
"_density": 1,
|
"_density": 1,
|
||||||
"_sensor": false,
|
"_sensor": false,
|
||||||
"_friction": 0.2,
|
"_friction": 0.2,
|
||||||
"_restitution": 0,
|
"_restitution": 0,
|
||||||
"_offset": {
|
"_offset": {
|
||||||
"__type__": "cc.Vec2",
|
"__type__": "cc.Vec2",
|
||||||
"x": -16.1,
|
"x": -11.1,
|
||||||
"y": 7.4
|
"y": -0.2
|
||||||
},
|
},
|
||||||
"_size": {
|
"_size": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 97.4,
|
"width": 86.2,
|
||||||
"height": 121.5
|
"height": 110.3
|
||||||
},
|
},
|
||||||
"_id": "3ca3euVMhBkrOMELnKR9Zg"
|
"_id": "3ca3euVMhBkrOMELnKR9Zg"
|
||||||
},
|
},
|
||||||
@@ -4964,20 +4962,20 @@
|
|||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"tag": 0,
|
"tag": 0,
|
||||||
"_group": 1,
|
"_group": 4,
|
||||||
"_density": 1,
|
"_density": 1,
|
||||||
"_sensor": false,
|
"_sensor": false,
|
||||||
"_friction": 0.2,
|
"_friction": 0.2,
|
||||||
"_restitution": 0,
|
"_restitution": 0,
|
||||||
"_offset": {
|
"_offset": {
|
||||||
"__type__": "cc.Vec2",
|
"__type__": "cc.Vec2",
|
||||||
"x": 2.3,
|
"x": 1.7,
|
||||||
"y": -0.3
|
"y": -3.3
|
||||||
},
|
},
|
||||||
"_size": {
|
"_size": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 74.8,
|
"width": 45.7,
|
||||||
"height": 132
|
"height": 119.5
|
||||||
},
|
},
|
||||||
"_id": "efpC9N9EpG+r7WSMK95lgW"
|
"_id": "efpC9N9EpG+r7WSMK95lgW"
|
||||||
},
|
},
|
||||||
@@ -5371,20 +5369,20 @@
|
|||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"tag": 0,
|
"tag": 0,
|
||||||
"_group": 1,
|
"_group": 4,
|
||||||
"_density": 1,
|
"_density": 1,
|
||||||
"_sensor": false,
|
"_sensor": false,
|
||||||
"_friction": 0.2,
|
"_friction": 0.2,
|
||||||
"_restitution": 0,
|
"_restitution": 0,
|
||||||
"_offset": {
|
"_offset": {
|
||||||
"__type__": "cc.Vec2",
|
"__type__": "cc.Vec2",
|
||||||
"x": 1.3,
|
"x": 6,
|
||||||
"y": 7.5
|
"y": 3.2
|
||||||
},
|
},
|
||||||
"_size": {
|
"_size": {
|
||||||
"__type__": "cc.Size",
|
"__type__": "cc.Size",
|
||||||
"width": 88.9,
|
"width": 73.1,
|
||||||
"height": 96.6
|
"height": 65.6
|
||||||
},
|
},
|
||||||
"_id": "3dz7NavQFBMZ60R0LoZHEP"
|
"_id": "3dz7NavQFBMZ60R0LoZHEP"
|
||||||
},
|
},
|
||||||
@@ -5778,7 +5776,7 @@
|
|||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"tag": 0,
|
"tag": 0,
|
||||||
"_group": 1,
|
"_group": 4,
|
||||||
"_density": 1,
|
"_density": 1,
|
||||||
"_sensor": false,
|
"_sensor": false,
|
||||||
"_friction": 0.2,
|
"_friction": 0.2,
|
||||||
@@ -6412,7 +6410,7 @@
|
|||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"tag": 0,
|
"tag": 0,
|
||||||
"_group": 1,
|
"_group": 4,
|
||||||
"_density": 1,
|
"_density": 1,
|
||||||
"_sensor": false,
|
"_sensor": false,
|
||||||
"_friction": 0.2,
|
"_friction": 0.2,
|
||||||
@@ -6612,7 +6610,7 @@
|
|||||||
},
|
},
|
||||||
"_lscale": {
|
"_lscale": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 3.256,
|
"x": -3.256,
|
||||||
"y": 1.763,
|
"y": 1.763,
|
||||||
"z": 1.763
|
"z": 1.763
|
||||||
},
|
},
|
||||||
@@ -7046,7 +7044,7 @@
|
|||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"tag": 0,
|
"tag": 0,
|
||||||
"_group": 1,
|
"_group": 4,
|
||||||
"_density": 1,
|
"_density": 1,
|
||||||
"_sensor": false,
|
"_sensor": false,
|
||||||
"_friction": 0.2,
|
"_friction": 0.2,
|
||||||
@@ -7246,7 +7244,7 @@
|
|||||||
},
|
},
|
||||||
"_lscale": {
|
"_lscale": {
|
||||||
"__type__": "cc.Vec3",
|
"__type__": "cc.Vec3",
|
||||||
"x": 1.36,
|
"x": 1.86,
|
||||||
"y": 0.822,
|
"y": 0.822,
|
||||||
"z": 1.154
|
"z": 1.154
|
||||||
},
|
},
|
||||||
@@ -7680,7 +7678,7 @@
|
|||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"tag": 0,
|
"tag": 0,
|
||||||
"_group": 1,
|
"_group": 4,
|
||||||
"_density": 1,
|
"_density": 1,
|
||||||
"_sensor": false,
|
"_sensor": false,
|
||||||
"_friction": 0.2,
|
"_friction": 0.2,
|
||||||
@@ -8314,7 +8312,7 @@
|
|||||||
"_enabled": true,
|
"_enabled": true,
|
||||||
"__prefab": null,
|
"__prefab": null,
|
||||||
"tag": 0,
|
"tag": 0,
|
||||||
"_group": 1,
|
"_group": 4,
|
||||||
"_density": 1,
|
"_density": 1,
|
||||||
"_sensor": false,
|
"_sensor": false,
|
||||||
"_friction": 0.2,
|
"_friction": 0.2,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export class PathNode {
|
|||||||
fCost: number = 0; // 总代价 f = g + h
|
fCost: number = 0; // 总代价 f = g + h
|
||||||
parent: PathNode | null = null;
|
parent: PathNode | null = null;
|
||||||
walkable: boolean = true;
|
walkable: boolean = true;
|
||||||
|
moveCost: number = 1.0; // 从父节点移动到此节点的代价
|
||||||
|
|
||||||
constructor(x: number, y: number, walkable: boolean = true) {
|
constructor(x: number, y: number, walkable: boolean = true) {
|
||||||
this.x = x;
|
this.x = x;
|
||||||
@@ -110,7 +111,7 @@ export class AStarPathfinding extends Component {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newGCost = currentNode.gCost + this.getDistance(currentNode, neighbor);
|
const newGCost = currentNode.gCost + neighbor.moveCost;
|
||||||
|
|
||||||
if (newGCost < neighbor.gCost || openSet.indexOf(neighbor) === -1) {
|
if (newGCost < neighbor.gCost || openSet.indexOf(neighbor) === -1) {
|
||||||
neighbor.gCost = newGCost;
|
neighbor.gCost = newGCost;
|
||||||
@@ -130,17 +131,21 @@ export class AStarPathfinding extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取节点的相邻节点(4方向)
|
* 获取节点的相邻节点(8方向)
|
||||||
*/
|
*/
|
||||||
private getNeighbors(node: PathNode): PathNode[] {
|
private getNeighbors(node: PathNode): PathNode[] {
|
||||||
const neighbors: PathNode[] = [];
|
const neighbors: PathNode[] = [];
|
||||||
|
|
||||||
// 四个方向:上、下、左、右
|
// 八个方向:上、下、左、右、左上、右上、左下、右下
|
||||||
const directions = [
|
const directions = [
|
||||||
{ x: 0, y: 1 }, // 上
|
{ x: 0, y: 1, cost: 1.0 }, // 上
|
||||||
{ x: 0, y: -1 }, // 下
|
{ x: 0, y: -1, cost: 1.0 }, // 下
|
||||||
{ x: -1, y: 0 }, // 左
|
{ x: -1, y: 0, cost: 1.0 }, // 左
|
||||||
{ x: 1, y: 0 } // 右
|
{ x: 1, y: 0, cost: 1.0 }, // 右
|
||||||
|
{ x: -1, y: 1, cost: 1.414 }, // 左上
|
||||||
|
{ x: 1, y: 1, cost: 1.414 }, // 右上
|
||||||
|
{ x: -1, y: -1, cost: 1.414 },// 左下
|
||||||
|
{ x: 1, y: -1, cost: 1.414 } // 右下
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const dir of directions) {
|
for (const dir of directions) {
|
||||||
@@ -148,7 +153,18 @@ export class AStarPathfinding extends Component {
|
|||||||
const checkY = node.y + dir.y;
|
const checkY = node.y + dir.y;
|
||||||
|
|
||||||
if (this.isValidPosition(checkX, checkY)) {
|
if (this.isValidPosition(checkX, checkY)) {
|
||||||
neighbors.push(this.grid[checkX][checkY]);
|
const neighbor = this.grid[checkX][checkY];
|
||||||
|
// 为对角线移动添加额外检查,防止穿过墙角
|
||||||
|
if (dir.cost > 1.0) {
|
||||||
|
// 检查对角线移动时,相邻的两个直角方向是否可行走
|
||||||
|
const xCheck = this.grid[node.x + dir.x][node.y];
|
||||||
|
const yCheck = this.grid[node.x][node.y + dir.y];
|
||||||
|
if (!xCheck.walkable || !yCheck.walkable) {
|
||||||
|
continue; // 如果相邻的直角方向不可行走,则不能进行对角线移动
|
||||||
|
}
|
||||||
|
}
|
||||||
|
neighbor.moveCost = dir.cost;
|
||||||
|
neighbors.push(neighbor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,12 +172,19 @@ export class AStarPathfinding extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取两个节点之间的距离(曼哈顿距离)
|
* 获取两个节点之间的距离(欧几里得距离,支持对角线移动)
|
||||||
*/
|
*/
|
||||||
private getDistance(nodeA: PathNode, nodeB: PathNode): number {
|
private getDistance(nodeA: PathNode, nodeB: PathNode): number {
|
||||||
const distX = Math.abs(nodeA.x - nodeB.x);
|
const distX = Math.abs(nodeA.x - nodeB.x);
|
||||||
const distY = Math.abs(nodeA.y - nodeB.y);
|
const distY = Math.abs(nodeA.y - nodeB.y);
|
||||||
return distX + distY;
|
|
||||||
|
// 使用欧几里得距离,更适合对角线移动
|
||||||
|
// 对角线距离约为1.414,直线距离为1
|
||||||
|
if (distX === distY) {
|
||||||
|
return distX * 1.414; // 对角线移动
|
||||||
|
} else {
|
||||||
|
return distX + distY; // 曼哈顿距离
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -41,12 +41,6 @@ export class PlayerController extends Component {
|
|||||||
@property
|
@property
|
||||||
mapHeight: number = 2560; // 地图高度
|
mapHeight: number = 2560; // 地图高度
|
||||||
|
|
||||||
@property({ tooltip: '玩家与怪物进入战斗时的理想距离' })
|
|
||||||
attackPreferredDistance: number = 10;
|
|
||||||
|
|
||||||
@property({ tooltip: '允许的距离误差范围,超出后会进行位置调整' })
|
|
||||||
attackDistanceTolerance: number = 10;
|
|
||||||
|
|
||||||
private isMoving: boolean = false;
|
private isMoving: boolean = false;
|
||||||
private isAttacking: boolean = false;
|
private isAttacking: boolean = false;
|
||||||
private currentPath: Vec3[] = [];
|
private currentPath: Vec3[] = [];
|
||||||
@@ -59,10 +53,11 @@ export class PlayerController extends Component {
|
|||||||
private isWin: boolean = false; // 游戏是否胜利(到达终点)
|
private isWin: boolean = false; // 游戏是否胜利(到达终点)
|
||||||
private currentDirection: number = 5; // 当前玩家朝向:3表示左/上,5表示右/下,默认为5
|
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 hasWinTimes = 0
|
||||||
private readonly _tempVec3B: Vec3 = new Vec3();
|
|
||||||
|
|
||||||
// 道具列表
|
// 道具列表
|
||||||
private props: Node[] = [];
|
private props: Node[] = [];
|
||||||
@@ -225,7 +220,7 @@ export class PlayerController extends Component {
|
|||||||
if (!this.player || !this.pathfinder) return;
|
if (!this.player || !this.pathfinder) return;
|
||||||
|
|
||||||
// 停止当前移动
|
// 停止当前移动
|
||||||
this.stopMovement();
|
// this.stopMovement();
|
||||||
|
|
||||||
// 限制目标位置在地图边界内
|
// 限制目标位置在地图边界内
|
||||||
const clampedPos = this.clampPlayerPosition(worldPos);
|
const clampedPos = this.clampPlayerPosition(worldPos);
|
||||||
@@ -262,7 +257,8 @@ export class PlayerController extends Component {
|
|||||||
// 切换到对应的动画
|
// 切换到对应的动画
|
||||||
this.switchAnimation(animationName);
|
this.switchAnimation(animationName);
|
||||||
|
|
||||||
this.moveToNextWaypoint();
|
// 使用平滑路径移动
|
||||||
|
this.startSmoothPathMovement();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 限制玩家位置在地图边界内
|
// 限制玩家位置在地图边界内
|
||||||
@@ -374,12 +370,15 @@ export class PlayerController extends Component {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 停止当前的移动tween
|
||||||
|
if (this.moveTween) {
|
||||||
|
this.moveTween.stop();
|
||||||
|
this.moveTween = null;
|
||||||
|
}
|
||||||
|
|
||||||
const targetPos = this.currentPath[this.currentPathIndex];
|
const targetPos = this.currentPath[this.currentPathIndex];
|
||||||
const currentPos = this.player.position;
|
const currentPos = this.player.position;
|
||||||
|
|
||||||
|
|
||||||
// 计算移动距离和时间
|
// 计算移动距离和时间
|
||||||
const distance = Vec3.distance(currentPos, targetPos);
|
const distance = Vec3.distance(currentPos, targetPos);
|
||||||
const moveTime = distance / this.moveSpeed;
|
const moveTime = distance / this.moveSpeed;
|
||||||
@@ -388,10 +387,16 @@ export class PlayerController extends Component {
|
|||||||
|
|
||||||
// 记录目标位置用于方向判断
|
// 记录目标位置用于方向判断
|
||||||
this.lastTargetPosition.set(targetPos);
|
this.lastTargetPosition.set(targetPos);
|
||||||
|
this.lastPosition.set(currentPos);
|
||||||
|
|
||||||
// 使用缓动移动到目标位置
|
// 使用缓动移动到目标位置
|
||||||
tween(this.player)
|
this.moveTween = tween(this.player)
|
||||||
.to(moveTime, { position: targetPos }, {
|
.to(moveTime, { position: targetPos }, {
|
||||||
|
easing: 'linear', // 使用线性插值,保持匀速移动
|
||||||
|
onUpdate: (target: Node) => {
|
||||||
|
// 在移动过程中更新动画方向
|
||||||
|
this.updateMovementDirection(target.position);
|
||||||
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
this.currentPathIndex++;
|
this.currentPathIndex++;
|
||||||
this.moveToNextWaypoint();
|
this.moveToNextWaypoint();
|
||||||
@@ -400,10 +405,153 @@ export class PlayerController extends Component {
|
|||||||
.start();
|
.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() {
|
private stopMovement() {
|
||||||
|
// 停止当前的移动tween
|
||||||
|
if (this.moveTween) {
|
||||||
|
this.moveTween.stop();
|
||||||
|
this.moveTween = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.player) {
|
if (this.player) {
|
||||||
tween(this.player).stop();
|
tween(this.player).stop();
|
||||||
}
|
}
|
||||||
@@ -447,12 +595,6 @@ export class PlayerController extends Component {
|
|||||||
|
|
||||||
this.stopMovement();
|
this.stopMovement();
|
||||||
|
|
||||||
// this.scheduleOnce(() => {
|
|
||||||
// this.adjustPositionsForAttack(otherCollider.node);
|
|
||||||
// }, 0);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 获取玩家和怪物的生命值
|
// 获取玩家和怪物的生命值
|
||||||
const playerHpLabel = this.player.getChildByName('hp');
|
const playerHpLabel = this.player.getChildByName('hp');
|
||||||
const monsterHpLabel = otherCollider.node.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) {
|
private setNodeWorldPosition(node: Node, worldPos: Vec3) {
|
||||||
const parent = node.parent;
|
const parent = node.parent;
|
||||||
|
|||||||
@@ -134,8 +134,11 @@ export class TiledMapPathfinder extends Component {
|
|||||||
worldPath.push(worldPos);
|
worldPath.push(worldPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`找到路径,包含${worldPath.length}个点`);
|
// 应用路径平滑算法
|
||||||
return worldPath;
|
const smoothedPath = this.smoothPath(worldPath);
|
||||||
|
|
||||||
|
console.log(`找到路径,原始${worldPath.length}个点,平滑后${smoothedPath.length}个点`);
|
||||||
|
return smoothedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,4 +250,111 @@ export class TiledMapPathfinder extends Component {
|
|||||||
orientation: this.tiledMap.getMapOrientation()
|
orientation: this.tiledMap.getMapOrientation()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路径平滑算法,减少不必要的转折点
|
||||||
|
* 使用视线检查算法,移除可以直接到达的中间点
|
||||||
|
*/
|
||||||
|
private smoothPath(path: Vec3[]): Vec3[] {
|
||||||
|
if (path.length <= 2) {
|
||||||
|
return path; // 路径太短,不需要平滑
|
||||||
|
}
|
||||||
|
|
||||||
|
const smoothedPath: Vec3[] = [];
|
||||||
|
smoothedPath.push(path[0]); // 添加起点
|
||||||
|
|
||||||
|
let currentIndex = 0;
|
||||||
|
let targetIndex = 1;
|
||||||
|
|
||||||
|
while (targetIndex < path.length - 1) {
|
||||||
|
// 尝试找到从当前点可以直接到达的最远点
|
||||||
|
let farthestReachable = targetIndex;
|
||||||
|
|
||||||
|
for (let i = targetIndex + 1; i < path.length; i++) {
|
||||||
|
if (this.hasLineOfSight(path[currentIndex], path[i])) {
|
||||||
|
farthestReachable = i;
|
||||||
|
} else {
|
||||||
|
break; // 一旦发现不可达的点,停止搜索
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果找到了更远的可达点,跳过中间点
|
||||||
|
if (farthestReachable > targetIndex) {
|
||||||
|
currentIndex = farthestReachable;
|
||||||
|
smoothedPath.push(path[currentIndex]);
|
||||||
|
targetIndex = currentIndex + 1;
|
||||||
|
} else {
|
||||||
|
// 否则,添加当前目标点
|
||||||
|
smoothedPath.push(path[targetIndex]);
|
||||||
|
currentIndex = targetIndex;
|
||||||
|
targetIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保终点被添加
|
||||||
|
if (smoothedPath[smoothedPath.length - 1] !== path[path.length - 1]) {
|
||||||
|
smoothedPath.push(path[path.length - 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return smoothedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查两点之间是否有直接的视线(是否可以直线移动)
|
||||||
|
*/
|
||||||
|
private hasLineOfSight(start: Vec3, end: Vec3): boolean {
|
||||||
|
if (!this.pathfinding) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将世界坐标转换为瓦片坐标
|
||||||
|
const startTile = this.worldToTileCoordinate(start);
|
||||||
|
const endTile = this.worldToTileCoordinate(end);
|
||||||
|
|
||||||
|
// 使用Bresenham直线算法检查路径上的所有点
|
||||||
|
const points = this.getLinePoints(startTile.x, startTile.y, endTile.x, endTile.y);
|
||||||
|
|
||||||
|
for (const point of points) {
|
||||||
|
if (!this.pathfinding.isWalkable(point.x, point.y)) {
|
||||||
|
return false; // 路径上有障碍物
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // 路径上没有障碍物
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用Bresenham算法获取两点之间的所有点
|
||||||
|
*/
|
||||||
|
private getLinePoints(x0: number, y0: number, x1: number, y1: number): Vec2[] {
|
||||||
|
const points: Vec2[] = [];
|
||||||
|
const dx = Math.abs(x1 - x0);
|
||||||
|
const dy = Math.abs(y1 - y0);
|
||||||
|
const sx = x0 < x1 ? 1 : -1;
|
||||||
|
const sy = y0 < y1 ? 1 : -1;
|
||||||
|
let err = dx - dy;
|
||||||
|
|
||||||
|
let x = x0;
|
||||||
|
let y = y0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
points.push(new Vec2(x, y));
|
||||||
|
|
||||||
|
if (x === x1 && y === y1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const e2 = 2 * err;
|
||||||
|
if (e2 > -dy) {
|
||||||
|
err -= dy;
|
||||||
|
x += sx;
|
||||||
|
}
|
||||||
|
if (e2 < dx) {
|
||||||
|
err += dx;
|
||||||
|
y += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,5 +5,22 @@
|
|||||||
"width": 720,
|
"width": 720,
|
||||||
"height": 1334
|
"height": 1334
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"physics": {
|
||||||
|
"collisionGroups": [
|
||||||
|
{
|
||||||
|
"index": 1,
|
||||||
|
"name": "player"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index": 2,
|
||||||
|
"name": "npc"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"collisionMatrix": {
|
||||||
|
"0": 1,
|
||||||
|
"1": 4,
|
||||||
|
"2": 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user