@cc-component/cc-pathfinding 1.1.4 → 1.1.6
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.
|
@@ -4,7 +4,7 @@ import { AttackDistance } from "./base/Common";
|
|
|
4
4
|
import { MapBoxView } from "./base/MapBoxView";
|
|
5
5
|
import { Component } from "cc";
|
|
6
6
|
import { Node } from "cc";
|
|
7
|
-
import { GridTweenFactory } from "./base/GridMovementController";
|
|
7
|
+
import { DepthSortOptions, GridTweenFactory } from "./base/GridMovementController";
|
|
8
8
|
|
|
9
9
|
declare module "cc" {
|
|
10
10
|
|
|
@@ -57,7 +57,20 @@ declare global {
|
|
|
57
57
|
|
|
58
58
|
//#region 移动控制
|
|
59
59
|
/** 内部创建并绑定移动控制器 */
|
|
60
|
-
export function InitMovementController(scheduler: Component, options?: { retryDelay?: number; moveDuration?: number; tweenFactory?: GridTweenFactory }): void
|
|
60
|
+
export function InitMovementController(scheduler: Component, options?: { retryDelay?: number; moveDuration?: number; tweenFactory?: GridTweenFactory; depthSort?: boolean }): void
|
|
61
|
+
/**
|
|
62
|
+
* 开启 / 关闭深度排序
|
|
63
|
+
* @param setting 不传 → 内置插入式排序(默认按世界坐标 y,越小越靠前);
|
|
64
|
+
* 传 (node) => number → 内置排序 + 自定义排序键;
|
|
65
|
+
* 传 { handler: (nodes) => void } → 完全交给外部处理层级(一帧只回调一次)
|
|
66
|
+
*/
|
|
67
|
+
export function SetDepthSort(enabled: boolean, setting?: ((node: Node) => number) | DepthSortOptions): void
|
|
68
|
+
/**
|
|
69
|
+
* 一次性给一批节点设置层级(初始化、批量改完位置后调用一次)
|
|
70
|
+
* @param getOrder 排序键,键越大越靠前显示;不传则用 SetDepthSort 设过的键,
|
|
71
|
+
* 都没设过默认按世界坐标 y(y 越小越靠前)
|
|
72
|
+
*/
|
|
73
|
+
export function SetNodesLayer(nodes: Node[], getOrder?: (node: Node) => number): void
|
|
61
74
|
/**
|
|
62
75
|
* 注入自定义 tween 实现(比如换成项目自己的动画系统)
|
|
63
76
|
* @param factory 传 null 或从未调用时,自动使用 cc 自带 tween
|
|
@@ -12,11 +12,14 @@ import { Node } from 'cc';
|
|
|
12
12
|
*/
|
|
13
13
|
export class DepthSorter {
|
|
14
14
|
|
|
15
|
+
/** 默认排序键:y 越小(画面越靠下)越靠前显示 */
|
|
16
|
+
static readonly DEFAULT_ORDER = (node: Node): number => -node.worldPosition.y;
|
|
17
|
+
|
|
15
18
|
/**
|
|
16
19
|
* @param getOrder 排序键:键越大越靠前显示(siblingIndex 越大,越后绘制、盖在上面)。
|
|
17
|
-
*
|
|
20
|
+
* 默认按世界坐标 y:(node) => -node.worldPosition.y
|
|
18
21
|
*/
|
|
19
|
-
constructor(
|
|
22
|
+
constructor(readonly getOrder: (node: Node) => number = DepthSorter.DEFAULT_ORDER) { }
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* 把节点调整到正确的层级位置。位置已经正确时不做任何操作。
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Component, Node, Tween, tween, Vec3 } from 'cc';
|
|
2
2
|
import { AttackDistance } from './Common';
|
|
3
|
+
import { DepthSorter } from './DepthSorter';
|
|
3
4
|
import { Cell } from './GridMap';
|
|
4
5
|
import { PathGridStateManager } from './PathGridStateManager';
|
|
5
6
|
import { PathfindingModule } from './PathfindingModule';
|
|
@@ -47,6 +48,21 @@ export type MovementUpdateHandler = (playerNode: Node, worldPosition: Vec3) => v
|
|
|
47
48
|
*/
|
|
48
49
|
export type MovementFrameHandler = (playerNodes: Node[]) => void;
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* 深度排序配置。两种用法二选一(handler 优先):
|
|
53
|
+
* - getOrder:交给内置插入式排序,只传排序键,最省事;
|
|
54
|
+
* - handler:完全交给外部处理层级,内部只负责"脏标记 + 每帧合并成一次回调"。
|
|
55
|
+
*/
|
|
56
|
+
export interface DepthSortOptions {
|
|
57
|
+
/** 内置插入式排序的排序键:键越大越靠前显示(siblingIndex 越大) */
|
|
58
|
+
getOrder?: (node: Node) => number;
|
|
59
|
+
/** 外部处理器:每帧一次,参数是本帧坐标发生变化的单位 */
|
|
60
|
+
handler?: (nodes: Node[]) => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 直接传一个排序键函数,等价于 { getOrder } */
|
|
64
|
+
export type DepthSortSetting = ((node: Node) => number) | DepthSortOptions;
|
|
65
|
+
|
|
50
66
|
export interface GridMovementOptions {
|
|
51
67
|
/** 每次重试前的等待时间,单位秒 */
|
|
52
68
|
retryDelay?: number;
|
|
@@ -54,6 +70,11 @@ export interface GridMovementOptions {
|
|
|
54
70
|
moveDuration?: number;
|
|
55
71
|
/** 自定义 tween 实现,不传则用 cc 自带 tween */
|
|
56
72
|
tweenFactory?: GridTweenFactory;
|
|
73
|
+
/**
|
|
74
|
+
* 是否开启内置深度排序:移动中的单位每帧自动调整层级(只调整动过的节点)。
|
|
75
|
+
* 默认 false。排序键可通过 SetDepthSort 自定义。
|
|
76
|
+
*/
|
|
77
|
+
depthSort?: boolean;
|
|
57
78
|
}
|
|
58
79
|
|
|
59
80
|
/**
|
|
@@ -93,12 +114,21 @@ export class GridMovementController {
|
|
|
93
114
|
/** 本帧坐标发生变化的单位,合并回调后清空 */
|
|
94
115
|
private readonly dirtyNodes = new Set<Node>();
|
|
95
116
|
private frameFlushScheduled = false;
|
|
117
|
+
/** 内置深度排序器,开启后每帧自动调整动过的单位层级 */
|
|
118
|
+
private depthSorter: DepthSorter | null = null;
|
|
119
|
+
/** 外部深度排序处理器,设置后内置排序不生效 */
|
|
120
|
+
private depthSortHandler: ((nodes: Node[]) => void) | null = null;
|
|
121
|
+
/** 当前排序键:SetNodesLayer 在没有显式传 getOrder 时用它 */
|
|
122
|
+
private depthSortOrder: (node: Node) => number = DepthSorter.DEFAULT_ORDER;
|
|
96
123
|
|
|
97
124
|
constructor(private readonly scheduler: Component, options: GridMovementOptions = {}) {
|
|
98
125
|
// 允许外部按场景调整重试间隔和动画时长,默认值保持兼容。
|
|
99
126
|
this.retryDelay = options.retryDelay ?? 1;
|
|
100
127
|
this.moveDuration = options.moveDuration ?? 0.5;
|
|
101
128
|
this.customTweenFactory = options.tweenFactory ?? null;
|
|
129
|
+
if (options.depthSort) {
|
|
130
|
+
this.setDepthSort(true);
|
|
131
|
+
}
|
|
102
132
|
this.pathState = new PathGridStateManager({
|
|
103
133
|
isOccupied: (row, col) => PathfindingModule.IsOccupied(row, col),
|
|
104
134
|
occupy: (row, col) => PathfindingModule.OccupyNode(row, col, true),
|
|
@@ -147,6 +177,84 @@ export class GridMovementController {
|
|
|
147
177
|
}
|
|
148
178
|
}
|
|
149
179
|
|
|
180
|
+
/**
|
|
181
|
+
* 开启 / 关闭深度排序。
|
|
182
|
+
* @param setting 不传 → 内置插入式排序 + 默认键(世界坐标 y,越小越靠前);
|
|
183
|
+
* 传 (node) => number → 内置排序 + 自定义键;
|
|
184
|
+
* 传 { handler } → 完全交给外部处理层级(内部只做脏标记与每帧合并)。
|
|
185
|
+
*/
|
|
186
|
+
setDepthSort(enabled: boolean, setting?: DepthSortSetting): void {
|
|
187
|
+
this.depthSortOrder = this.resolveDepthSortOrder(setting);
|
|
188
|
+
if (!enabled) {
|
|
189
|
+
this.depthSorter = null;
|
|
190
|
+
this.depthSortHandler = null;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (typeof setting === 'function') {
|
|
194
|
+
this.depthSorter = new DepthSorter(setting);
|
|
195
|
+
this.depthSortHandler = null;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
this.depthSortHandler = setting?.handler ?? null;
|
|
199
|
+
// 有外部处理器时内置排序不生效,避免两套逻辑重复跑
|
|
200
|
+
this.depthSorter = this.depthSortHandler ? null : new DepthSorter(setting?.getOrder);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** 取出排序键:函数直接用,配置对象取 getOrder,都没有就用默认(世界坐标 y) */
|
|
204
|
+
private resolveDepthSortOrder(setting?: DepthSortSetting): (node: Node) => number {
|
|
205
|
+
if (typeof setting === 'function') {
|
|
206
|
+
return setting;
|
|
207
|
+
}
|
|
208
|
+
return setting?.getOrder ?? DepthSorter.DEFAULT_ORDER;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 一次性给一批节点设置层级(初始化、批量改动位置后调用)。
|
|
213
|
+
* 按父节点分组,只在这些节点当前占据的"槽位"之间按排序键重排,
|
|
214
|
+
* 不会打乱父节点下其它子节点(比如地图格子)的顺序。
|
|
215
|
+
*/
|
|
216
|
+
setNodesLayer(nodes: Node[], getOrder?: (node: Node) => number): void {
|
|
217
|
+
if (!nodes || nodes.length === 0) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const order = getOrder ?? this.depthSortOrder;
|
|
221
|
+
|
|
222
|
+
// 按父节点分组,跳过无效 / 无父节点的
|
|
223
|
+
const groups = new Map<Node, Node[]>();
|
|
224
|
+
for (const node of nodes) {
|
|
225
|
+
if (!node?.isValid || !node.parent) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const list = groups.get(node.parent);
|
|
229
|
+
if (list) {
|
|
230
|
+
list.push(node);
|
|
231
|
+
} else {
|
|
232
|
+
groups.set(node.parent, [node]);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
groups.forEach((list) => {
|
|
237
|
+
// 槽位 = 这批节点当前的 siblingIndex(升序),重排只发生在这些槽位之间
|
|
238
|
+
const slots = list.map(node => node.getSiblingIndex()).sort((a, b) => a - b);
|
|
239
|
+
// 排序键升序:键越小 index 越小(越靠后绘制)
|
|
240
|
+
list.sort((a, b) => order(a) - order(b));
|
|
241
|
+
// 按槽位从小到大写入,避免先写大下标导致后续位置错乱
|
|
242
|
+
for (let i = 0; i < list.length; i++) {
|
|
243
|
+
if (list[i].getSiblingIndex() !== slots[i]) {
|
|
244
|
+
list[i].setSiblingIndex(slots[i]);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** 是否需要在 tween 上挂每帧回调(外部监听 或 深度排序) */
|
|
251
|
+
private needsFrameUpdate(): boolean {
|
|
252
|
+
return this.movementUpdateHandler !== null
|
|
253
|
+
|| this.movementFrameHandler !== null
|
|
254
|
+
|| this.depthSorter !== null
|
|
255
|
+
|| this.depthSortHandler !== null;
|
|
256
|
+
}
|
|
257
|
+
|
|
150
258
|
/** 脏标记:只记录"动过",真正的回调合并到下一帧统一触发一次 */
|
|
151
259
|
private markDirty(playerNode: Node): void {
|
|
152
260
|
this.dirtyNodes.add(playerNode);
|
|
@@ -159,15 +267,16 @@ export class GridMovementController {
|
|
|
159
267
|
|
|
160
268
|
private readonly flushMovementFrame = (): void => {
|
|
161
269
|
this.frameFlushScheduled = false;
|
|
162
|
-
const handler = this.movementFrameHandler;
|
|
163
|
-
if (!handler) {
|
|
164
|
-
this.dirtyNodes.clear();
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
270
|
const nodes: Node[] = [];
|
|
168
271
|
this.dirtyNodes.forEach(node => nodes.push(node));
|
|
169
272
|
this.dirtyNodes.clear();
|
|
170
|
-
|
|
273
|
+
// 深度排序:有外部处理器就交给外部,否则用内置插入式排序
|
|
274
|
+
if (this.depthSortHandler) {
|
|
275
|
+
this.depthSortHandler(nodes);
|
|
276
|
+
} else {
|
|
277
|
+
this.depthSorter?.updateAll(nodes);
|
|
278
|
+
}
|
|
279
|
+
this.movementFrameHandler?.(nodes);
|
|
171
280
|
};
|
|
172
281
|
|
|
173
282
|
/** 获取某个单位当前实际生效的单格移动时长(秒) */
|
|
@@ -346,11 +455,11 @@ export class GridMovementController {
|
|
|
346
455
|
stepping = true;
|
|
347
456
|
// 有外部监听时才挂每帧回调;批量监听只做脏标记(一次 Set.add),排序等重活每帧合并一次
|
|
348
457
|
const updateHandler = this.movementUpdateHandler;
|
|
349
|
-
const needOnUpdate =
|
|
458
|
+
const needOnUpdate = this.needsFrameUpdate();
|
|
350
459
|
const toOptions: GridTweenOption | undefined = needOnUpdate
|
|
351
460
|
? {
|
|
352
461
|
onUpdate: () => {
|
|
353
|
-
if (this.movementFrameHandler) {
|
|
462
|
+
if (this.movementFrameHandler || this.depthSorter || this.depthSortHandler) {
|
|
354
463
|
this.markDirty(playerNode);
|
|
355
464
|
}
|
|
356
465
|
updateHandler?.(playerNode, playerNode.getWorldPosition(GridMovementController.TMP_POSITION));
|
|
@@ -4,7 +4,7 @@ import { Vec3 } from 'cc';
|
|
|
4
4
|
import { MapViewModel } from './MapViewModel';
|
|
5
5
|
import { Color } from 'cc';
|
|
6
6
|
import { AttackDistance } from './Common';
|
|
7
|
-
import { GridMovementController, GridTweenFactory } from './GridMovementController';
|
|
7
|
+
import { DepthSortOptions, GridMovementController, GridTweenFactory } from './GridMovementController';
|
|
8
8
|
const { ccclass, property } = _decorator;
|
|
9
9
|
|
|
10
10
|
@ccclass('PathfindingModule')
|
|
@@ -27,7 +27,7 @@ export class PathfindingModule {
|
|
|
27
27
|
this.movementController = controller;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
static InitMovementController(scheduler: Component, options?: { retryDelay?: number; moveDuration?: number; tweenFactory?: GridTweenFactory }): void {
|
|
30
|
+
static InitMovementController(scheduler: Component, options?: { retryDelay?: number; moveDuration?: number; tweenFactory?: GridTweenFactory; depthSort?: boolean }): void {
|
|
31
31
|
if (!this.movementController) {
|
|
32
32
|
this.movementController = new GridMovementController(scheduler, options);
|
|
33
33
|
}
|
|
@@ -66,6 +66,30 @@ export class PathfindingModule {
|
|
|
66
66
|
this.movementController?.setMovementUpdateHandler(handler);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/**
|
|
70
|
+
* 一次性给一批节点设置层级(初始化、批量改完位置后调用一次即可)。
|
|
71
|
+
* 按父节点分组,只在这些节点当前占据的槽位之间按排序键重排,
|
|
72
|
+
* 不会打乱父节点下其它子节点(比如地图格子)的顺序。
|
|
73
|
+
* @param getOrder 排序键,键越大越靠前显示;不传则用 SetDepthSort 设过的键,
|
|
74
|
+
* 都没设过默认按世界坐标 y(y 越小越靠前)
|
|
75
|
+
*/
|
|
76
|
+
static SetNodesLayer(nodes: Node[], getOrder?: (node: Node) => number): void {
|
|
77
|
+
this.movementController?.setNodesLayer(nodes, getOrder);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 开启 / 关闭深度排序(三种用法):
|
|
82
|
+
* 1. SetDepthSort(true) → 内置插入式排序,默认按世界坐标 y(y 越小越靠前)
|
|
83
|
+
* 2. SetDepthSort(true, (node) => -node.worldPosition.y) → 内置排序 + 自定义排序键
|
|
84
|
+
* 3. SetDepthSort(true, { handler: (nodes) => {...} }) → 完全交给外部处理层级,
|
|
85
|
+
* 内部只负责脏标记并把一帧内的多次变化合并成一次回调(nodes 是本帧动过的单位)
|
|
86
|
+
*
|
|
87
|
+
* 传 false 关闭。内置排序只调整本帧动过的节点,不做全量排序,单位再多也是 O(移动位数)。
|
|
88
|
+
*/
|
|
89
|
+
static SetDepthSort(enabled: boolean, setting?: ((node: Node) => number) | DepthSortOptions): void {
|
|
90
|
+
this.movementController?.setDepthSort(enabled, setting);
|
|
91
|
+
}
|
|
92
|
+
|
|
69
93
|
/**
|
|
70
94
|
* 批量监听"移动过程中位置更新"(推荐,多个单位时用它):
|
|
71
95
|
* 无论多少个单位在移动,每帧只回调一次,参数是本帧坐标发生变化的单位数组。
|