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

@@ -3,223 +3,246 @@ const { ccclass, property } = _decorator;
// A*寻路节点
export class PathNode {
x: number;
y: number;
gCost: number = 0; // 从起点到当前点的实际代价
hCost: number = 0; // 从当前点到终点的启发式代价
fCost: number = 0; // 总代价 f = g + h
parent: PathNode | null = null;
walkable: boolean = true;
x: number;
y: number;
gCost: number = 0; // 从起点到当前点的实际代价
hCost: number = 0; // 从当前点到终点的启发式代价
fCost: number = 0; // 总代价 f = g + h
parent: PathNode | null = null;
walkable: boolean = true;
moveCost: number = 1.0; // 从父节点移动到此节点的代价
constructor(x: number, y: number, walkable: boolean = true) {
this.x = x;
this.y = y;
this.walkable = walkable;
}
constructor(x: number, y: number, walkable: boolean = true) {
this.x = x;
this.y = y;
this.walkable = walkable;
}
calculateFCost() {
this.fCost = this.gCost + this.hCost;
}
calculateFCost() {
this.fCost = this.gCost + this.hCost;
}
}
@ccclass('AStarPathfinding')
export class AStarPathfinding extends Component {
private grid: PathNode[][] = [];
private gridWidth: number = 0;
private gridHeight: number = 0;
private grid: PathNode[][] = [];
private gridWidth: number = 0;
private gridHeight: number = 0;
/**
* 初始化寻路网格
* @param width 网格宽度
* @param height 网格高度
* @param walkableData 可行走数据0表示不可行走1表示可行走
*/
initializeGrid(width: number, height: number, walkableData: number[][]) {
this.gridWidth = width;
this.gridHeight = height;
this.grid = [];
/**
* 初始化寻路网格
* @param width 网格宽度
* @param height 网格高度
* @param walkableData 可行走数据0表示不可行走1表示可行走
*/
initializeGrid(width: number, height: number, walkableData: number[][]) {
this.gridWidth = width;
this.gridHeight = height;
this.grid = [];
for (let x = 0; x < width; x++) {
this.grid[x] = [];
for (let y = 0; y < height; y++) {
const walkable = walkableData[y] && walkableData[y][x] === 1;
this.grid[x][y] = new PathNode(x, y, walkable);
}
}
for (let x = 0; x < width; x++) {
this.grid[x] = [];
for (let y = 0; y < height; y++) {
const walkable = walkableData[y] && walkableData[y][x] === 1;
this.grid[x][y] = new PathNode(x, y, walkable);
}
}
}
/**
* 使用A*算法寻找路径
* @param startX 起点X坐标
* @param startY 起点Y坐标
* @param targetX 终点X坐标
* @param targetY 终点Y坐标
* @returns 路径点数组,如果找不到路径返回空数组
*/
findPath(startX: number, startY: number, targetX: number, targetY: number): Vec2[] {
// 验证起点和终点是否有效
if (!this.isValidPosition(startX, startY) || !this.isValidPosition(targetX, targetY)) {
console.warn('起点或终点坐标无效');
return [];
}
/**
* 使用A*算法寻找路径
* @param startX 起点X坐标
* @param startY 起点Y坐标
* @param targetX 终点X坐标
* @param targetY 终点Y坐标
* @returns 路径点数组,如果找不到路径返回空数组
*/
findPath(startX: number, startY: number, targetX: number, targetY: number): Vec2[] {
// 验证起点和终点是否有效
if (!this.isValidPosition(startX, startY) || !this.isValidPosition(targetX, targetY)) {
console.warn('起点或终点坐标无效');
return [];
}
if (!this.grid[startX][startY].walkable || !this.grid[targetX][targetY].walkable) {
console.warn('起点或终点不可行走');
return [];
}
if (startX === targetX && startY === targetY) {
return [new Vec2(startX, startY)];
}
// 重置所有节点
this.resetNodes();
const startNode = this.grid[startX][startY];
const targetNode = this.grid[targetX][targetY];
const openSet: PathNode[] = [];
const closedSet: PathNode[] = [];
openSet.push(startNode);
while (openSet.length > 0) {
// 找到f值最小的节点
let currentNode = openSet[0];
for (let i = 1; i < openSet.length; i++) {
if (openSet[i].fCost < currentNode.fCost ||
(openSet[i].fCost === currentNode.fCost && openSet[i].hCost < currentNode.hCost)) {
currentNode = openSet[i];
}
}
// 从开放列表移除当前节点,添加到关闭列表
openSet.splice(openSet.indexOf(currentNode), 1);
closedSet.push(currentNode);
// 如果到达目标节点,重建路径
if (currentNode === targetNode) {
return this.retracePath(startNode, targetNode);
}
// 检查相邻节点
const neighbors = this.getNeighbors(currentNode);
for (const neighbor of neighbors) {
if (!neighbor.walkable || closedSet.indexOf(neighbor) !== -1) {
continue;
}
const newGCost = currentNode.gCost + this.getDistance(currentNode, neighbor);
if (newGCost < neighbor.gCost || openSet.indexOf(neighbor) === -1) {
neighbor.gCost = newGCost;
neighbor.hCost = this.getDistance(neighbor, targetNode);
neighbor.calculateFCost();
neighbor.parent = currentNode;
if (openSet.indexOf(neighbor) === -1) {
openSet.push(neighbor);
}
}
}
}
// 没有找到路径
return [];
if (!this.grid[startX][startY].walkable || !this.grid[targetX][targetY].walkable) {
console.warn('起点或终点不可行走');
return [];
}
/**
* 获取节点的相邻节点4方向
*/
private getNeighbors(node: PathNode): PathNode[] {
const neighbors: PathNode[] = [];
if (startX === targetX && startY === targetY) {
return [new Vec2(startX, startY)];
}
// 四个方向:上、下、左、右
const directions = [
{ x: 0, y: 1 }, // 上
{ x: 0, y: -1 }, // 下
{ x: -1, y: 0 }, // 左
{ x: 1, y: 0 } // 右
];
// 重置所有节点
this.resetNodes();
for (const dir of directions) {
const checkX = node.x + dir.x;
const checkY = node.y + dir.y;
const startNode = this.grid[startX][startY];
const targetNode = this.grid[targetX][targetY];
if (this.isValidPosition(checkX, checkY)) {
neighbors.push(this.grid[checkX][checkY]);
}
const openSet: PathNode[] = [];
const closedSet: PathNode[] = [];
openSet.push(startNode);
while (openSet.length > 0) {
// 找到f值最小的节点
let currentNode = openSet[0];
for (let i = 1; i < openSet.length; i++) {
if (openSet[i].fCost < currentNode.fCost ||
(openSet[i].fCost === currentNode.fCost && openSet[i].hCost < currentNode.hCost)) {
currentNode = openSet[i];
}
}
// 从开放列表移除当前节点,添加到关闭列表
openSet.splice(openSet.indexOf(currentNode), 1);
closedSet.push(currentNode);
// 如果到达目标节点,重建路径
if (currentNode === targetNode) {
return this.retracePath(startNode, targetNode);
}
// 检查相邻节点
const neighbors = this.getNeighbors(currentNode);
for (const neighbor of neighbors) {
if (!neighbor.walkable || closedSet.indexOf(neighbor) !== -1) {
continue;
}
return neighbors;
}
const newGCost = currentNode.gCost + neighbor.moveCost;
/**
* 获取两个节点之间的距离(曼哈顿距离)
*/
private getDistance(nodeA: PathNode, nodeB: PathNode): number {
const distX = Math.abs(nodeA.x - nodeB.x);
const distY = Math.abs(nodeA.y - nodeB.y);
return distX + distY;
}
if (newGCost < neighbor.gCost || openSet.indexOf(neighbor) === -1) {
neighbor.gCost = newGCost;
neighbor.hCost = this.getDistance(neighbor, targetNode);
neighbor.calculateFCost();
neighbor.parent = currentNode;
/**
* 重建路径
*/
private retracePath(startNode: PathNode, endNode: PathNode): Vec2[] {
const path: Vec2[] = [];
let currentNode = endNode;
while (currentNode !== startNode) {
path.push(new Vec2(currentNode.x, currentNode.y));
currentNode = currentNode.parent!;
if (openSet.indexOf(neighbor) === -1) {
openSet.push(neighbor);
}
}
path.push(new Vec2(startNode.x, startNode.y));
path.reverse();
return path;
}
}
/**
* 重置所有节点的寻路参数
*/
private resetNodes() {
for (let x = 0; x < this.gridWidth; x++) {
for (let y = 0; y < this.gridHeight; y++) {
const node = this.grid[x][y];
node.gCost = 0;
node.hCost = 0;
node.fCost = 0;
node.parent = null;
}
// 没有找到路径
return [];
}
/**
* 获取节点的相邻节点8方向
*/
private getNeighbors(node: PathNode): PathNode[] {
const neighbors: PathNode[] = [];
// 八个方向:上、下、左、右、左上、右上、左下、右下
const directions = [
{ x: 0, y: 1, cost: 1.0 }, // 上
{ x: 0, y: -1, cost: 1.0 }, // 下
{ x: -1, y: 0, cost: 1.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) {
const checkX = node.x + dir.x;
const checkY = node.y + dir.y;
if (this.isValidPosition(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);
}
}
/**
* 检查位置是否有效
*/
private isValidPosition(x: number, y: number): boolean {
return x >= 0 && x < this.gridWidth && y >= 0 && y < this.gridHeight;
return neighbors;
}
/**
* 获取两个节点之间的距离(欧几里得距离,支持对角线移动)
*/
private getDistance(nodeA: PathNode, nodeB: PathNode): number {
const distX = Math.abs(nodeA.x - nodeB.x);
const distY = Math.abs(nodeA.y - nodeB.y);
// 使用欧几里得距离,更适合对角线移动
// 对角线距离约为1.414直线距离为1
if (distX === distY) {
return distX * 1.414; // 对角线移动
} else {
return distX + distY; // 曼哈顿距离
}
}
/**
* 重建路径
*/
private retracePath(startNode: PathNode, endNode: PathNode): Vec2[] {
const path: Vec2[] = [];
let currentNode = endNode;
while (currentNode !== startNode) {
path.push(new Vec2(currentNode.x, currentNode.y));
currentNode = currentNode.parent!;
}
/**
* 设置节点的可行走状态
*/
setWalkable(x: number, y: number, walkable: boolean) {
if (this.isValidPosition(x, y)) {
this.grid[x][y].walkable = walkable;
}
}
path.push(new Vec2(startNode.x, startNode.y));
path.reverse();
/**
* 获取节点是否可行走
*/
isWalkable(x: number, y: number): boolean {
if (!this.isValidPosition(x, y)) {
return false;
}
return this.grid[x][y].walkable;
return path;
}
/**
* 重置所有节点的寻路参数
*/
private resetNodes() {
for (let x = 0; x < this.gridWidth; x++) {
for (let y = 0; y < this.gridHeight; y++) {
const node = this.grid[x][y];
node.gCost = 0;
node.hCost = 0;
node.fCost = 0;
node.parent = null;
}
}
}
/**
* 检查位置是否有效
*/
private isValidPosition(x: number, y: number): boolean {
return x >= 0 && x < this.gridWidth && y >= 0 && y < this.gridHeight;
}
/**
* 设置节点的可行走状态
*/
setWalkable(x: number, y: number, walkable: boolean) {
if (this.isValidPosition(x, y)) {
this.grid[x][y].walkable = walkable;
}
}
/**
* 获取节点是否可行走
*/
isWalkable(x: number, y: number): boolean {
if (!this.isValidPosition(x, y)) {
return false;
}
return this.grid[x][y].walkable;
}
}