drgame-cc 1.0.49 → 1.0.52

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.
@@ -1,4 +1,6 @@
1
1
  import { KeyCode } from "../ad-sdk/ad-sdk";
2
+ import { FingerStyle } from "../Define";
3
+ import FocusVisualLayer from "./FocusVisualLayer";
2
4
 
3
5
  export interface FocusRuntimeItem {
4
6
  node: cc.Node;
@@ -25,8 +27,8 @@ interface ScopeInfo {
25
27
 
26
28
  export default class FocusManager {
27
29
  private static readonly FOCUS_SCALE_ACTION_TAG: number = 9911;
28
- private static readonly BASE_SCALE_X_KEY: string = "__tvFocusBaseScaleX";
29
- private static readonly BASE_SCALE_Y_KEY: string = "__tvFocusBaseScaleY";
30
+ private static readonly PERP_WEIGHT = 3; // 垂直方向惩罚权重
31
+ private static readonly BASE_SCALE_KEY: string = "__tvFocusBaseScale";
30
32
  private static items: FocusRuntimeItem[] = [];
31
33
  private static itemMap: { [uuid: string]: FocusRuntimeItem } = {};
32
34
  private static scopeMap: { [uuid: string]: string } = {};
@@ -35,43 +37,44 @@ export default class FocusManager {
35
37
  private static activeScopeId: string = "";
36
38
  private static listeners: FocusListener[] = [];
37
39
 
40
+ // ---- 视觉层管理 ----
41
+ private static visualLayer: FocusVisualLayer | null = null;
42
+ private static _focusStyle: FingerStyle = FingerStyle.Default;
43
+ private static _focusZIndex: number = 800;
44
+ private static _cursorVisible: boolean = true;
45
+
38
46
  private static readonly DEFAULT_FOCUS_SCALE = 1.08;
39
47
 
40
- public static register(target: cc.Node | any, options: { scopeId?: string; onFocus?: Function; onBlur?: Function; onClick?: Function; focusScale?: number; showCursor?: boolean; offset?: cc.Vec2 } = {}): void {
41
- let node = this.resolveNode(target);
48
+ public static register(node: cc.Node, options: { scopeId?: string; onFocus?: Function; onBlur?: Function; onClick?: Function; focusScale?: number; showCursor?: boolean; offset?: cc.Vec2 } = {}): void {
42
49
  if (!node) return;
43
- let component = this.resolveComponent(target, node);
44
50
  this.unregister(node);
45
51
 
46
- let uuid = node.uuid;
47
- this.getNodeBaseScale(node);
48
-
49
- let scopeId = options.scopeId || (component && component.scopeId) || this.activeScopeId;
50
- this.scopeMap[uuid] = scopeId;
52
+ let scopeId = options.scopeId || this.activeScopeId;
53
+ this.scopeMap[node.uuid] = scopeId;
51
54
 
52
55
  let item: FocusRuntimeItem = {
53
56
  node: node,
54
- onFocus: options.onFocus || (component && component.onFocus),
55
- onBlur: options.onBlur || (component && component.onBlur),
57
+ onFocus: options.onFocus,
58
+ onBlur: options.onBlur,
56
59
  onClick: options.onClick,
57
- focusScale: options.focusScale || (component && component.focusScale) || this.DEFAULT_FOCUS_SCALE,
60
+ focusScale: options.focusScale || this.DEFAULT_FOCUS_SCALE,
58
61
  showCursor: options.showCursor !== false,
59
62
  offset: options.offset || cc.v2(0, 0)
60
63
  };
61
64
 
65
+ this.storeBaseScale(node);
62
66
  this.items.push(item);
63
- this.itemMap[uuid] = item;
67
+ this.itemMap[node.uuid] = item;
64
68
  this.ensureFocus();
65
69
  }
66
70
 
67
- public static unregister(target: cc.Node | any) {
68
- let node = this.resolveNode(target);
71
+ public static unregister(node: cc.Node) {
69
72
  if (!node) return;
70
73
  delete this.scopeMap[node.uuid];
71
- for (let i = this.items.length - 1; i >= 0; i--) {
72
- if (this.items[i].node == node) {
73
- this.removeItemAt(i);
74
- }
74
+ let item = this.itemMap[node.uuid];
75
+ if (item) {
76
+ let idx = this.items.indexOf(item);
77
+ if (idx >= 0) this.removeItemAt(idx);
75
78
  }
76
79
  this.ensureFocus();
77
80
  }
@@ -79,39 +82,50 @@ export default class FocusManager {
79
82
  public static unregisterByRoot(root: cc.Node) {
80
83
  if (!root) return;
81
84
  for (let i = this.items.length - 1; i >= 0; i--) {
82
- let item = this.items[i];
83
- if (this.isChildOf(item.node, root)) {
84
- delete this.scopeMap[item.node.uuid];
85
- this.removeItemAt(i);
85
+ let cur = this.items[i].node;
86
+ while (cur) {
87
+ if (cur == root) {
88
+ delete this.scopeMap[this.items[i].node.uuid];
89
+ this.removeItemAt(i);
90
+ break;
91
+ }
92
+ cur = cur.parent;
86
93
  }
87
94
  }
88
95
  this.ensureFocus();
89
96
  }
90
97
 
91
- private static unregisterByUuid(uuid: string) {
92
- if (!uuid) return;
93
- for (let i = this.items.length - 1; i >= 0; i--) {
94
- if (this.items[i].node.uuid == uuid) {
95
- delete this.scopeMap[uuid];
96
- this.removeItemAt(i);
97
- }
98
+ private static storeBaseScale(node: cc.Node) {
99
+ let anyNode = node as any;
100
+ if (anyNode[this.BASE_SCALE_KEY] == null) {
101
+ anyNode[this.BASE_SCALE_KEY] = { x: node.scaleX, y: node.scaleY };
98
102
  }
99
103
  }
100
104
 
105
+ private static getBaseScale(node: cc.Node): { x: number, y: number } {
106
+ return ((node as any)[this.BASE_SCALE_KEY] || { x: 1, y: 1 });
107
+ }
108
+
101
109
  public static registerButtons(root: cc.Node) {
102
110
  if (!root) return;
103
111
  this.unregisterByRoot(root);
104
- this.walk(root, (node: cc.Node) => {
105
- if (node instanceof cc.Scene) return;
106
- let button = node.getComponent(cc.Button);
107
- if (!button) return;
108
- this.register(node, {
109
- scopeId: this.activeScopeId,
110
- focusScale: this.DEFAULT_FOCUS_SCALE,
111
- onClick: () => this.invokeButton(button)
112
- });
113
- });
114
-
112
+ let walk = (node: cc.Node) => {
113
+ // Scene 节点自身不注册,但必须遍历其子节点
114
+ if (!(node instanceof cc.Scene)) {
115
+ let button = node.getComponent(cc.Button);
116
+ if (button) {
117
+ this.register(node, {
118
+ scopeId: this.activeScopeId,
119
+ focusScale: this.DEFAULT_FOCUS_SCALE,
120
+ onClick: () => this.invokeButton(button)
121
+ });
122
+ }
123
+ }
124
+ for (let i = 0; i < node.childrenCount; i++) {
125
+ walk(node.children[i]);
126
+ }
127
+ };
128
+ walk(root);
115
129
  this.ensureFocus();
116
130
  }
117
131
 
@@ -184,10 +198,70 @@ export default class FocusManager {
184
198
  this.currentNodeUuid = "";
185
199
  this.scopes = [];
186
200
  this.activeScopeId = "";
201
+ if (this.visualLayer) {
202
+ this.visualLayer.dispose();
203
+ this.visualLayer = null;
204
+ }
187
205
  this.emitFocusChanged(null, old);
188
206
  }
189
207
 
190
- public static focus(target: string | cc.Node, silent: boolean = false): boolean {
208
+ // ========================================================================
209
+ // 视觉层管理(FocusVisualLayer 由 FocusManager 统一管理)
210
+ // ========================================================================
211
+
212
+ /** 显示/隐藏导航手指 */
213
+ public static setCursorVisible(visible: boolean): void {
214
+ this._cursorVisible = visible;
215
+ if (this.visualLayer) {
216
+ if (visible) {
217
+ const current = this.getCurrentFocusItem();
218
+ this.visualLayer.onFocusChanged(current);
219
+ } else {
220
+ this.visualLayer.hide();
221
+ }
222
+ }
223
+ }
224
+
225
+ /** 导航手指是否可见 */
226
+ public static isCursorVisible(): boolean {
227
+ return this._cursorVisible;
228
+ }
229
+
230
+ /** 初始化焦点视觉层 */
231
+ public static initVisualLayer(): void {
232
+ if (this.visualLayer) {
233
+ this.visualLayer.dispose();
234
+ this.visualLayer = null;
235
+ }
236
+ const scene = cc.director.getScene();
237
+ if (!scene) {
238
+ console.warn("[FocusManager] Cannot init visual layer: scene is null, will retry next frame");
239
+ setTimeout(() => this.initVisualLayer(), 0);
240
+ return;
241
+ }
242
+
243
+ this.visualLayer = new FocusVisualLayer(scene, { style: this._focusStyle, zIndex: this._focusZIndex });
244
+
245
+ // 创建后立即同步当前焦点状态
246
+ const current = this.getCurrentFocusItem();
247
+ if (current) {
248
+ this.visualLayer.onFocusChanged(current);
249
+ }
250
+ }
251
+
252
+ /** 设置焦点样式 */
253
+ public static setFocusStyle(style: FingerStyle): void {
254
+ this._focusStyle = style;
255
+ if (this.visualLayer) this.visualLayer.setStyle(style);
256
+ }
257
+
258
+ /** 设置焦点视觉层级 */
259
+ public static setFocusZIndex(z: number): void {
260
+ this._focusZIndex = z;
261
+ if (this.visualLayer) this.visualLayer.setZIndex(z);
262
+ }
263
+
264
+ public static focus(target: string | cc.Node): boolean {
191
265
  this.cleanup();
192
266
  if (!target) return false;
193
267
  let uuid = typeof target == "string" ? target : target.uuid;
@@ -196,11 +270,9 @@ export default class FocusManager {
196
270
 
197
271
  let previous: FocusRuntimeItem | null = this.getCurrentFocusItem();
198
272
  if (uuid == this.currentNodeUuid && uuid) {
199
- let current = this.getCurrentFocusItem();
200
- if (current) {
201
- this.setFocusVisual(current, true);
202
- // 总是触发位置更新,即使静默模式也要更新视觉位置
203
- this.emitFocusChanged(current, previous || current);
273
+ if (previous) {
274
+ this.setFocusVisual(previous, true);
275
+ this.emitFocusChanged(previous, previous);
204
276
  }
205
277
  return true;
206
278
  }
@@ -212,7 +284,6 @@ export default class FocusManager {
212
284
  if (scope) scope.lastNodeUuid = current.node.uuid;
213
285
  this.setFocusVisual(current, true);
214
286
  }
215
- // 总是触发位置更新,确保视觉层收到通知
216
287
  this.emitFocusChanged(current, previous);
217
288
  return true;
218
289
  }
@@ -225,24 +296,27 @@ export default class FocusManager {
225
296
  return !!this.getCurrentFocusItem();
226
297
  }
227
298
 
228
- let vector = this.getDirectionVector(direction);
229
299
  let currentCenter = this.getWorldCenter(current.node).add(current.offset);
230
300
  let best: FocusRuntimeItem | null = null;
231
- let bestScore = Number.MAX_VALUE;
301
+ let bestDist = Infinity;
232
302
 
233
303
  for (let i = 0; i < this.items.length; i++) {
234
304
  let item = this.items[i];
235
305
  if (item == current || !this.isItemActive(item) || !this.isInActiveScope(item)) continue;
306
+
236
307
  let center = this.getWorldCenter(item.node).add(item.offset);
237
308
  let dx = center.x - currentCenter.x;
238
309
  let dy = center.y - currentCenter.y;
239
- if (vector.x != 0 && dx * vector.x <= 0) continue;
240
- if (vector.y != 0 && dy * vector.y <= 0) continue;
241
- let primary = vector.x != 0 ? Math.abs(dx) : Math.abs(dy);
242
- let secondary = vector.x != 0 ? Math.abs(dy) : Math.abs(dx);
243
- let score = primary + secondary * 2;
244
- if (score < bestScore) {
245
- bestScore = score;
310
+
311
+ // 必须在目标方向一侧(Cocos Y 轴向上递增)
312
+ if (direction === KeyCode.Left && dx >= 0) continue;
313
+ if (direction === KeyCode.Right && dx <= 0) continue;
314
+ if (direction === KeyCode.Up && dy <= 0) continue; // 上方:dy > 0
315
+ if (direction === KeyCode.Down && dy >= 0) continue; // 下方:dy < 0
316
+
317
+ let dist = this.navDistance(dx, dy, direction);
318
+ if (dist < bestDist) {
319
+ bestDist = dist;
246
320
  best = item;
247
321
  }
248
322
  }
@@ -251,22 +325,33 @@ export default class FocusManager {
251
325
  this.focus(best.node.uuid);
252
326
  return true;
253
327
  }
254
- return this.moveByOrder(direction == "left" || direction == "down" ? -1 : 1);
328
+ return this.moveByOrder(direction === KeyCode.Left || direction === KeyCode.Down ? -1 : 1);
329
+ }
330
+
331
+ /** 方向加权距离:对垂直于移动方向的分量加重惩罚,导航更精准 */
332
+ private static navDistance(dx: number, dy: number, direction: KeyCode): number {
333
+ switch (direction) {
334
+ case KeyCode.Left:
335
+ case KeyCode.Right:
336
+ // 水平移动:垂直偏差 * 3
337
+ return Math.sqrt(dx * dx + dy * dy * this.PERP_WEIGHT);
338
+ case KeyCode.Up:
339
+ case KeyCode.Down:
340
+ // 垂直移动:水平偏差 * 3
341
+ return Math.sqrt(dx * dx * this.PERP_WEIGHT + dy * dy);
342
+ default:
343
+ return Math.sqrt(dx * dx + dy * dy);
344
+ }
255
345
  }
256
346
 
257
347
  public static confirm(): boolean {
258
- this.ensureFocus();
259
348
  let item = this.getCurrentFocusItem();
260
- if (!item) return false;
261
- if (item.onClick) {
262
- item.onClick();
263
- return true;
264
- }
265
- let focusItem = item.node.getComponent("FocusItem") as any;
266
- if (focusItem && focusItem.onConfirm) {
267
- focusItem.onConfirm();
268
- return true;
349
+ if (!item) {
350
+ this.ensureFocus();
351
+ item = this.getCurrentFocusItem();
352
+ if (!item) return false;
269
353
  }
354
+ if (item.onClick) return (item.onClick(), true);
270
355
  this.invokeButton(item.node.getComponent(cc.Button));
271
356
  return true;
272
357
  }
@@ -295,9 +380,8 @@ export default class FocusManager {
295
380
 
296
381
  private static setFocusVisual(item: FocusRuntimeItem, focused: boolean, immediate: boolean = false) {
297
382
  if (!item || !item.node || !item.node.isValid) return;
298
- this.setButtonSpriteVisual(item, focused);
299
383
  item.node.stopActionByTag(this.FOCUS_SCALE_ACTION_TAG);
300
- let baseScale = this.getNodeBaseScale(item.node);
384
+ let baseScale = this.getBaseScale(item.node);
301
385
  let targetScaleX = focused ? baseScale.x * item.focusScale : baseScale.x;
302
386
  let targetScaleY = focused ? baseScale.y * item.focusScale : baseScale.y;
303
387
  if (immediate) {
@@ -311,20 +395,6 @@ export default class FocusManager {
311
395
  if (!focused && item.onBlur) item.onBlur();
312
396
  }
313
397
 
314
- private static setButtonSpriteVisual(item: FocusRuntimeItem, focused: boolean) {
315
- let button = item.node.getComponent(cc.Button);
316
- if (!button || button.transition != cc.Button.Transition.SPRITE) return;
317
- let target = button.target || button.node;
318
- if (!target || !target.isValid) return;
319
- let sprite = target.getComponent(cc.Sprite);
320
- if (!sprite) return;
321
- let spriteFrame = focused ? (button.hoverSprite || button.pressedSprite) : button.normalSprite;
322
- if (spriteFrame) sprite.spriteFrame = spriteFrame;
323
- }
324
-
325
- private static _ensureFocusRetry: number = 0;
326
- private static readonly _ENSURE_FOCUS_MAX_RETRY = 3;
327
-
328
398
  private static ensureFocus() {
329
399
  this.cleanup();
330
400
  if (this.getCurrentFocusItem()) return;
@@ -334,37 +404,18 @@ export default class FocusManager {
334
404
  let item = this.items[i];
335
405
  if (this.isItemActive(item) && this.isInActiveScope(item)) {
336
406
  this.focus(item.node.uuid);
337
- this._ensureFocusRetry = 0;
338
407
  return;
339
408
  }
340
409
  }
341
- // 有注册的按钮但暂无活跃节点,可能是节点尚未完全初始化,延迟重试
342
- if (this.items.length > 0 && this._ensureFocusRetry < this._ENSURE_FOCUS_MAX_RETRY) {
343
- this._ensureFocusRetry++;
344
- setTimeout(() => {
345
- this.ensureFocus();
346
- }, 50);
347
- return;
348
- }
349
- // 超过重试次数/无注册项,清除焦点状态
350
- this._ensureFocusRetry = 0;
351
410
  this.currentNodeUuid = "";
352
411
  this.emitFocusChanged(null, null);
353
412
  }
354
413
 
355
414
  private static moveByOrder(step: number): boolean {
356
- let activeItems = [];
357
- for (let i = 0; i < this.items.length; i++) {
358
- if (this.isItemActive(this.items[i]) && this.isInActiveScope(this.items[i])) activeItems.push(this.items[i]);
359
- }
415
+ let activeItems = this.items.filter(i => this.isItemActive(i) && this.isInActiveScope(i));
360
416
  if (activeItems.length <= 0) return false;
361
- let index = 0;
362
- for (let i = 0; i < activeItems.length; i++) {
363
- if (activeItems[i].node.uuid == this.currentNodeUuid) {
364
- index = i;
365
- break;
366
- }
367
- }
417
+ let index = activeItems.findIndex(i => i.node.uuid == this.currentNodeUuid);
418
+ if (index < 0) index = 0;
368
419
  let next = (index + step + activeItems.length) % activeItems.length;
369
420
  this.focus(activeItems[next].node.uuid);
370
421
  return true;
@@ -412,13 +463,6 @@ export default class FocusManager {
412
463
  return cc.v2(box.x + box.width / 2, box.y + box.height / 2);
413
464
  }
414
465
 
415
- private static getDirectionVector(direction: KeyCode): cc.Vec2 {
416
- if (direction == KeyCode.Left) return cc.v2(-1, 0);
417
- if (direction == KeyCode.Right) return cc.v2(1, 0);
418
- if (direction == KeyCode.Up) return cc.v2(0, 1);
419
- return cc.v2(0, -1);
420
- }
421
-
422
466
  private static invokeButton(button: cc.Button) {
423
467
  if (!button || !button.node || !button.node.activeInHierarchy || !button.interactable) return;
424
468
  var event = new cc.Event.EventCustom(cc.Node.EventType.TOUCH_END, true);
@@ -434,64 +478,17 @@ export default class FocusManager {
434
478
  }
435
479
 
436
480
  private static emitFocusChanged(item: FocusRuntimeItem | null, previous?: FocusRuntimeItem | null) {
481
+ // 更新焦点视觉层(受 _cursorVisible 开关控制)
482
+ if (this.visualLayer) {
483
+ if (this._cursorVisible) {
484
+ this.visualLayer.onFocusChanged(item);
485
+ } else {
486
+ this.visualLayer.hide();
487
+ }
488
+ }
489
+ // 通知外部监听者
437
490
  for (let i = 0; i < this.listeners.length; i++) {
438
491
  this.listeners[i](item, previous);
439
492
  }
440
493
  }
441
-
442
- private static resolveNode(target: any): cc.Node | null {
443
- if (!target) return null;
444
- if (target instanceof cc.Node) return target;
445
- return target.node || null;
446
- }
447
-
448
- private static resolveComponent(target: any, node: cc.Node): any {
449
- if (!target || target instanceof cc.Node) return node.getComponent("FocusItem");
450
- return target;
451
- }
452
-
453
- private static getNodeBaseScale(node: cc.Node): { x: number, y: number } {
454
- let nodeAny: any = node as any;
455
- let storedX = nodeAny[this.BASE_SCALE_X_KEY];
456
- let storedY = nodeAny[this.BASE_SCALE_Y_KEY];
457
- if (this.isValidScale(storedX) && this.isValidScale(storedY)) {
458
- return { x: storedX, y: storedY };
459
- }
460
-
461
- let baseScale = {
462
- x: this.readScale(node, "scaleX"),
463
- y: this.readScale(node, "scaleY")
464
- };
465
- nodeAny[this.BASE_SCALE_X_KEY] = baseScale.x;
466
- nodeAny[this.BASE_SCALE_Y_KEY] = baseScale.y;
467
- return baseScale;
468
- }
469
-
470
- private static readScale(node: cc.Node, key: string): number {
471
- let nodeAny: any = node as any;
472
- let value = nodeAny[key];
473
- if (this.isValidScale(value)) return value;
474
- value = nodeAny.scale;
475
- return this.isValidScale(value) ? value : 1;
476
- }
477
-
478
- private static isValidScale(value: any): boolean {
479
- return typeof value == "number" && isFinite(value);
480
- }
481
-
482
- private static walk(node: cc.Node, visitor: (node: cc.Node) => void) {
483
- visitor(node);
484
- for (let i = 0; i < node.childrenCount; i++) {
485
- this.walk(node.children[i], visitor);
486
- }
487
- }
488
-
489
- private static isChildOf(node: cc.Node, root: cc.Node): boolean {
490
- let cur = node;
491
- while (cur) {
492
- if (cur == root) return true;
493
- cur = cur.parent;
494
- }
495
- return false;
496
- }
497
494
  }