@esengine/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/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/source.iml +12 -0
- package/.idea/vcs.xml +6 -0
- package/.vscode/tasks.json +13 -0
- package/.wing/settings.json +3 -0
- package/README.md +245 -0
- package/bin/README.md +245 -0
- package/bin/modules/astar.js +559 -0
- package/bin/modules/astar.min.js +1 -0
- package/bin/modules/breadth-first.js +559 -0
- package/bin/modules/breadth-first.min.js +1 -0
- package/bin/package.json +54 -0
- package/bin/pathfinding.d.ts +167 -0
- package/bin/pathfinding.js +559 -0
- package/bin/pathfinding.min.js +1 -0
- package/gulpfile.js +137 -0
- package/lib/wxgame.d.ts +3945 -0
- package/package.json +52 -0
- package/src/AI/Pathfinding/AStar/AStarPathfinder.ts +244 -0
- package/src/AI/Pathfinding/AStar/AstarGridGraph.ts +183 -0
- package/src/AI/Pathfinding/AStar/IAstarGraph.ts +30 -0
- package/src/AI/Pathfinding/BreadthFirst/BreadthFirstPathfinder.ts +109 -0
- package/src/AI/Pathfinding/BreadthFirst/IUnweightedGraph.ts +14 -0
- package/src/AI/Pathfinding/BreadthFirst/UnweightedGraph.ts +29 -0
- package/src/AI/Pathfinding/BreadthFirst/UnweightedGridGraph.ts +81 -0
- package/src/Types/IVector2.ts +102 -0
- package/src/Utils/PriorityQueue.ts +121 -0
- package/src/index.ts +49 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { IVector2, Vector2Utils } from '../../../Types/IVector2';
|
|
2
|
+
import { IUnweightedGraph } from './IUnweightedGraph';
|
|
3
|
+
import { BreadthFirstPathfinder } from './BreadthFirstPathfinder';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 基本的无权网格图,用于广度优先搜索
|
|
7
|
+
* 适用于简单的网格寻路,如迷宫、推箱子等游戏
|
|
8
|
+
*/
|
|
9
|
+
export class UnweightedGridGraph implements IUnweightedGraph<IVector2> {
|
|
10
|
+
private static readonly CARDINAL_DIRS: IVector2[] = [
|
|
11
|
+
Vector2Utils.create(1, 0),
|
|
12
|
+
Vector2Utils.create(0, -1),
|
|
13
|
+
Vector2Utils.create(-1, 0),
|
|
14
|
+
Vector2Utils.create(0, 1)
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
private static readonly COMPASS_DIRS: IVector2[] = [
|
|
18
|
+
Vector2Utils.create(1, 0),
|
|
19
|
+
Vector2Utils.create(1, -1),
|
|
20
|
+
Vector2Utils.create(0, -1),
|
|
21
|
+
Vector2Utils.create(-1, -1),
|
|
22
|
+
Vector2Utils.create(-1, 0),
|
|
23
|
+
Vector2Utils.create(-1, 1),
|
|
24
|
+
Vector2Utils.create(0, 1),
|
|
25
|
+
Vector2Utils.create(1, 1),
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
public walls: IVector2[] = [];
|
|
29
|
+
|
|
30
|
+
private _width: number;
|
|
31
|
+
private _height: number;
|
|
32
|
+
private _dirs: IVector2[];
|
|
33
|
+
private _neighbors: IVector2[] = [];
|
|
34
|
+
|
|
35
|
+
constructor(width: number, height: number, allowDiagonalSearch: boolean = false) {
|
|
36
|
+
this._width = width;
|
|
37
|
+
this._height = height;
|
|
38
|
+
this._dirs = allowDiagonalSearch ? UnweightedGridGraph.COMPASS_DIRS : UnweightedGridGraph.CARDINAL_DIRS;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
public isNodeInBounds(node: IVector2): boolean {
|
|
42
|
+
return 0 <= node.x && node.x < this._width && 0 <= node.y && node.y < this._height;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public isNodePassable(node: IVector2): boolean {
|
|
46
|
+
return !this.walls.find(wall => Vector2Utils.equals(wall, node));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public getNeighbors(node: IVector2): IVector2[] {
|
|
50
|
+
this._neighbors.length = 0;
|
|
51
|
+
|
|
52
|
+
for (const dir of this._dirs) {
|
|
53
|
+
const next = Vector2Utils.add(node, dir);
|
|
54
|
+
if (this.isNodeInBounds(next) && this.isNodePassable(next)) {
|
|
55
|
+
this._neighbors.push(next);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return this._neighbors;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 搜索路径的便捷方法
|
|
64
|
+
* @param start 起始位置
|
|
65
|
+
* @param goal 目标位置
|
|
66
|
+
* @returns 路径数组,如果没找到则返回空数组
|
|
67
|
+
*/
|
|
68
|
+
public searchPath(start: IVector2, goal: IVector2): IVector2[] {
|
|
69
|
+
return BreadthFirstPathfinder.searchPath(this, start, goal);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 检查是否存在路径
|
|
74
|
+
* @param start 起始位置
|
|
75
|
+
* @param goal 目标位置
|
|
76
|
+
* @returns 是否存在路径
|
|
77
|
+
*/
|
|
78
|
+
public hasPath(start: IVector2, goal: IVector2): boolean {
|
|
79
|
+
return BreadthFirstPathfinder.search(this, start, goal);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用Vector2接口,兼容Cocos Creator和Laya引擎的Vector2类型
|
|
3
|
+
* 支持 cc.Vec2, Laya.Vector2 等
|
|
4
|
+
*/
|
|
5
|
+
export interface IVector2 {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 可比较的Vector2接口,用于路径查找算法
|
|
12
|
+
*/
|
|
13
|
+
export interface IComparableVector2 extends IVector2 {
|
|
14
|
+
/**
|
|
15
|
+
* 判断两个向量是否相等
|
|
16
|
+
*/
|
|
17
|
+
equals?(other: IVector2): boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Vector2工具类,提供通用的向量操作
|
|
22
|
+
*/
|
|
23
|
+
export class Vector2Utils {
|
|
24
|
+
// 哈希计算相关常量
|
|
25
|
+
private static readonly HASH_MULTIPLIER = 73856093;
|
|
26
|
+
private static readonly MAX_COORD = 32767;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 判断两个向量是否相等
|
|
30
|
+
*/
|
|
31
|
+
static equals(a: IVector2, b: IVector2): boolean {
|
|
32
|
+
if ((a as IComparableVector2).equals) {
|
|
33
|
+
return (a as IComparableVector2).equals!(b);
|
|
34
|
+
}
|
|
35
|
+
return a.x === b.x && a.y === b.y;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 创建一个新的Vector2对象
|
|
40
|
+
*/
|
|
41
|
+
static create(x: number, y: number): IVector2 {
|
|
42
|
+
return { x, y };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 复制Vector2
|
|
47
|
+
*/
|
|
48
|
+
static clone(vector: IVector2): IVector2 {
|
|
49
|
+
return { x: vector.x, y: vector.y };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 向量加法
|
|
54
|
+
*/
|
|
55
|
+
static add(a: IVector2, b: IVector2): IVector2 {
|
|
56
|
+
return { x: a.x + b.x, y: a.y + b.y };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 计算曼哈顿距离
|
|
61
|
+
*/
|
|
62
|
+
static manhattanDistance(a: IVector2, b: IVector2): number {
|
|
63
|
+
return Math.abs(a.x - b.x) + Math.abs(a.y - b.y);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 计算欧几里得距离
|
|
68
|
+
*/
|
|
69
|
+
static distance(a: IVector2, b: IVector2): number {
|
|
70
|
+
const dx = a.x - b.x;
|
|
71
|
+
const dy = a.y - b.y;
|
|
72
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 将Vector2转换为数值哈希键
|
|
77
|
+
* 使用位运算生成哈希值,支持坐标范围:-32767 到 32767
|
|
78
|
+
*/
|
|
79
|
+
static toHash(vector: IVector2): number {
|
|
80
|
+
const x = (vector.x + this.MAX_COORD) | 0;
|
|
81
|
+
const y = (vector.y + this.MAX_COORD) | 0;
|
|
82
|
+
return (x << 16) | y;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 将Vector2转换为字符串键
|
|
87
|
+
* 用于需要字符串键的场景
|
|
88
|
+
*/
|
|
89
|
+
static toKey(vector: IVector2): string {
|
|
90
|
+
return `${vector.x},${vector.y}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 从哈希值还原坐标
|
|
95
|
+
* 主要用于调试
|
|
96
|
+
*/
|
|
97
|
+
static fromHash(hash: number): IVector2 {
|
|
98
|
+
const x = (hash >> 16) - this.MAX_COORD;
|
|
99
|
+
const y = (hash & 0xFFFF) - this.MAX_COORD;
|
|
100
|
+
return { x, y };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 优先队列实现
|
|
3
|
+
* 使用二叉堆数据结构,时间复杂度:插入O(log n),删除O(log n)
|
|
4
|
+
*/
|
|
5
|
+
export interface IPriorityQueueNode {
|
|
6
|
+
priority: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class PriorityQueue<T extends IPriorityQueueNode> {
|
|
10
|
+
private _heap: T[] = [];
|
|
11
|
+
private _size = 0;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 队列中元素的数量
|
|
15
|
+
*/
|
|
16
|
+
get size(): number {
|
|
17
|
+
return this._size;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 队列是否为空
|
|
22
|
+
*/
|
|
23
|
+
get isEmpty(): boolean {
|
|
24
|
+
return this._size === 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 清空队列
|
|
29
|
+
*/
|
|
30
|
+
clear(): void {
|
|
31
|
+
this._heap.length = 0;
|
|
32
|
+
this._size = 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 入队
|
|
37
|
+
* @param item 要插入的元素
|
|
38
|
+
*/
|
|
39
|
+
enqueue(item: T): void {
|
|
40
|
+
this._heap[this._size] = item;
|
|
41
|
+
this._bubbleUp(this._size);
|
|
42
|
+
this._size++;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 出队,返回优先级最高(值最小)的元素
|
|
47
|
+
*/
|
|
48
|
+
dequeue(): T | undefined {
|
|
49
|
+
if (this._size === 0) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = this._heap[0];
|
|
54
|
+
this._size--;
|
|
55
|
+
|
|
56
|
+
if (this._size > 0) {
|
|
57
|
+
this._heap[0] = this._heap[this._size];
|
|
58
|
+
this._bubbleDown(0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 查看队首元素但不移除
|
|
66
|
+
*/
|
|
67
|
+
peek(): T | undefined {
|
|
68
|
+
return this._size > 0 ? this._heap[0] : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 向上冒泡
|
|
73
|
+
*/
|
|
74
|
+
private _bubbleUp(index: number): void {
|
|
75
|
+
while (index > 0) {
|
|
76
|
+
const parentIndex = Math.floor((index - 1) / 2);
|
|
77
|
+
if (this._heap[index].priority >= this._heap[parentIndex].priority) {
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
this._swap(index, parentIndex);
|
|
81
|
+
index = parentIndex;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 向下冒泡
|
|
87
|
+
*/
|
|
88
|
+
private _bubbleDown(index: number): void {
|
|
89
|
+
while (true) {
|
|
90
|
+
let minIndex = index;
|
|
91
|
+
const leftChild = 2 * index + 1;
|
|
92
|
+
const rightChild = 2 * index + 2;
|
|
93
|
+
|
|
94
|
+
if (leftChild < this._size &&
|
|
95
|
+
this._heap[leftChild].priority < this._heap[minIndex].priority) {
|
|
96
|
+
minIndex = leftChild;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (rightChild < this._size &&
|
|
100
|
+
this._heap[rightChild].priority < this._heap[minIndex].priority) {
|
|
101
|
+
minIndex = rightChild;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (minIndex === index) {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
this._swap(index, minIndex);
|
|
109
|
+
index = minIndex;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* 交换两个元素
|
|
115
|
+
*/
|
|
116
|
+
private _swap(i: number, j: number): void {
|
|
117
|
+
const temp = this._heap[i];
|
|
118
|
+
this._heap[i] = this._heap[j];
|
|
119
|
+
this._heap[j] = temp;
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 寻路算法库
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 导出类型定义
|
|
6
|
+
export { IVector2, IComparableVector2, Vector2Utils } from './Types/IVector2';
|
|
7
|
+
|
|
8
|
+
// 导出工具类
|
|
9
|
+
export { PriorityQueue, IPriorityQueueNode } from './Utils/PriorityQueue';
|
|
10
|
+
|
|
11
|
+
// 导出A*算法相关
|
|
12
|
+
export { IAstarGraph } from './AI/Pathfinding/AStar/IAstarGraph';
|
|
13
|
+
export { AStarPathfinder } from './AI/Pathfinding/AStar/AStarPathfinder';
|
|
14
|
+
export { AstarGridGraph } from './AI/Pathfinding/AStar/AstarGridGraph';
|
|
15
|
+
|
|
16
|
+
// 导出广度优先算法相关
|
|
17
|
+
export { IUnweightedGraph } from './AI/Pathfinding/BreadthFirst/IUnweightedGraph';
|
|
18
|
+
export { BreadthFirstPathfinder } from './AI/Pathfinding/BreadthFirst/BreadthFirstPathfinder';
|
|
19
|
+
export { UnweightedGraph } from './AI/Pathfinding/BreadthFirst/UnweightedGraph';
|
|
20
|
+
export { UnweightedGridGraph } from './AI/Pathfinding/BreadthFirst/UnweightedGridGraph';
|
|
21
|
+
|
|
22
|
+
// 使用示例和说明
|
|
23
|
+
/**
|
|
24
|
+
* 使用示例:
|
|
25
|
+
*
|
|
26
|
+
* // A*算法 - 适用于大部分游戏场景
|
|
27
|
+
* const astarGraph = new AstarGridGraph(10, 10);
|
|
28
|
+
* astarGraph.walls.push({ x: 5, y: 5 });
|
|
29
|
+
* const astarPath = astarGraph.searchPath({ x: 0, y: 0 }, { x: 9, y: 9 });
|
|
30
|
+
*
|
|
31
|
+
* // 广度优先算法 - 适用于简单网格,保证最短路径
|
|
32
|
+
* const bfsGraph = new UnweightedGridGraph(10, 10);
|
|
33
|
+
* bfsGraph.walls.push({ x: 5, y: 5 });
|
|
34
|
+
* const bfsPath = bfsGraph.searchPath({ x: 0, y: 0 }, { x: 9, y: 9 });
|
|
35
|
+
*
|
|
36
|
+
* // 自定义图结构
|
|
37
|
+
* const customGraph = new UnweightedGraph<IVector2>();
|
|
38
|
+
* customGraph.addEdgesForNode({ x: 0, y: 0 }, [{ x: 1, y: 0 }, { x: 0, y: 1 }]);
|
|
39
|
+
*
|
|
40
|
+
* // 在Cocos Creator中使用
|
|
41
|
+
* // const start = cc.v2(0, 0);
|
|
42
|
+
* // const goal = cc.v2(9, 9);
|
|
43
|
+
* // const path = astarGraph.searchPath(start, goal);
|
|
44
|
+
*
|
|
45
|
+
* // 在Laya中使用
|
|
46
|
+
* // const start = new Laya.Vector2(0, 0);
|
|
47
|
+
* // const goal = new Laya.Vector2(9, 9);
|
|
48
|
+
* // const path = astarGraph.searchPath(start, goal);
|
|
49
|
+
*/
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"experimentalDecorators": true,
|
|
4
|
+
"emitDecoratorMetadata": true,
|
|
5
|
+
"module": "es6",
|
|
6
|
+
"target": "es5",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"sourceMap": false,
|
|
9
|
+
"removeComments": true,
|
|
10
|
+
"outDir": "./bin",
|
|
11
|
+
"strict": false,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"forceConsistentCasingInFileNames": true,
|
|
14
|
+
"moduleResolution": "node",
|
|
15
|
+
"esModuleInterop": true,
|
|
16
|
+
"allowSyntheticDefaultImports": true,
|
|
17
|
+
"lib": [
|
|
18
|
+
"es5",
|
|
19
|
+
"dom",
|
|
20
|
+
"es2015.promise",
|
|
21
|
+
"es6"
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"include": [
|
|
25
|
+
"src/index.ts",
|
|
26
|
+
"src/Types/**/*",
|
|
27
|
+
"src/Utils/**/*",
|
|
28
|
+
"src/AI/Pathfinding/AStar/**/*",
|
|
29
|
+
"src/AI/Pathfinding/BreadthFirst/**/*"
|
|
30
|
+
],
|
|
31
|
+
"exclude": [
|
|
32
|
+
"node_modules"
|
|
33
|
+
]
|
|
34
|
+
}
|