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,7 +1,5 @@
1
1
  import { FingerStyle } from "../Define";
2
- import FocusManager, { FocusRuntimeItem } from "./FocusManager";
3
-
4
- const { ccclass } = cc._decorator;
2
+ import { FocusRuntimeItem } from "./FocusManager";
5
3
 
6
4
  const FOCUS_STYLE_MAP: { [key: string]: string } = {
7
5
  [FingerStyle.Default]: "focus_1",
@@ -10,140 +8,172 @@ const FOCUS_STYLE_MAP: { [key: string]: string } = {
10
8
  [FingerStyle.Blue]: "focus_4",
11
9
  };
12
10
 
13
- @ccclass
14
- export default class FocusVisualLayer extends cc.Component {
15
- private static _style: FingerStyle = FingerStyle.Default;
16
- private static _focusZIndex: number = 800;
17
-
18
- public static get style(): FingerStyle {
19
- return FocusVisualLayer._style;
20
- }
21
-
22
- public static setStyle(style: FingerStyle) {
23
- FocusVisualLayer._style = style;
24
- }
25
-
26
- public static setFocusZIndex(z: number) {
27
- FocusVisualLayer._focusZIndex = z;
28
- }
11
+ const CURSOR_SCALE = 1.5;
12
+ const MAX_RETRY = 3;
29
13
 
14
+ export default class FocusVisualLayer {
15
+ private node: cc.Node;
30
16
  private spineNode: cc.Node | null = null;
31
17
  private spine: sp.Skeleton | null = null;
32
- private currentItem: FocusRuntimeItem | null = null;
33
- private inited: boolean = false;
34
18
  private spineLoaded: boolean = false;
35
- private static _focusEnabled: boolean = true;
36
-
37
- public static create(parent: cc.Node): FocusVisualLayer {
38
- let node = parent.getChildByName("FocusVisualLayer");
39
- if (!node) {
40
- node = new cc.Node("FocusVisualLayer");
41
- parent.addChild(node, FocusVisualLayer._focusZIndex);
42
- cc.game?.addPersistRootNode?.(node);
43
- }
44
- let layer = node.getComponent(FocusVisualLayer);
45
- if (!layer) layer = node.addComponent(FocusVisualLayer);
46
- layer.init();
47
- return layer;
48
- }
19
+ private style: FingerStyle;
20
+ private lastItem: FocusRuntimeItem | null = null;
21
+ private retryCount: number = 0;
22
+ /** 是否已标记为销毁 */
23
+ private _disposed: boolean = false;
24
+
25
+ constructor(parent: cc.Node, options?: { style?: FingerStyle; zIndex?: number }) {
26
+ this.style = options?.style ?? FingerStyle.Default;
27
+ this.node = new cc.Node("FocusVisualLayer");
28
+
29
+ // anchor 设为 (0,0),position 为 (0,0),使本地坐标与"左下角原点"的世界坐标对齐
30
+ this.node.anchorX = 0;
31
+ this.node.anchorY = 0;
32
+ this.node.setPosition(0, 0);
33
+ parent.addChild(this.node, options?.zIndex ?? 800);
49
34
 
50
- onDestroy() {
51
- FocusManager.offFocusChanged(this.onFocusChanged);
35
+ this.buildView();
36
+ this.loadSpine();
52
37
  }
53
38
 
54
- public init() {
55
- if (this.inited) return;
56
- this.inited = true;
57
- this.node.zIndex = FocusVisualLayer._focusZIndex;
58
- this.resizeToParent();
59
- this.buildView();
39
+ setStyle(style: FingerStyle) {
40
+ this.style = style;
41
+ this.spineLoaded = false;
42
+ this.retryCount = 0;
60
43
  this.loadSpine();
61
- FocusManager.onFocusChanged(this.onFocusChanged);
62
- this.refresh(FocusManager.getCurrentFocusItem());
63
44
  }
64
45
 
65
- private onFocusChanged = (item: FocusRuntimeItem | null) => {
66
- this.refresh(item);
46
+ setZIndex(z: number) {
47
+ this.node.zIndex = z;
67
48
  }
68
49
 
69
- private buildView() {
70
- if (!this.spineNode) {
71
- this.spineNode = new cc.Node("FocusSpine");
72
- this.spineNode.zIndex = 2;
73
- this.spine = this.spineNode.addComponent(sp.Skeleton);
74
- this.spine.premultipliedAlpha = false;
75
- this.node.addChild(this.spineNode);
76
- this.spineNode.active = false;
50
+ dispose() {
51
+ this._disposed = true;
52
+ if (this.node && this.node.parent) {
53
+ this.node.removeFromParent(true);
77
54
  }
55
+ }
56
+
57
+ onFocusChanged(item: FocusRuntimeItem | null) {
58
+ this.lastItem = item;
59
+ if (!item || item.showCursor === false) {
60
+ this.hide();
61
+ return;
62
+ }
63
+ // 不管 spine 是否加载完成,都尝试更新位置
64
+ this.node.active = true;
65
+ this.updatePosition(item);
66
+ }
67
+
68
+ private buildView() {
69
+ this.spineNode = new cc.Node("FocusSpine");
70
+ this.spineNode.zIndex = 2;
71
+ // anchor 设为 (0.5, 0.5),以手指中心定位
72
+ this.spineNode.anchorX = 0.5;
73
+ this.spineNode.anchorY = 0.5;
74
+ this.spine = this.spineNode.addComponent(sp.Skeleton);
75
+ this.spine.premultipliedAlpha = false;
76
+ this.spine.defaultSkin = "default";
77
+ this.node.addChild(this.spineNode);
78
+ this.spineNode.active = false;
78
79
  this.node.active = false;
79
80
  }
80
81
 
81
82
  private getFocusPath(): string {
82
- let folder = FOCUS_STYLE_MAP[FocusVisualLayer._style] || "focus_1";
83
+ let folder = FOCUS_STYLE_MAP[this.style] || "focus_1";
83
84
  return `drres/focus/${folder}/gesture`;
84
85
  }
85
86
 
86
87
  private loadSpine() {
87
88
  let path = this.getFocusPath();
88
89
  cc.loader.loadRes(path, sp.SkeletonData, (error: Error, spData: sp.SkeletonData) => {
90
+ if (this._disposed) return;
91
+
89
92
  if (error || !this.spine) {
90
- console.warn("[FocusVisualLayer] Load spine failed:", error);
93
+ console.warn("[FocusVisualLayer] Load spine failed:", error ? error.message : "spine component is null");
94
+ if (this.retryCount < MAX_RETRY) {
95
+ this.retryCount++;
96
+ console.warn(`[FocusVisualLayer] Retry ${this.retryCount}/${MAX_RETRY}...`);
97
+ setTimeout(() => this.loadSpine(), 500);
98
+ } else {
99
+ console.error(`[FocusVisualLayer] Spine load exhausted retries, path=${path}`);
100
+ }
91
101
  return;
92
102
  }
93
103
  this.spine.skeletonData = spData;
94
104
  this.spineLoaded = true;
95
- if (this.currentItem) {
96
- this.refresh(this.currentItem);
105
+ this.retryCount = 0;
106
+
107
+ // Spine 加载完成后,如果有待处理的焦点,重新定位
108
+ if (this.lastItem && this.lastItem.showCursor !== false) {
109
+ this.node.active = true;
110
+ this.updatePosition(this.lastItem);
97
111
  }
98
112
  });
99
113
  }
100
114
 
101
- private refresh(item: FocusRuntimeItem | null) {
102
- this.currentItem = item;
103
- if (!item || item.showCursor === false) {
104
- this.hide();
105
- return;
106
- }
107
- if (!FocusVisualLayer._focusEnabled) {
108
- this.hide();
109
- return;
110
- }
111
- this.node.active = true;
112
- this.updatePosition(item);
113
- }
114
-
115
- private hide() {
115
+ hide() {
116
116
  this.node.active = false;
117
- this.currentItem = null;
118
117
  if (this.spineNode) this.spineNode.active = false;
119
118
  }
120
119
 
121
- private static readonly CURSOR_SCALE: number = 1.5;
122
-
123
120
  private updatePosition(item: FocusRuntimeItem) {
124
- if (!item || !item.node || !item.node.isValid || !item.node.activeInHierarchy) {
121
+ if (!item || !item.node || !item.node.isValid) {
125
122
  this.hide();
126
123
  return;
127
124
  }
125
+
126
+ // spine 未加载完成,记录 lastItem,等加载完成后再定位
128
127
  if (!this.spineLoaded || !this.spine || !this.spineNode) {
128
+ if (this.spineNode) this.spineNode.active = false;
129
129
  return;
130
130
  }
131
- this.resizeToParent();
132
- let box = item.node.getBoundingBoxToWorld();
133
- let bottomRight = cc.v2(box.x + box.width / 2 + 20, box.y + box.height / 2 - 20);
134
- let local = this.node.convertToNodeSpaceAR(bottomRight);
135
-
136
- this.spineNode.setPosition(local);
137
- this.spineNode.setScale(FocusVisualLayer.CURSOR_SCALE);
138
- this.spineNode.active = true;
139
- this.spine.setAnimation(0, "gesture", true);
140
- }
141
131
 
142
- private resizeToParent() {
143
- let parent = this.node.parent;
144
- let width = parent && parent.width ? parent.width : (cc.winSize && cc.winSize.width) || 1920;
145
- let height = parent && parent.height ? parent.height : (cc.winSize && cc.winSize.height) || 1080;
146
- this.node.setContentSize(width, height);
147
- this.node.setPosition(0, 0);
132
+ try {
133
+ let box = item.node.getBoundingBoxToWorld();
134
+ let bottomRight = cc.v2(box.x + box.width / 2 + 20, box.y + box.height / 2 - 20);
135
+ let local = this.node.convertToNodeSpaceAR(bottomRight);
136
+
137
+ this.spineNode.setPosition(local);
138
+ this.spineNode.setScale(CURSOR_SCALE);
139
+ this.spineNode.active = true;
140
+ this.spine.setAnimation(0, "gesture", true);
141
+ // // 获取焦点项的世界坐标位置: 用 getPosition 替代 getBoundingBoxToWorld
142
+ // // getBoundingBoxToWorld 在 scale 动画过程中可能返回不稳定的值
143
+ // let worldPos = item.node.convertToWorldSpaceAR(cc.v2(0, 0));
144
+ // let size = item.node.getContentSize();
145
+
146
+ // // 如果内容尺寸为 0,尝试从 Sprite/Button 获取尺寸
147
+ // if (size.width <= 0 || size.height <= 0) {
148
+ // let sprite = item.node.getComponent(cc.Sprite);
149
+ // if (sprite && sprite.spriteFrame) {
150
+ // let rect = sprite.spriteFrame.getRect();
151
+ // size = cc.size(rect.width, rect.height);
152
+ // } else {
153
+ // // 兜底:使用节点 scale 后的默认尺寸
154
+ // size = cc.size(100, 60);
155
+ // }
156
+ // }
157
+
158
+ // // 按实际 scale 缩放尺寸
159
+ // let scaleX = item.node.scaleX;
160
+ // let scaleY = item.node.scaleY;
161
+ // let w = size.width * Math.abs(scaleX || 1);
162
+ // let h = size.height * Math.abs(scaleY || 1);
163
+
164
+ // // 定位在焦点项的右上方(固定偏移 40px)
165
+ // let posX = worldPos.x + w / 2 + 40;
166
+ // let posY = worldPos.y + h / 2 + 40;
167
+
168
+ // this.spineNode.setPosition(posX, posY);
169
+ // this.spineNode.setScale(CURSOR_SCALE);
170
+ // this.spineNode.active = true;
171
+ // this.node.active = true;
172
+
173
+ // // 每次导航必须重新设置动画,确保 Spine 渲染更新
174
+ // this.spine.setAnimation(0, "gesture", true);
175
+ } catch (e) {
176
+ console.warn("[FocusVisualLayer] updatePosition error:", e);
177
+ }
148
178
  }
149
179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drgame-cc",
3
- "version": "1.0.49",
3
+ "version": "1.0.52",
4
4
  "description": "道然游戏适配包",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,17 +0,0 @@
1
-
2
- const { ccclass, property } = cc._decorator;
3
-
4
- @ccclass('BtnExtra')
5
- export class BtnExtra extends cc.Component {
6
- @property({
7
- type: cc.SpriteFrame,
8
- tooltip: '悬停状态图片'
9
- })
10
- hoverSprite: cc.SpriteFrame = null;
11
-
12
- @property({
13
- type: cc.SpriteFrame,
14
- tooltip: '普通状态图片'
15
- })
16
- normalSprite: cc.SpriteFrame = null;
17
- }
@@ -1,150 +0,0 @@
1
- import { ActionType, Reporter, IReportItem } from "./ad-sdk/ad-sdk";
2
- import { AnalyticsEvent, AnalyticsAction } from "./Define";
3
-
4
- const REPORT_URL = "https://sup.daoran.tv/bi-api/api/game/lewo/batch";
5
-
6
- /** 事件定义:element_position + 曝光/点击事件名 */
7
- const EVENT_MAP: Record<AnalyticsEvent, { ep: string; expose: string; click?: string }> = {
8
- [AnalyticsEvent.GameLoading]: { ep: "game_loading", expose: "game_loading_expose" },
9
- [AnalyticsEvent.StartGameButton]: { ep: "start_game_button", expose: "start_game_expose", click: "start_game_click" },
10
- [AnalyticsEvent.WatchVideoContinueButton]: { ep: "watch_video_continue_button", expose: "watch_video_continue_expose", click: "watch_video_continue_click" },
11
- [AnalyticsEvent.ReplayButton]: { ep: "replay_button", expose: "replay_expose", click: "replay_click" },
12
- [AnalyticsEvent.RewardVideoAd]: { ep: "reward_video_ad", expose: "ad_expose" },
13
- };
14
-
15
- export default class GameAnalytics {
16
- private static initialized: boolean = false;
17
- private static loadingReported: boolean = false;
18
- private static reportedExposures: { [event: string]: boolean } = {};
19
- private static reportInterval: number = 1000 * 30; // 30秒上报一次
20
- private static reportMaxSize: number = 50;
21
- private static reportKey = "game_analytics_report_cache";
22
-
23
- public static init(config: {
24
- app_item: string;
25
- content_type?: string;
26
- content_id?: string;
27
- content_title?: string;
28
- }): void {
29
- if (GameAnalytics.initialized) return;
30
-
31
- try {
32
- Reporter.init({
33
- report_url: REPORT_URL,
34
- app_item: config.app_item,
35
- content_type: config.content_type || "游戏",
36
- content_id: config.content_id || "",
37
- content_title: config.content_title || "",
38
- });
39
- GameAnalytics.initialized = true;
40
- var httpReq = new XMLHttpRequest();
41
- httpReq.open("POST", 'https://sup.daoran.tv/bi-api/api/config/get');
42
- httpReq.setRequestHeader("Content-Type", "application/json");
43
- httpReq.setRequestHeader("Accept", "application/json");
44
- httpReq.send();
45
- httpReq.onreadystatechange = () => {
46
- if (httpReq.readyState === 4 && httpReq.status === 200) {
47
- console.log("[GameAnalytics] report batch success", httpReq.responseText);
48
- }
49
- };
50
- setInterval(() => {
51
- this.reportBatchCache();
52
- }, this.reportInterval);
53
- } catch (error) {
54
- console.warn("[GameAnalytics] init failed", error);
55
- }
56
- }
57
-
58
- public static reportLoadingOnce(): void {
59
- if (this.loadingReported) return;
60
- this.loadingReported = true;
61
- this.report(AnalyticsEvent.GameLoading, AnalyticsAction.Exposure);
62
- }
63
-
64
- public static reportExposureOnce(event: AnalyticsEvent): void {
65
- if (this.reportedExposures[event]) return;
66
- this.reportedExposures[event] = true;
67
- this.report(event, AnalyticsAction.Exposure);
68
- }
69
-
70
- public static report(
71
- event: AnalyticsEvent,
72
- action: AnalyticsAction,
73
- adRequestId?: string
74
- ): void {
75
- if (!this.initialized) return;
76
- const def = EVENT_MAP[event];
77
- const eventName = action === AnalyticsAction.Exposure ? def.expose : def.click;
78
- if (!def || !eventName) {
79
- console.warn("[GameAnalytics] unsupported event/action", event, action);
80
- return;
81
- }
82
-
83
- try {
84
- const reportItem: any = {
85
- element_position: def.ep,
86
- action_type: action === AnalyticsAction.Exposure
87
- ? ActionType.Exposure
88
- : ActionType.Click,
89
- event_name: eventName,
90
- };
91
- if (event === AnalyticsEvent.RewardVideoAd && adRequestId) {
92
- reportItem.ad_request_id = adRequestId;
93
- }
94
- var reportCache: IReportItem[] = null;
95
- const cache = localStorage.getItem(this.reportKey);
96
- if (cache) {
97
- reportCache = JSON.parse(cache);
98
- }
99
- if (reportCache) {
100
- reportCache.push(reportItem);
101
- } else {
102
- reportCache = [reportItem];
103
- }
104
- localStorage.setItem(this.reportKey, JSON.stringify(reportCache));
105
- } catch (error) {
106
- console.warn("[GameAnalytics] report failed", error);
107
- }
108
- }
109
-
110
- public static reportBatch(events: AnalyticsEvent[]): void {
111
- if (!this.initialized || events.length === 0) return;
112
- try {
113
- const items: IReportItem[] = [];
114
- for (const e of events) {
115
- const def = EVENT_MAP[e];
116
- if (!def) continue;
117
- const item: any = {
118
- element_position: def.ep,
119
- action_type: ActionType.Exposure,
120
- event_name: def.expose,
121
- ad_request_id: "",
122
- };
123
- items.push(item);
124
- }
125
- if (items.length > 0) Reporter.reportBatch(items);
126
- } catch (error) {
127
- console.warn("[GameAnalytics] report failed", error);
128
- }
129
- }
130
-
131
- private static reportBatchCache(): void {
132
- if (!this.initialized) return;
133
- var reportCache: IReportItem[] = null;
134
- const cache = localStorage.getItem(this.reportKey);
135
- if (cache) {
136
- reportCache = JSON.parse(cache);
137
- }
138
- if (!reportCache || reportCache.length === 0) return;
139
-
140
- try {
141
- const reportItem = reportCache.splice(0, this.reportMaxSize);
142
- Reporter.reportBatch(reportItem);
143
- } catch (error) {
144
- console.warn("[GameAnalytics] report failed", error);
145
- reportCache = [];
146
- } finally {
147
- localStorage.setItem(this.reportKey, JSON.stringify(reportCache));
148
- }
149
- }
150
- }
@@ -1,39 +0,0 @@
1
- import FocusManager from "./FocusManager";
2
-
3
- const { ccclass, property } = cc._decorator;
4
-
5
- @ccclass
6
- export default class FocusItem extends cc.Component {
7
- @property
8
- scopeId: string = "";
9
-
10
- @property
11
- focusScale: number = 1.08;
12
-
13
- @property
14
- autoRegister: boolean = true;
15
-
16
- onEnable() {
17
- if (this.autoRegister) FocusManager.register(this);
18
- }
19
-
20
- onDisable() {
21
- if (this.autoRegister) FocusManager.unregister(this.node);
22
- }
23
-
24
- onFocus() {
25
- }
26
-
27
- onBlur() {
28
- }
29
-
30
- onConfirm() {
31
- let button = this.node.getComponent(cc.Button);
32
- if (button && button.interactable) {
33
- cc.Component.EventHandler.emitEvents(button.clickEvents, null);
34
- } else {
35
- var event = new cc.Event.EventCustom(cc.Node.EventType.TOUCH_END, true);
36
- this.node.emit(cc.Node.EventType.TOUCH_END, event);
37
- }
38
- }
39
- }
@@ -1,10 +0,0 @@
1
- {
2
- "ver": "1.1.0",
3
- "uuid": "bd94425f-361c-4c94-a03d-8001b12f1305",
4
- "importer": "typescript",
5
- "isPlugin": false,
6
- "loadPluginInWeb": true,
7
- "loadPluginInNative": true,
8
- "loadPluginInEditor": false,
9
- "subMetas": {}
10
- }
@@ -1,10 +0,0 @@
1
- export default class FocusScope {
2
- public scopeId: string;
3
- public root: cc.Node | null;
4
- public lastFocusId: string = "";
5
-
6
- constructor(scopeId: string, root?: cc.Node) {
7
- this.scopeId = scopeId || "page";
8
- this.root = root || null;
9
- }
10
- }
@@ -1,10 +0,0 @@
1
- {
2
- "ver": "1.1.0",
3
- "uuid": "cfa53b0b-9d55-4aa2-8000-241eebc81a4d",
4
- "importer": "typescript",
5
- "isPlugin": false,
6
- "loadPluginInWeb": true,
7
- "loadPluginInNative": true,
8
- "loadPluginInEditor": false,
9
- "subMetas": {}
10
- }