@cc-component/cc-pathfinding 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cc-ex-component.md +12 -0
- package/assets/pathfinding/Interface.ts +73 -0
- package/assets/pathfinding/Interface.ts.meta +9 -0
- package/assets/pathfinding/base/AStarPathfinder.ts +264 -0
- package/assets/pathfinding/base/AStarPathfinder.ts.meta +9 -0
- package/assets/pathfinding/base/Common.ts +342 -0
- package/assets/pathfinding/base/Common.ts.meta +9 -0
- package/assets/pathfinding/base/GridMap.ts +314 -0
- package/assets/pathfinding/base/GridMap.ts.meta +9 -0
- package/assets/pathfinding/base/GridMovementController.ts +257 -0
- package/assets/pathfinding/base/GridMovementController.ts.meta +9 -0
- package/assets/pathfinding/base/IMapData.ts +13 -0
- package/assets/pathfinding/base/IMapData.ts.meta +9 -0
- package/assets/pathfinding/base/IPlayerBuff.ts +1 -0
- package/assets/pathfinding/base/IPlayerBuff.ts.meta +9 -0
- package/assets/pathfinding/base/MapBoxView.ts +386 -0
- package/assets/pathfinding/base/MapBoxView.ts.meta +9 -0
- package/assets/pathfinding/base/MapViewModel.ts +17 -0
- package/assets/pathfinding/base/MapViewModel.ts.meta +9 -0
- package/assets/pathfinding/base/Mapitem.ts +38 -0
- package/assets/pathfinding/base/Mapitem.ts.meta +9 -0
- package/assets/pathfinding/base/PathGridStateManager.ts +94 -0
- package/assets/pathfinding/base/PathGridStateManager.ts.meta +9 -0
- package/assets/pathfinding/base/PathfindingModule.ts +161 -0
- package/assets/pathfinding/base/PathfindingModule.ts.meta +9 -0
- package/assets/pathfinding/base/prefab/map_root.prefab +16290 -0
- package/assets/pathfinding/base/prefab/map_root.prefab.meta +13 -0
- package/assets/pathfinding/base/prefab.meta +9 -0
- package/assets/pathfinding/base.meta +9 -0
- package/assets/pathfinding.meta +9 -0
- package/assets.meta +9 -0
- package/index.ts +9 -0
- package/index.ts.meta +9 -0
- package/package.json +19 -0
- package/package.json.meta +11 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Vec3 } from "cc";
|
|
2
|
+
import { MapViewModel } from "./base/MapViewModel";
|
|
3
|
+
import { AttackDistance } from "./base/Common";
|
|
4
|
+
import { MapBoxView } from "./base/MapBoxView";
|
|
5
|
+
import { Component } from "cc";
|
|
6
|
+
import { Node } from "cc";
|
|
7
|
+
|
|
8
|
+
declare module "cc" {
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
declare global {
|
|
12
|
+
namespace PathfindingModule {
|
|
13
|
+
/**开启日志打印 */
|
|
14
|
+
//export function Debug(enabled: boolean);
|
|
15
|
+
// 调用 A* 路径寻找,从 (1,1) 到 (5,5),attackDis: 攻击距离
|
|
16
|
+
export function TracePath(start: { row: number, col: number }, end: { row: number, col: number }, attackDis: AttackDistance, openColor?: boolean): { row: number, col: number }[]
|
|
17
|
+
// 占用某个节点
|
|
18
|
+
export function OccupyNode(row: number, col: number, openColor?: boolean)
|
|
19
|
+
// 判断节点是否被占用
|
|
20
|
+
export function IsOccupied(row: number, col: number): boolean
|
|
21
|
+
// 节点可以用
|
|
22
|
+
export function FreeNode(row: number, col: number)
|
|
23
|
+
|
|
24
|
+
// 获取指定行列位置的坐标
|
|
25
|
+
export function GetMapPosition(row: number, col: number): Vec3
|
|
26
|
+
|
|
27
|
+
// 获取指定位置的MapViewModel
|
|
28
|
+
export function CreateMapModel(row: number, col: number): MapViewModel
|
|
29
|
+
// 获取指定位置的MapViewModel
|
|
30
|
+
export function GetMapModel(row: number, col: number): MapViewModel
|
|
31
|
+
//获取所有的地图块
|
|
32
|
+
export function GetSeatModelList(): MapViewModel[][]
|
|
33
|
+
/*获取目标点周围最近的位置*/
|
|
34
|
+
export function CheckAvailableSurrounding(target: { row: number, col: number, dis: number })
|
|
35
|
+
|
|
36
|
+
/**在攻击距离内 */
|
|
37
|
+
export function IsAttackDistance(model1: MapViewModel, model2: MapViewModel, maxDistance: number): boolean
|
|
38
|
+
|
|
39
|
+
/**设置颜色 */
|
|
40
|
+
export function TargetColor(row, col, dis: number)
|
|
41
|
+
|
|
42
|
+
/**清除颜色所所有 */
|
|
43
|
+
export function ClearColor()
|
|
44
|
+
/**清除颜色-寻路路径 */
|
|
45
|
+
export function ClearColorPath()
|
|
46
|
+
|
|
47
|
+
/**初始化 */
|
|
48
|
+
export function Init(map_view: MapBoxView);
|
|
49
|
+
export function Destroy();
|
|
50
|
+
// 判断目标点是否被包围
|
|
51
|
+
export function IsClosedLoop(
|
|
52
|
+
targetRow: number,
|
|
53
|
+
targetCol: number, maxDistance: number
|
|
54
|
+
): boolean;
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
//#region 移动控制
|
|
58
|
+
/** 内部创建并绑定移动控制器 */
|
|
59
|
+
export function InitMovementController(scheduler: Component, options?: { retryDelay?: number; moveDuration?: number }): void
|
|
60
|
+
/** 设置单位位置 */
|
|
61
|
+
export function SetPosition(playerNode: Node, position: { row: number, col: number }): void
|
|
62
|
+
/** 让单位移动到目标格子 */
|
|
63
|
+
export function MoveTo(playerNode: Node, target: { row: number, col: number }, attackDistance: AttackDistance, openColor: boolean, callBack?: () => void): void
|
|
64
|
+
/** 取消移动 */
|
|
65
|
+
export function CancelMove(playerNode: Node, preserveCurrent?: boolean): void
|
|
66
|
+
/** 销毁移动控制器 */
|
|
67
|
+
export function DestroyMovement(): void
|
|
68
|
+
/**开启或关闭所有高亮颜色 */
|
|
69
|
+
export function SetColorVisible(visible: boolean)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// // 必须在 game.init() 之前设置!
|
|
73
|
+
//macro.ENABLE_TRANSPARENT_CANVAS = true;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { Cell, GridMap } from './GridMap';
|
|
2
|
+
|
|
3
|
+
/** A* 寻路节点 */
|
|
4
|
+
class PathNode {
|
|
5
|
+
constructor(
|
|
6
|
+
public row: number,
|
|
7
|
+
public col: number,
|
|
8
|
+
public g: number = 0, // 从起点到当前节点的实际代价
|
|
9
|
+
public h: number = 0, // 当前节点到终点的估计代价
|
|
10
|
+
public parent: PathNode | null = null // 父节点,用于回溯路径
|
|
11
|
+
) { }
|
|
12
|
+
|
|
13
|
+
/** f = g + h,值越小越优先扩展 */
|
|
14
|
+
get f(): number {
|
|
15
|
+
return this.g + this.h;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** 按 f 值组织的最小堆(开放列表,替代线性扫最小值) */
|
|
20
|
+
class MinHeap {
|
|
21
|
+
private readonly data: PathNode[] = [];
|
|
22
|
+
|
|
23
|
+
get size(): number {
|
|
24
|
+
return this.data.length;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
push(node: PathNode): void {
|
|
28
|
+
this.data.push(node);
|
|
29
|
+
this.siftUp(this.data.length - 1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
pop(): PathNode | undefined {
|
|
33
|
+
if (this.data.length === 0) {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
const top = this.data[0];
|
|
37
|
+
const last = this.data.pop();
|
|
38
|
+
if (this.data.length > 0 && last) {
|
|
39
|
+
this.data[0] = last;
|
|
40
|
+
this.siftDown(0);
|
|
41
|
+
}
|
|
42
|
+
return top;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private siftUp(index: number): void {
|
|
46
|
+
while (index > 0) {
|
|
47
|
+
const parent = (index - 1) >> 1;
|
|
48
|
+
if (this.data[index].f >= this.data[parent].f) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
[this.data[index], this.data[parent]] = [this.data[parent], this.data[index]];
|
|
52
|
+
index = parent;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
private siftDown(index: number): void {
|
|
57
|
+
const n = this.data.length;
|
|
58
|
+
while (true) {
|
|
59
|
+
let smallest = index;
|
|
60
|
+
const left = index * 2 + 1;
|
|
61
|
+
const right = index * 2 + 2;
|
|
62
|
+
if (left < n && this.data[left].f < this.data[smallest].f) {
|
|
63
|
+
smallest = left;
|
|
64
|
+
}
|
|
65
|
+
if (right < n && this.data[right].f < this.data[smallest].f) {
|
|
66
|
+
smallest = right;
|
|
67
|
+
}
|
|
68
|
+
if (smallest === index) {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
[this.data[index], this.data[smallest]] = [this.data[smallest], this.data[index]];
|
|
72
|
+
index = smallest;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A* 寻路算法层(纯逻辑,只依赖 GridMap)
|
|
79
|
+
*
|
|
80
|
+
* - 方向规则由 GridMap.getDirections 决定(六方向蜂窝 / 八方向)
|
|
81
|
+
* - 被占用的格子不能通行;目标格子例外(目标常是敌人,需要走到它身边)
|
|
82
|
+
* - 支持多目标搜索:一次 A* 命中任意目标即返回,天然按真实路径长度(g)扩展,
|
|
83
|
+
* 保证返回的是“实际走过去最近”的目标
|
|
84
|
+
*/
|
|
85
|
+
export class AStarPathfinder {
|
|
86
|
+
|
|
87
|
+
/** 转向惩罚:极小值,只用于等长路径的平局消解,不影响步数最优性 */
|
|
88
|
+
private static readonly TURN_PENALTY = 0.001;
|
|
89
|
+
|
|
90
|
+
constructor(private readonly grid: GridMap) { }
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* A* 寻路:返回起点到终点的路径(含起终点),找不到返回空数组。
|
|
94
|
+
* 单目标场景,等价于 findPathToNearest(start, [end])。
|
|
95
|
+
*/
|
|
96
|
+
findPath(start: Cell, end: Cell): Cell[] {
|
|
97
|
+
// 起点或终点不在地图内,直接失败
|
|
98
|
+
if (!this.grid.isWithinBounds(start.row, start.col) || !this.grid.isWithinBounds(end.row, end.col)) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
return this.search(start, [end]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 多目标 A*:一次搜索,命中 targets 中任意一个格子即返回。
|
|
106
|
+
* A* 按真实路径长度扩展,所以第一个命中的目标一定是“实际走过去最近”的那个,
|
|
107
|
+
* 比按直线距离逐个试 A* 更优,且只搜一次。
|
|
108
|
+
*/
|
|
109
|
+
findPathToNearest(start: Cell, targets: Cell[]): Cell[] {
|
|
110
|
+
const validTargets = targets.filter(t => this.grid.isWithinBounds(t.row, t.col));
|
|
111
|
+
return this.search(start, validTargets);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 找到“从 start 出发、能走到 target 身边”的路径:
|
|
116
|
+
* 优先直达,走不到再从小到大扩大搜索半径取最近可站点。
|
|
117
|
+
*
|
|
118
|
+
* 1. 终点本身可站(界内且未被占用):先直达终点,路径最直最短
|
|
119
|
+
* 2. 终点被占用(敌人站在终点上):直接走环圈,到身边最近点
|
|
120
|
+
* 3. 终点可站但不可达(被墙隔开):直达失败后落回环圈搜索
|
|
121
|
+
* 4. 目标被围住:半径 1 全被占 → 扩到半径 2 取围墙外最近空格;
|
|
122
|
+
* 若这一圈空格全部不可达(被墙圈出的“口袋”),继续扩大半径,
|
|
123
|
+
* 直到出现可达点或超出地图(返回空数组 = 整个区域都到不了)
|
|
124
|
+
*/
|
|
125
|
+
findPathToNearby(start: Cell, target: Cell, maxDistance: number = 1): Cell[] {
|
|
126
|
+
// 终点本身可站:优先直达,避免绕到终点旁边再折返
|
|
127
|
+
if (this.grid.isWithinBounds(target.row, target.col) && !this.grid.isOccupied(target.row, target.col)) {
|
|
128
|
+
const direct = this.search(start, [target]);
|
|
129
|
+
if (direct.length > 0) {
|
|
130
|
+
return direct;
|
|
131
|
+
}
|
|
132
|
+
// 直达不可达(被墙隔开),落回环圈搜索
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const maxRadius = Math.max(this.grid.rowCount, this.grid.colCount);
|
|
136
|
+
let radius = Math.max(1, maxDistance);
|
|
137
|
+
|
|
138
|
+
while (radius <= maxRadius) {
|
|
139
|
+
const availablePoints = this.grid.findAvailablePointsInRange(target.row, target.col, radius);
|
|
140
|
+
if (availablePoints.length > 0) {
|
|
141
|
+
const path = this.findPathToNearest(start, availablePoints);
|
|
142
|
+
if (path.length > 0) {
|
|
143
|
+
return path;
|
|
144
|
+
}
|
|
145
|
+
// 这一圈的空格都走不到,扩大半径再试
|
|
146
|
+
}
|
|
147
|
+
radius++;
|
|
148
|
+
}
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* A* 主循环(单目标/多目标共用):
|
|
154
|
+
* 返回起点到第一个命中目标的路径(含起终点),无可达目标返回空数组。
|
|
155
|
+
*
|
|
156
|
+
* 最优性保证:启发函数取“到最近目标的步数距离下界”(可采纳且一致),
|
|
157
|
+
* A* 按 g + h 从小到大扩展,第一个出堆的目标必为真实路径最短的目标。
|
|
158
|
+
*
|
|
159
|
+
* 路径质量(平局消解):步数相同的路径代价完全相等时,A* 会按入堆顺序
|
|
160
|
+
* 随机取一条,可能返回“之”字形等长路径。这里给 g 加一个极小的转向惩罚
|
|
161
|
+
* (TURN_PENALTY < 1 / 最大步数),不改变步数最优性,但保证步数相同时
|
|
162
|
+
* 优先选拐弯最少(最顺直)的那条。
|
|
163
|
+
*/
|
|
164
|
+
private search(start: Cell, targets: Cell[]): Cell[] {
|
|
165
|
+
if (targets.length === 0) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const targetKeys = new Set<string>();
|
|
170
|
+
for (const t of targets) {
|
|
171
|
+
targetKeys.add(`${t.row},${t.col}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 起点本身就在目标集合里:路径只有自己
|
|
175
|
+
if (targetKeys.has(`${start.row},${start.col}`)) {
|
|
176
|
+
return [{ row: start.row, col: start.col }];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 启发函数:到最近目标的步数距离下界(min 保持可采纳性)
|
|
180
|
+
const heuristic = (cell: Cell): number => {
|
|
181
|
+
let min = Infinity;
|
|
182
|
+
for (const t of targets) {
|
|
183
|
+
const d = this.grid.getStepDistance(cell, t);
|
|
184
|
+
if (d < min) {
|
|
185
|
+
min = d;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return min;
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// 开放列表:待扩展的节点(最小堆)
|
|
192
|
+
const openList = new MinHeap();
|
|
193
|
+
openList.push(new PathNode(start.row, start.col));
|
|
194
|
+
// 每个格子已发现的最小 g 值(用于去重,避免重复入队)
|
|
195
|
+
const bestG = new Map<string, number>();
|
|
196
|
+
// 闭合列表:已扩展完成的格子
|
|
197
|
+
const closed = new Set<string>();
|
|
198
|
+
|
|
199
|
+
while (openList.size > 0) {
|
|
200
|
+
// 1. 取出 f 值最小的节点
|
|
201
|
+
const current = openList.pop()!;
|
|
202
|
+
const curKey = `${current.row},${current.col}`;
|
|
203
|
+
if (closed.has(curKey)) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
closed.add(curKey);
|
|
207
|
+
|
|
208
|
+
// 2. 命中任意目标,回溯出完整路径
|
|
209
|
+
if (targetKeys.has(curKey)) {
|
|
210
|
+
const path: Cell[] = [];
|
|
211
|
+
let node: PathNode | null = current;
|
|
212
|
+
while (node !== null) {
|
|
213
|
+
path.unshift({ row: node.row, col: node.col });
|
|
214
|
+
node = node.parent;
|
|
215
|
+
}
|
|
216
|
+
return path;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 3. 扩展邻居
|
|
220
|
+
for (const [dRow, dCol] of this.grid.getDirections(current.row)) {
|
|
221
|
+
const newRow = current.row + dRow;
|
|
222
|
+
const newCol = current.col + dCol;
|
|
223
|
+
const key = `${newRow},${newCol}`;
|
|
224
|
+
|
|
225
|
+
if (!this.grid.isWithinBounds(newRow, newCol) || closed.has(key)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
// 被占用的格子不能走(目标格子例外)
|
|
229
|
+
if (this.grid.isOccupied(newRow, newCol) && !targetKeys.has(key)) {
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// 步数代价 + 极小转向惩罚:延续父节点的行进方向记 1,
|
|
234
|
+
// 换方向记 1 + TURN_PENALTY。总惩罚远小于 1 步,
|
|
235
|
+
// 不改变步数最优性,只在等长路径中优先拐弯最少(最顺直)的
|
|
236
|
+
let stepCost = 1;
|
|
237
|
+
if (current.parent) {
|
|
238
|
+
const keptDirection =
|
|
239
|
+
current.row - current.parent.row === dRow &&
|
|
240
|
+
current.col - current.parent.col === dCol;
|
|
241
|
+
if (!keptDirection) {
|
|
242
|
+
stepCost += AStarPathfinder.TURN_PENALTY;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const g = current.g + stepCost;
|
|
246
|
+
// 已经用更短的路径到达过该格子,跳过
|
|
247
|
+
if (bestG.has(key) && bestG.get(key)! <= g) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
bestG.set(key, g);
|
|
251
|
+
|
|
252
|
+
openList.push(new PathNode(
|
|
253
|
+
newRow, newCol,
|
|
254
|
+
g,
|
|
255
|
+
heuristic({ row: newRow, col: newCol }),
|
|
256
|
+
current
|
|
257
|
+
));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// 开放列表耗尽仍未命中任何目标:无可达路径
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
}
|