改进寻路系统,允许玩家从不可行走的当前位置开始寻路到可行走区域。 当玩家位于不可行走位置时,系统会自动寻找最近的可行走位置作为起点, 并临时将起点设置为可行走状态以启动A*算法。 主要变更: - AStarPathfinding: 临时修改起点可行走状态以支持算法启动 - PlayerController: 检测玩家当前位置并自动传送到最近可行走点 - TiledMapPathfinder: 在寻路前验证起点并寻找替代位置
263 lines
7.7 KiB
TypeScript
263 lines
7.7 KiB
TypeScript
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;
|
||
moveCost: number = 1.0; // 从父节点移动到此节点的代价
|
||
|
||
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[targetX][targetY].walkable) {
|
||
console.warn('终点不可行走');
|
||
return [];
|
||
}
|
||
|
||
// 注意:起点可能不可行走,我们在TiledMapPathfinder中已经处理了这种情况
|
||
// 这里不再检查起点是否可行走,允许从不可行走的起点开始寻路
|
||
|
||
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 originalStartWalkable = startNode.walkable;
|
||
startNode.walkable = true;
|
||
|
||
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) {
|
||
// 恢复起点的原始可行走状态
|
||
startNode.walkable = originalStartWalkable;
|
||
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 + neighbor.moveCost;
|
||
|
||
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);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 恢复起点的原始可行走状态
|
||
startNode.walkable = originalStartWalkable;
|
||
|
||
// 没有找到路径
|
||
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);
|
||
}
|
||
}
|
||
|
||
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!;
|
||
}
|
||
|
||
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;
|
||
}
|
||
} |