@cc-component/cc-pathfinding 1.1.2 → 1.1.4
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.
|
@@ -69,13 +69,35 @@ declare global {
|
|
|
69
69
|
* true 用格子底部 GetMapModel(...).bottom_positon
|
|
70
70
|
*/
|
|
71
71
|
export function SetPosition(playerNode: Node, position: { row: number, col: number }, useBottomPosition?: boolean): void
|
|
72
|
+
/**
|
|
73
|
+
* 运行中设置单位移动速度(单格移动时长,秒,越小越快),立即生效:
|
|
74
|
+
* 正在播放的那一格按新速度从当前位置继续走完,不跳变
|
|
75
|
+
* @param moveDuration 传 <= 0 清除该单位设置,按以下顺序回退:
|
|
76
|
+
* 1) 本次 MoveTo 传入的 moveDuration;2) MoveTo 没传,才用 InitMovementController 配置的默认值
|
|
77
|
+
*/
|
|
78
|
+
export function SetMoveDuration(playerNode: Node, moveDuration: number): void
|
|
79
|
+
/** 获取单位当前的单格移动时长(秒) */
|
|
80
|
+
export function GetMoveDuration(playerNode: Node): number
|
|
81
|
+
/**
|
|
82
|
+
* 监听移动过程中的位置更新:tween 每更新一次节点坐标就回调一次,
|
|
83
|
+
* 适合外部在这里刷新节点层级(按 y 做深度排序)
|
|
84
|
+
* @param handler (playerNode, worldPosition),worldPosition 是复用的临时向量,仅在回调内有效;
|
|
85
|
+
* 传 null 取消监听
|
|
86
|
+
*/
|
|
87
|
+
export function SetMovementUpdateHandler(handler: ((playerNode: Node, worldPosition: Vec3) => void) | null): void
|
|
88
|
+
/**
|
|
89
|
+
* 批量监听移动中的位置更新(多个单位时推荐):无论多少个单位在移动,每帧只回调一次,
|
|
90
|
+
* 参数是本帧坐标发生变化的单位数组
|
|
91
|
+
* @param handler 传 null 取消监听;与 SetMovementUpdateHandler 二选一
|
|
92
|
+
*/
|
|
93
|
+
export function SetMovementFrameHandler(handler: ((playerNodes: Node[]) => void) | null): void
|
|
72
94
|
/**
|
|
73
95
|
* 让单位移动到目标格子
|
|
74
96
|
* @param allowOverlap 为 true 时该单位进入"允许重叠"模式,
|
|
75
97
|
* 与其他同样开启该开关的单位可以走到同一格(互相穿过,不再互相绕行/互相等待),
|
|
76
98
|
* 与终点是否相同无关
|
|
77
|
-
* @param moveDuration
|
|
78
|
-
*
|
|
99
|
+
* @param moveDuration 单格移动时长,单位秒(越小越快);传了会同时更新该单位的速度设置,
|
|
100
|
+
* 不传时沿用 SetMoveDuration 设过的速度,都没设过才用 InitMovementController 配置的默认值
|
|
79
101
|
* @param useBottomPosition 节点对齐方式:false(默认)用格子中心 GetMapPosition,
|
|
80
102
|
* true 用格子底部 GetMapModel(...).bottom_positon
|
|
81
103
|
*/
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Node } from 'cc';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 增量深度排序器(配合移动中的位置更新回调使用)。
|
|
5
|
+
*
|
|
6
|
+
* 为什么不用全量排序:上百个单位每帧对 parent.children 做一次 sort + 逐个 setSiblingIndex,
|
|
7
|
+
* 开销是 O(N log N) 且会打乱整个 children 数组;而实际每帧真正需要挪动的往往只有少数几个节点。
|
|
8
|
+
*
|
|
9
|
+
* 这里的做法是"插入式调整":只对动过的节点,与相邻兄弟逐个比较并交换,
|
|
10
|
+
* 直到它落在正确位置。大多数帧只需要 0~1 次 setSiblingIndex,
|
|
11
|
+
* 单次开销 O(移动的位数),与单位总数无关。
|
|
12
|
+
*/
|
|
13
|
+
export class DepthSorter {
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param getOrder 排序键:键越大越靠前显示(siblingIndex 越大,越后绘制、盖在上面)。
|
|
17
|
+
* 常见写法:(node) => -node.worldPosition.y —— y 越小(画面越靠下)越靠前。
|
|
18
|
+
*/
|
|
19
|
+
constructor(private readonly getOrder: (node: Node) => number) { }
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 把节点调整到正确的层级位置。位置已经正确时不做任何操作。
|
|
23
|
+
* @returns 是否发生过调整
|
|
24
|
+
*/
|
|
25
|
+
update(node: Node): boolean {
|
|
26
|
+
if (!node?.isValid) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const parent = node.parent;
|
|
30
|
+
if (!parent) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const order = this.getOrder(node);
|
|
35
|
+
const siblings = parent.children;
|
|
36
|
+
let index = node.getSiblingIndex();
|
|
37
|
+
const startIndex = index;
|
|
38
|
+
|
|
39
|
+
// 向前挪:前面的兄弟比自己更"靠前",就往后再换(index 变大 = 后绘制 = 盖在上面)
|
|
40
|
+
while (index < siblings.length - 1) {
|
|
41
|
+
const nextSibling = siblings[index + 1];
|
|
42
|
+
if (nextSibling === node || this.getOrder(nextSibling) >= order) {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
node.setSiblingIndex(index + 1);
|
|
46
|
+
index++;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 向后挪:后面的兄弟比自己更"靠后",就往前换
|
|
50
|
+
while (index > 0) {
|
|
51
|
+
const prevSibling = siblings[index - 1];
|
|
52
|
+
if (prevSibling === node || this.getOrder(prevSibling) <= order) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
node.setSiblingIndex(index - 1);
|
|
56
|
+
index--;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return index !== startIndex;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 批量调整:只对传入的(脏)节点做插入式调整 */
|
|
63
|
+
updateAll(nodes: Node[]): void {
|
|
64
|
+
for (const node of nodes) {
|
|
65
|
+
this.update(node);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -4,9 +4,15 @@ import { Cell } from './GridMap';
|
|
|
4
4
|
import { PathGridStateManager } from './PathGridStateManager';
|
|
5
5
|
import { PathfindingModule } from './PathfindingModule';
|
|
6
6
|
|
|
7
|
+
/** to() 的可选项(与 cc 的 ITweenOption 用法一致) */
|
|
8
|
+
export interface GridTweenOption {
|
|
9
|
+
/** 每帧回调:tween 更新完目标属性后触发 */
|
|
10
|
+
onUpdate?: (target: Node) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
7
13
|
/** 外部 tween 对象需要满足的最小接口(与 cc.Tween 用法一致) */
|
|
8
14
|
export interface GridTween {
|
|
9
|
-
to(duration: number, props: object): GridTween;
|
|
15
|
+
to(duration: number, props: object, opts?: GridTweenOption): GridTween;
|
|
10
16
|
call(callback: () => void): GridTween;
|
|
11
17
|
start(): GridTween;
|
|
12
18
|
}
|
|
@@ -14,6 +20,11 @@ export interface GridTween {
|
|
|
14
20
|
/**
|
|
15
21
|
* tween 工厂:外部注入后,移动控制器用它创建/停止 tween。
|
|
16
22
|
* 未注入(null)时自动使用 cc 自带的 tween。
|
|
23
|
+
*
|
|
24
|
+
* 注意:工厂本身是长期复用的(控制器只持有一个),但每移动一格都会调用一次 tween(target)。
|
|
25
|
+
* 是否复用 tween 实例由实现决定:
|
|
26
|
+
* - cc 自带 Tween 不能复用(start 后内部动作会累积,官方也警告不要复用实例),必须每次新建;
|
|
27
|
+
* - 自定义实现若支持复用,可以缓存一个实例并在 to() 时自行重置后返回同一个对象。
|
|
17
28
|
*/
|
|
18
29
|
export interface GridTweenFactory {
|
|
19
30
|
/** 创建一个绑定在 target 上的 tween */
|
|
@@ -22,6 +33,20 @@ export interface GridTweenFactory {
|
|
|
22
33
|
stopAllByTarget(target: Node): void;
|
|
23
34
|
}
|
|
24
35
|
|
|
36
|
+
/**
|
|
37
|
+
* 移动过程中"位置更新"的回调。tween 每更新一次节点坐标就触发一次,
|
|
38
|
+
* 常用于外部刷新节点层级(按 y 做深度排序,越靠下越靠前显示)。
|
|
39
|
+
* @param worldPosition 复用同一个临时向量,只在本次回调内有效,不要保存引用
|
|
40
|
+
*/
|
|
41
|
+
export type MovementUpdateHandler = (playerNode: Node, worldPosition: Vec3) => void;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 批量监听:多个单位同时移动时,每帧只回调一次,参数是"本帧坐标发生变化"的单位。
|
|
45
|
+
* 相比 MovementUpdateHandler(每个单位每帧各回调一次),把 N 次调用合并成 1 次,
|
|
46
|
+
* 排序等重活每帧只做一遍。单位越多收益越大。
|
|
47
|
+
*/
|
|
48
|
+
export type MovementFrameHandler = (playerNodes: Node[]) => void;
|
|
49
|
+
|
|
25
50
|
export interface GridMovementOptions {
|
|
26
51
|
/** 每次重试前的等待时间,单位秒 */
|
|
27
52
|
retryDelay?: number;
|
|
@@ -38,6 +63,8 @@ export interface GridMovementOptions {
|
|
|
38
63
|
export class GridMovementController {
|
|
39
64
|
/** 允许重叠的单位共用的共享令牌 */
|
|
40
65
|
private static readonly OVERLAP_TOKEN = 'overlap';
|
|
66
|
+
/** 位置更新回调里复用,避免每帧分配向量 */
|
|
67
|
+
private static readonly TMP_POSITION = new Vec3();
|
|
41
68
|
|
|
42
69
|
private readonly movementVersions = new Map<Node, number>();
|
|
43
70
|
private readonly movementOwners = new Map<Node, symbol>();
|
|
@@ -52,7 +79,20 @@ export class GridMovementController {
|
|
|
52
79
|
private readonly retryDelay: number;
|
|
53
80
|
private readonly moveDuration: number;
|
|
54
81
|
private readonly pathState: PathGridStateManager;
|
|
82
|
+
/** SetMoveDuration 设置的"单位速度"(持久),优先级最高 */
|
|
83
|
+
private readonly unitMoveDurations = new Map<Node, number>();
|
|
84
|
+
/** 本次 MoveTo 传入的 moveDuration,SetMoveDuration 清除后回退到这里 */
|
|
85
|
+
private readonly callMoveDurations = new Map<Node, number>();
|
|
86
|
+
/** 正在播放的那一格的"重放"入口,用于改速度时立即生效 */
|
|
87
|
+
private readonly activeStepReplayers = new Map<Node, () => void>();
|
|
55
88
|
private customTweenFactory: GridTweenFactory | null;
|
|
89
|
+
/** 移动中位置更新的外部回调(每单位每帧) */
|
|
90
|
+
private movementUpdateHandler: MovementUpdateHandler | null = null;
|
|
91
|
+
/** 移动中位置更新的外部回调(每帧合并一次) */
|
|
92
|
+
private movementFrameHandler: MovementFrameHandler | null = null;
|
|
93
|
+
/** 本帧坐标发生变化的单位,合并回调后清空 */
|
|
94
|
+
private readonly dirtyNodes = new Set<Node>();
|
|
95
|
+
private frameFlushScheduled = false;
|
|
56
96
|
|
|
57
97
|
constructor(private readonly scheduler: Component, options: GridMovementOptions = {}) {
|
|
58
98
|
// 允许外部按场景调整重试间隔和动画时长,默认值保持兼容。
|
|
@@ -71,6 +111,72 @@ export class GridMovementController {
|
|
|
71
111
|
this.customTweenFactory = factory ?? null;
|
|
72
112
|
}
|
|
73
113
|
|
|
114
|
+
/**
|
|
115
|
+
* 运行中设置某个单位的移动速度(单格移动时长,单位秒,越小越快),立即生效:
|
|
116
|
+
* 已经保存的旧速度会被替换,并且正在播放的那一格会按新速度从当前位置继续走,不会跳变。
|
|
117
|
+
*
|
|
118
|
+
* 传非法值(<= 0 / 非数字)则清除该单位的设置,按下面顺序回退:
|
|
119
|
+
* 1. 本次 PathfindingModule.MoveTo 传入的 moveDuration
|
|
120
|
+
* 2. 都没有才用 InitMovementController 配置的默认 moveDuration
|
|
121
|
+
*/
|
|
122
|
+
setMoveDuration(playerNode: Node, moveDuration: number): void {
|
|
123
|
+
if (typeof moveDuration === 'number' && moveDuration > 0) {
|
|
124
|
+
this.unitMoveDurations.set(playerNode, moveDuration);
|
|
125
|
+
} else {
|
|
126
|
+
this.unitMoveDurations.delete(playerNode);
|
|
127
|
+
}
|
|
128
|
+
this.activeStepReplayers.get(playerNode)?.();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 设置"移动中位置更新"回调:tween 每更新一次节点坐标就触发一次,
|
|
133
|
+
* 外部可据此刷新节点层级(深度排序)。传 null 取消监听。
|
|
134
|
+
*/
|
|
135
|
+
setMovementUpdateHandler(handler: MovementUpdateHandler | null): void {
|
|
136
|
+
this.movementUpdateHandler = handler ?? null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 设置批量监听:多个单位同时移动时每帧只回调一次(参数是本帧动过的单位)。
|
|
141
|
+
* 传 null 取消监听。与 setMovementUpdateHandler 二选一即可,不要两个都设。
|
|
142
|
+
*/
|
|
143
|
+
setMovementFrameHandler(handler: MovementFrameHandler | null): void {
|
|
144
|
+
this.movementFrameHandler = handler ?? null;
|
|
145
|
+
if (!this.movementFrameHandler) {
|
|
146
|
+
this.dirtyNodes.clear();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** 脏标记:只记录"动过",真正的回调合并到下一帧统一触发一次 */
|
|
151
|
+
private markDirty(playerNode: Node): void {
|
|
152
|
+
this.dirtyNodes.add(playerNode);
|
|
153
|
+
if (this.frameFlushScheduled) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
this.frameFlushScheduled = true;
|
|
157
|
+
this.scheduler.scheduleOnce(this.flushMovementFrame, 0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private readonly flushMovementFrame = (): void => {
|
|
161
|
+
this.frameFlushScheduled = false;
|
|
162
|
+
const handler = this.movementFrameHandler;
|
|
163
|
+
if (!handler) {
|
|
164
|
+
this.dirtyNodes.clear();
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const nodes: Node[] = [];
|
|
168
|
+
this.dirtyNodes.forEach(node => nodes.push(node));
|
|
169
|
+
this.dirtyNodes.clear();
|
|
170
|
+
handler(nodes);
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** 获取某个单位当前实际生效的单格移动时长(秒) */
|
|
174
|
+
getMoveDuration(playerNode: Node): number {
|
|
175
|
+
return this.resolveMoveDuration(
|
|
176
|
+
this.unitMoveDurations.get(playerNode) ?? this.callMoveDurations.get(playerNode),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
74
180
|
/** 外部注入优先,取不到就回退到 cc 自带 tween */
|
|
75
181
|
private get tweenFactory(): GridTweenFactory {
|
|
76
182
|
return this.customTweenFactory ?? GridMovementController.SYSTEM_TWEEN_FACTORY;
|
|
@@ -125,6 +231,7 @@ export class GridMovementController {
|
|
|
125
231
|
this.tweenFactory.stopAllByTarget(playerNode);
|
|
126
232
|
this.clearPathfindingRetry(playerNode);
|
|
127
233
|
this.clearMovementRetry(playerNode);
|
|
234
|
+
this.activeStepReplayers.delete(playerNode);
|
|
128
235
|
|
|
129
236
|
const owner = this.movementOwners.get(playerNode);
|
|
130
237
|
if (owner) {
|
|
@@ -145,12 +252,18 @@ export class GridMovementController {
|
|
|
145
252
|
|
|
146
253
|
/** 销毁控制器,并清理所有单位的动画、重试和格子占用状态。 */
|
|
147
254
|
destroy(): void {
|
|
255
|
+
this.scheduler.unschedule(this.flushMovementFrame);
|
|
256
|
+
this.frameFlushScheduled = false;
|
|
257
|
+
this.dirtyNodes.clear();
|
|
148
258
|
this.movementVersions.clear();
|
|
149
259
|
this.movementOwners.forEach((owner, playerNode) => {
|
|
150
260
|
this.pathState.release(owner);
|
|
151
261
|
this.tweenFactory.stopAllByTarget(playerNode);
|
|
152
262
|
});
|
|
153
263
|
this.movementOwners.clear();
|
|
264
|
+
this.activeStepReplayers.clear();
|
|
265
|
+
this.unitMoveDurations.clear();
|
|
266
|
+
this.callMoveDurations.clear();
|
|
154
267
|
|
|
155
268
|
for (const playerNode of this.movementRetryCallbacks.keys()) {
|
|
156
269
|
this.clearPathfindingRetry(playerNode);
|
|
@@ -173,11 +286,21 @@ export class GridMovementController {
|
|
|
173
286
|
this.movementOwners.set(playerNode, owner);
|
|
174
287
|
const shareToken = this.getShareToken(allowOverlap);
|
|
175
288
|
this.pathState.reserveCurrent(start, owner, shareToken);
|
|
176
|
-
//
|
|
177
|
-
|
|
289
|
+
// 本次移动显式指定了时长就以它为准(覆盖之前 SetMoveDuration 的设置);
|
|
290
|
+
// 没指定就清掉本次记录,回退到"单位设置 → 控制器默认"
|
|
291
|
+
if (typeof moveDuration === 'number' && moveDuration > 0) {
|
|
292
|
+
this.unitMoveDurations.delete(playerNode);
|
|
293
|
+
this.callMoveDurations.set(playerNode, moveDuration);
|
|
294
|
+
} else {
|
|
295
|
+
this.callMoveDurations.delete(playerNode);
|
|
296
|
+
}
|
|
297
|
+
// 单格移动时长:每次播放时实时读取,保证 SetMoveDuration 改完立即生效
|
|
298
|
+
// 优先级:SetMoveDuration 设置值 → MoveTo 传入值 → 控制器默认值
|
|
299
|
+
const stepDuration = () => this.getMoveDuration(playerNode);
|
|
178
300
|
const version = this.bumpVersion(playerNode);
|
|
179
301
|
let previous = start;
|
|
180
302
|
let index = 0;
|
|
303
|
+
let stepping = false;
|
|
181
304
|
|
|
182
305
|
const reachedAttackDistance = () => this.isAttackDistance(previous, target, attackDistance);
|
|
183
306
|
// 当前格子/目标格子无法继续前进时,重新尝试路径计算或等待下一轮继续。
|
|
@@ -202,6 +325,58 @@ export class GridMovementController {
|
|
|
202
325
|
|
|
203
326
|
const scheduleRetry = () => this.scheduleMovementRetry(playerNode, retry);
|
|
204
327
|
|
|
328
|
+
/**
|
|
329
|
+
* 播放"当前格 → 下一格"这一段。
|
|
330
|
+
* 中途改速度时会被原样重放:从节点当前所在位置接着走向同一个目标格,
|
|
331
|
+
* 时长按剩余距离折算,不会跳变,也不会出现"剩 10% 路程却走满一整格时间"。
|
|
332
|
+
*/
|
|
333
|
+
const runStep = (stopRunning = false) => {
|
|
334
|
+
if (version !== this.movementVersions.get(playerNode)) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (stopRunning) {
|
|
338
|
+
this.tweenFactory.stopAllByTarget(playerNode);
|
|
339
|
+
}
|
|
340
|
+
const next = route[index];
|
|
341
|
+
const to = this.resolveCellPosition(next, useBottomPosition);
|
|
342
|
+
const total = Vec3.distance(this.resolveCellPosition(previous, useBottomPosition), to);
|
|
343
|
+
const remain = Vec3.distance(playerNode.worldPosition, to);
|
|
344
|
+
const ratio = total > 0 ? Math.min(1, remain / total) : 1;
|
|
345
|
+
|
|
346
|
+
stepping = true;
|
|
347
|
+
// 有外部监听时才挂每帧回调;批量监听只做脏标记(一次 Set.add),排序等重活每帧合并一次
|
|
348
|
+
const updateHandler = this.movementUpdateHandler;
|
|
349
|
+
const needOnUpdate = updateHandler !== null || this.movementFrameHandler !== null;
|
|
350
|
+
const toOptions: GridTweenOption | undefined = needOnUpdate
|
|
351
|
+
? {
|
|
352
|
+
onUpdate: () => {
|
|
353
|
+
if (this.movementFrameHandler) {
|
|
354
|
+
this.markDirty(playerNode);
|
|
355
|
+
}
|
|
356
|
+
updateHandler?.(playerNode, playerNode.getWorldPosition(GridMovementController.TMP_POSITION));
|
|
357
|
+
},
|
|
358
|
+
}
|
|
359
|
+
: undefined;
|
|
360
|
+
this.tweenFactory.tween(playerNode)
|
|
361
|
+
.to(stepDuration() * ratio, { worldPosition: to }, toOptions)
|
|
362
|
+
.call(() => {
|
|
363
|
+
stepping = false;
|
|
364
|
+
this.pathState.completeMove(previous, next, owner, shareToken);
|
|
365
|
+
this.positions.set(playerNode, { row: next.row, col: next.col });
|
|
366
|
+
previous = next;
|
|
367
|
+
index++;
|
|
368
|
+
moveNext();
|
|
369
|
+
})
|
|
370
|
+
.start();
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// 供 setMoveDuration 立即生效:只有真的有一步在播放时才重放
|
|
374
|
+
this.activeStepReplayers.set(playerNode, () => {
|
|
375
|
+
if (stepping) {
|
|
376
|
+
runStep(true);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
205
380
|
const moveNext = () => {
|
|
206
381
|
if (version !== this.movementVersions.get(playerNode)) {
|
|
207
382
|
return;
|
|
@@ -213,6 +388,7 @@ export class GridMovementController {
|
|
|
213
388
|
return;
|
|
214
389
|
}
|
|
215
390
|
this.clearMovementRetry(playerNode);
|
|
391
|
+
this.activeStepReplayers.delete(playerNode);
|
|
216
392
|
callBack?.();
|
|
217
393
|
return;
|
|
218
394
|
}
|
|
@@ -229,6 +405,7 @@ export class GridMovementController {
|
|
|
229
405
|
route.splice(0, route.length, ...newRoute);
|
|
230
406
|
index = 0;
|
|
231
407
|
} else if (reachedAttackDistance()) {
|
|
408
|
+
this.activeStepReplayers.delete(playerNode);
|
|
232
409
|
callBack?.();
|
|
233
410
|
return;
|
|
234
411
|
} else {
|
|
@@ -238,16 +415,7 @@ export class GridMovementController {
|
|
|
238
415
|
return;
|
|
239
416
|
}
|
|
240
417
|
|
|
241
|
-
|
|
242
|
-
.to(duration, { worldPosition: this.resolveCellPosition(next, useBottomPosition) })
|
|
243
|
-
.call(() => {
|
|
244
|
-
this.pathState.completeMove(previous, next, owner, shareToken);
|
|
245
|
-
this.positions.set(playerNode, { row: next.row, col: next.col });
|
|
246
|
-
previous = next;
|
|
247
|
-
index++;
|
|
248
|
-
moveNext();
|
|
249
|
-
})
|
|
250
|
-
.start();
|
|
418
|
+
runStep();
|
|
251
419
|
};
|
|
252
420
|
|
|
253
421
|
moveNext();
|
|
@@ -45,6 +45,42 @@ export class PathfindingModule {
|
|
|
45
45
|
* 设置单位所在格子,并同步节点世界坐标。
|
|
46
46
|
* useBottomPosition:false(默认)用格子中心 GetMapPosition,true 用格子底部 bottom_positon。
|
|
47
47
|
*/
|
|
48
|
+
/**
|
|
49
|
+
* 运行中设置单位移动速度(单格移动时长,单位秒,越小越快),立即生效:
|
|
50
|
+
* 正在播放的那一格会按新速度从当前位置继续走完,不会跳变;后续格子也用新速度。
|
|
51
|
+
* 传 <= 0 则清除该单位的速度设置,按以下顺序回退:
|
|
52
|
+
* 1. 本次 PathfindingModule.MoveTo 传入的 moveDuration
|
|
53
|
+
* 2. MoveTo 也没传,才用 InitMovementController 配置的默认 moveDuration
|
|
54
|
+
*/
|
|
55
|
+
static SetMoveDuration(playerNode: Node, moveDuration: number): void {
|
|
56
|
+
this.movementController?.setMoveDuration(playerNode, moveDuration);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 监听"移动过程中位置更新":tween 每更新一次节点坐标就回调一次,
|
|
61
|
+
* 适合外部在这里刷新节点层级(按 y 做深度排序、越靠下越靠前显示)。
|
|
62
|
+
* 传 null 取消监听。
|
|
63
|
+
* @param handler (playerNode, worldPosition),worldPosition 是复用的临时向量,仅在回调内有效
|
|
64
|
+
*/
|
|
65
|
+
static SetMovementUpdateHandler(handler: ((playerNode: Node, worldPosition: Vec3) => void) | null): void {
|
|
66
|
+
this.movementController?.setMovementUpdateHandler(handler);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 批量监听"移动过程中位置更新"(推荐,多个单位时用它):
|
|
71
|
+
* 无论多少个单位在移动,每帧只回调一次,参数是本帧坐标发生变化的单位数组。
|
|
72
|
+
* 单位越多收益越大:N 次回调 / N 次排序 收敛为 1 次。
|
|
73
|
+
* 传 null 取消监听。与 SetMovementUpdateHandler 二选一,不要两个都设。
|
|
74
|
+
*/
|
|
75
|
+
static SetMovementFrameHandler(handler: ((playerNodes: Node[]) => void) | null): void {
|
|
76
|
+
this.movementController?.setMovementFrameHandler(handler);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 获取单位当前的单格移动时长(秒) */
|
|
80
|
+
static GetMoveDuration(playerNode: Node): number {
|
|
81
|
+
return this.movementController?.getMoveDuration(playerNode) ?? 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
48
84
|
static SetPosition(playerNode: Node, position: { row: number, col: number }, useBottomPosition = false): void {
|
|
49
85
|
this.movementController?.setPosition(playerNode, position, useBottomPosition);
|
|
50
86
|
}
|
|
@@ -58,8 +94,8 @@ export class PathfindingModule {
|
|
|
58
94
|
* allowOverlap:为 true 时该单位进入"允许重叠"模式,
|
|
59
95
|
* 与其他同样开启该开关的单位可以走到同一格(互相穿过,不再互相绕行/互相等待),
|
|
60
96
|
* 与终点是否相同无关;未开启的单位之间、以及开启与未开启之间仍保持互斥。
|
|
61
|
-
* moveDuration
|
|
62
|
-
*
|
|
97
|
+
* moveDuration:单格移动时长,单位秒(数字越小走得越快)。传了就同时更新该单位的速度设置;
|
|
98
|
+
* 不传时沿用 SetMoveDuration 设过的速度,都没设过才用 InitMovementController 配置的默认值。
|
|
63
99
|
* useBottomPosition:节点对齐方式。false(默认)用格子中心 GetMapPosition,
|
|
64
100
|
* true 用格子底部 GetMapModel(...).bottom_positon(角色脚底踩在格子底边)。
|
|
65
101
|
*/
|