Files
climb/assets/scripts/AStarPathfinding.ts

225 lines
6.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { _decorator, Component, Vec2 } from 'cc';
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;
constructor(x: number, y: number, walkable: boolean = true) {
this.x = x;
this.y = y;
this.walkable = walkable;
}
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;
/**
* 初始化寻路网格
* @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);
}
}
}
/**
* 使用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 [];
}
/**
* 获取节点的相邻节点4方向
*/
private getNeighbors(node: PathNode): PathNode[] {
const neighbors: PathNode[] = [];
// 四个方向:上、下、左、右
const directions = [
{ x: 0, y: 1 }, // 上
{ x: 0, y: -1 }, // 下
{ x: -1, y: 0 }, // 左
{ x: 1, y: 0 } // 右
];
for (const dir of directions) {
const checkX = node.x + dir.x;
const checkY = node.y + dir.y;
if (this.isValidPosition(checkX, checkY)) {
neighbors.push(this.grid[checkX][checkY]);
}
}
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);
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!;
}
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;
}
}
}
/**
* 检查位置是否有效
*/
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;
}
}