@cc-component/cc-pathfinding 1.1.2 → 1.1.3

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,22 @@ 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
72
81
  /**
73
82
  * 让单位移动到目标格子
74
83
  * @param allowOverlap 为 true 时该单位进入"允许重叠"模式,
75
84
  * 与其他同样开启该开关的单位可以走到同一格(互相穿过,不再互相绕行/互相等待),
76
85
  * 与终点是否相同无关
77
- * @param moveDuration 本次移动的"单格移动时长",单位秒(越小越快);
78
- * 不传或传 <= 0 时使用 InitMovementController 配置的默认值
86
+ * @param moveDuration 单格移动时长,单位秒(越小越快);传了会同时更新该单位的速度设置,
87
+ * 不传时沿用 SetMoveDuration 设过的速度,都没设过才用 InitMovementController 配置的默认值
79
88
  * @param useBottomPosition 节点对齐方式:false(默认)用格子中心 GetMapPosition,
80
89
  * true 用格子底部 GetMapModel(...).bottom_positon
81
90
  */
@@ -14,6 +14,11 @@ export interface GridTween {
14
14
  /**
15
15
  * tween 工厂:外部注入后,移动控制器用它创建/停止 tween。
16
16
  * 未注入(null)时自动使用 cc 自带的 tween。
17
+ *
18
+ * 注意:工厂本身是长期复用的(控制器只持有一个),但每移动一格都会调用一次 tween(target)。
19
+ * 是否复用 tween 实例由实现决定:
20
+ * - cc 自带 Tween 不能复用(start 后内部动作会累积,官方也警告不要复用实例),必须每次新建;
21
+ * - 自定义实现若支持复用,可以缓存一个实例并在 to() 时自行重置后返回同一个对象。
17
22
  */
18
23
  export interface GridTweenFactory {
19
24
  /** 创建一个绑定在 target 上的 tween */
@@ -52,6 +57,12 @@ export class GridMovementController {
52
57
  private readonly retryDelay: number;
53
58
  private readonly moveDuration: number;
54
59
  private readonly pathState: PathGridStateManager;
60
+ /** SetMoveDuration 设置的"单位速度"(持久),优先级最高 */
61
+ private readonly unitMoveDurations = new Map<Node, number>();
62
+ /** 本次 MoveTo 传入的 moveDuration,SetMoveDuration 清除后回退到这里 */
63
+ private readonly callMoveDurations = new Map<Node, number>();
64
+ /** 正在播放的那一格的"重放"入口,用于改速度时立即生效 */
65
+ private readonly activeStepReplayers = new Map<Node, () => void>();
55
66
  private customTweenFactory: GridTweenFactory | null;
56
67
 
57
68
  constructor(private readonly scheduler: Component, options: GridMovementOptions = {}) {
@@ -71,6 +82,30 @@ export class GridMovementController {
71
82
  this.customTweenFactory = factory ?? null;
72
83
  }
73
84
 
85
+ /**
86
+ * 运行中设置某个单位的移动速度(单格移动时长,单位秒,越小越快),立即生效:
87
+ * 已经保存的旧速度会被替换,并且正在播放的那一格会按新速度从当前位置继续走,不会跳变。
88
+ *
89
+ * 传非法值(<= 0 / 非数字)则清除该单位的设置,按下面顺序回退:
90
+ * 1. 本次 PathfindingModule.MoveTo 传入的 moveDuration
91
+ * 2. 都没有才用 InitMovementController 配置的默认 moveDuration
92
+ */
93
+ setMoveDuration(playerNode: Node, moveDuration: number): void {
94
+ if (typeof moveDuration === 'number' && moveDuration > 0) {
95
+ this.unitMoveDurations.set(playerNode, moveDuration);
96
+ } else {
97
+ this.unitMoveDurations.delete(playerNode);
98
+ }
99
+ this.activeStepReplayers.get(playerNode)?.();
100
+ }
101
+
102
+ /** 获取某个单位当前实际生效的单格移动时长(秒) */
103
+ getMoveDuration(playerNode: Node): number {
104
+ return this.resolveMoveDuration(
105
+ this.unitMoveDurations.get(playerNode) ?? this.callMoveDurations.get(playerNode),
106
+ );
107
+ }
108
+
74
109
  /** 外部注入优先,取不到就回退到 cc 自带 tween */
75
110
  private get tweenFactory(): GridTweenFactory {
76
111
  return this.customTweenFactory ?? GridMovementController.SYSTEM_TWEEN_FACTORY;
@@ -125,6 +160,7 @@ export class GridMovementController {
125
160
  this.tweenFactory.stopAllByTarget(playerNode);
126
161
  this.clearPathfindingRetry(playerNode);
127
162
  this.clearMovementRetry(playerNode);
163
+ this.activeStepReplayers.delete(playerNode);
128
164
 
129
165
  const owner = this.movementOwners.get(playerNode);
130
166
  if (owner) {
@@ -151,6 +187,9 @@ export class GridMovementController {
151
187
  this.tweenFactory.stopAllByTarget(playerNode);
152
188
  });
153
189
  this.movementOwners.clear();
190
+ this.activeStepReplayers.clear();
191
+ this.unitMoveDurations.clear();
192
+ this.callMoveDurations.clear();
154
193
 
155
194
  for (const playerNode of this.movementRetryCallbacks.keys()) {
156
195
  this.clearPathfindingRetry(playerNode);
@@ -173,11 +212,21 @@ export class GridMovementController {
173
212
  this.movementOwners.set(playerNode, owner);
174
213
  const shareToken = this.getShareToken(allowOverlap);
175
214
  this.pathState.reserveCurrent(start, owner, shareToken);
176
- // 单格移动时长:本次指定优先,否则用控制器默认值
177
- const duration = this.resolveMoveDuration(moveDuration);
215
+ // 本次移动显式指定了时长就以它为准(覆盖之前 SetMoveDuration 的设置);
216
+ // 没指定就清掉本次记录,回退到"单位设置 → 控制器默认"
217
+ if (typeof moveDuration === 'number' && moveDuration > 0) {
218
+ this.unitMoveDurations.delete(playerNode);
219
+ this.callMoveDurations.set(playerNode, moveDuration);
220
+ } else {
221
+ this.callMoveDurations.delete(playerNode);
222
+ }
223
+ // 单格移动时长:每次播放时实时读取,保证 SetMoveDuration 改完立即生效
224
+ // 优先级:SetMoveDuration 设置值 → MoveTo 传入值 → 控制器默认值
225
+ const stepDuration = () => this.getMoveDuration(playerNode);
178
226
  const version = this.bumpVersion(playerNode);
179
227
  let previous = start;
180
228
  let index = 0;
229
+ let stepping = false;
181
230
 
182
231
  const reachedAttackDistance = () => this.isAttackDistance(previous, target, attackDistance);
183
232
  // 当前格子/目标格子无法继续前进时,重新尝试路径计算或等待下一轮继续。
@@ -202,6 +251,45 @@ export class GridMovementController {
202
251
 
203
252
  const scheduleRetry = () => this.scheduleMovementRetry(playerNode, retry);
204
253
 
254
+ /**
255
+ * 播放"当前格 → 下一格"这一段。
256
+ * 中途改速度时会被原样重放:从节点当前所在位置接着走向同一个目标格,
257
+ * 时长按剩余距离折算,不会跳变,也不会出现"剩 10% 路程却走满一整格时间"。
258
+ */
259
+ const runStep = (stopRunning = false) => {
260
+ if (version !== this.movementVersions.get(playerNode)) {
261
+ return;
262
+ }
263
+ if (stopRunning) {
264
+ this.tweenFactory.stopAllByTarget(playerNode);
265
+ }
266
+ const next = route[index];
267
+ const to = this.resolveCellPosition(next, useBottomPosition);
268
+ const total = Vec3.distance(this.resolveCellPosition(previous, useBottomPosition), to);
269
+ const remain = Vec3.distance(playerNode.worldPosition, to);
270
+ const ratio = total > 0 ? Math.min(1, remain / total) : 1;
271
+
272
+ stepping = true;
273
+ this.tweenFactory.tween(playerNode)
274
+ .to(stepDuration() * ratio, { worldPosition: to })
275
+ .call(() => {
276
+ stepping = false;
277
+ this.pathState.completeMove(previous, next, owner, shareToken);
278
+ this.positions.set(playerNode, { row: next.row, col: next.col });
279
+ previous = next;
280
+ index++;
281
+ moveNext();
282
+ })
283
+ .start();
284
+ };
285
+
286
+ // 供 setMoveDuration 立即生效:只有真的有一步在播放时才重放
287
+ this.activeStepReplayers.set(playerNode, () => {
288
+ if (stepping) {
289
+ runStep(true);
290
+ }
291
+ });
292
+
205
293
  const moveNext = () => {
206
294
  if (version !== this.movementVersions.get(playerNode)) {
207
295
  return;
@@ -213,6 +301,7 @@ export class GridMovementController {
213
301
  return;
214
302
  }
215
303
  this.clearMovementRetry(playerNode);
304
+ this.activeStepReplayers.delete(playerNode);
216
305
  callBack?.();
217
306
  return;
218
307
  }
@@ -229,6 +318,7 @@ export class GridMovementController {
229
318
  route.splice(0, route.length, ...newRoute);
230
319
  index = 0;
231
320
  } else if (reachedAttackDistance()) {
321
+ this.activeStepReplayers.delete(playerNode);
232
322
  callBack?.();
233
323
  return;
234
324
  } else {
@@ -238,16 +328,7 @@ export class GridMovementController {
238
328
  return;
239
329
  }
240
330
 
241
- this.tweenFactory.tween(playerNode)
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();
331
+ runStep();
251
332
  };
252
333
 
253
334
  moveNext();
@@ -45,6 +45,22 @@ 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
+ static GetMoveDuration(playerNode: Node): number {
61
+ return this.movementController?.getMoveDuration(playerNode) ?? 0;
62
+ }
63
+
48
64
  static SetPosition(playerNode: Node, position: { row: number, col: number }, useBottomPosition = false): void {
49
65
  this.movementController?.setPosition(playerNode, position, useBottomPosition);
50
66
  }
@@ -58,8 +74,8 @@ export class PathfindingModule {
58
74
  * allowOverlap:为 true 时该单位进入"允许重叠"模式,
59
75
  * 与其他同样开启该开关的单位可以走到同一格(互相穿过,不再互相绕行/互相等待),
60
76
  * 与终点是否相同无关;未开启的单位之间、以及开启与未开启之间仍保持互斥。
61
- * moveDuration:本次移动的"单格移动时长",单位秒(数字越小走得越快)。
62
- * 不传或传 <= 0 时使用 InitMovementController 配置的默认值。
77
+ * moveDuration:单格移动时长,单位秒(数字越小走得越快)。传了就同时更新该单位的速度设置;
78
+ * 不传时沿用 SetMoveDuration 设过的速度,都没设过才用 InitMovementController 配置的默认值。
63
79
  * useBottomPosition:节点对齐方式。false(默认)用格子中心 GetMapPosition,
64
80
  * true 用格子底部 GetMapModel(...).bottom_positon(角色脚底踩在格子底边)。
65
81
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cc-component/cc-pathfinding",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "engine": ">=3.8.6",
5
5
  "description": "系统组件添加常用扩展方法",
6
6
  "main": "index.ts",