@chiyou/minigame-framework 1.3.0 → 1.3.2

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.
Files changed (27) hide show
  1. package/package.json +1 -1
  2. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterBilibili.ts +28 -2
  3. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterDesktopBrowser.ts +8 -1
  4. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterDouYin.ts +28 -2
  5. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterHonor.ts +31 -5
  6. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterHuaWei.ts +34 -1
  7. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterKuaiShou.ts +28 -2
  8. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterOppo.ts +17 -2
  9. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterTapTap.ts +31 -5
  10. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterVivo.ts +32 -4
  11. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterWeiXin.ts +28 -2
  12. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterXiaoMi.ts +31 -5
  13. package/src/Framework/Adapter/PlatformAdapter/PlatformAdapterZhiFuBao.ts +31 -5
  14. package/src/Framework/Definition/AnalyticsDefinition.ts +107 -0
  15. package/src/Framework/Definition/FwkErrorDefinition.ts +18 -2
  16. package/src/Framework/Definition/SystemDefinition.ts +12 -1
  17. package/src/Framework/Definition/UIDefinition.ts +6 -0
  18. package/src/Framework/Manager/AnalyticsMgr.ts +351 -0
  19. package/src/Framework/Manager/AudioMgr.ts +56 -7
  20. package/src/Framework/Manager/InputMgr.ts +61 -0
  21. package/src/Framework/Manager/NodePoolMgr.ts +59 -1
  22. package/src/Framework/Manager/TimerMgr.ts +49 -0
  23. package/src/Framework/Manager/UIMgr.ts +75 -9
  24. package/src/Framework/SDK/Umeng/umtrack-wx-game.ts +107 -0
  25. package/src/Framework/Utils/LogUtils.ts +27 -0
  26. package/src/Framework/Utils/ObjectUtils.ts +111 -0
  27. package/src/index.ts +2 -0
@@ -2,4 +2,10 @@
2
2
  export enum ToastDuration {
3
3
  Duration_Short = 1,
4
4
  Duration_Long = 2,
5
+ }
6
+
7
+ export enum ToastPosition {
8
+ Top = 0,
9
+ Center = 1,
10
+ Bottom = 2,
5
11
  }
@@ -0,0 +1,351 @@
1
+ import { BaseMgr } from "./BaseMgr";
2
+ import { LogUtils } from "../Utils/LogUtils";
3
+ import { ServiceLocator } from "../Utils/ServiceLocator";
4
+ import { FwkErrorCode } from "../Definition/FwkErrorDefinition";
5
+ import { PlatformID } from "../Definition/SystemDefinition";
6
+ import { UmengConfig, EventParams, UserProperties, AnalyticsEventId, AnalyticsParamKey } from "../Definition/AnalyticsDefinition";
7
+ import { UmaInitConfig, UmaSDK, uma as wxUma } from "../SDK/Umeng/umtrack-wx-game";
8
+ // 后续新增平台 SDK 在此导入,例如:
9
+ // import { uma as ttUma } from "../SDK/Umeng/umtrack-tt-game";
10
+
11
+ export class AnalyticsMgr extends BaseMgr {
12
+ /** 单例实例 */
13
+ public static Instance: AnalyticsMgr = null;
14
+ /** 日志标签 */
15
+ static TAG: string = "AnalyticsMgr";
16
+
17
+ /** 是否已初始化 */
18
+ private _isInited: boolean = false;
19
+ /** 是否启用统计 */
20
+ private _isEnabled: boolean = false;
21
+ /** 友盟配置 */
22
+ private _umengConfig: UmengConfig = null;
23
+ /** 当前平台的 appKey */
24
+ private _appKey: string = "";
25
+ /** 当前平台对应的 SDK 实例 */
26
+ private _sdk: UmaSDK = null;
27
+ /** 当前页面名称(用于 trackPageStart/End 配对) */
28
+ private _currentPage: string = "";
29
+
30
+ /**
31
+ * 平台 → SDK 实例映射
32
+ * 新增平台时:
33
+ * 1. 在 SDK/Umeng/ 下添加对应的 SDK 文件
34
+ * 2. 在顶部 import 并在此注册
35
+ */
36
+ private static _sdkMap: Map<PlatformID, UmaSDK> = new Map([
37
+ [PlatformID.ID_MiniGame_WeiXin, wxUma],
38
+ // 新增平台示例:
39
+ // [PlatformID.ID_MiniGame_DouYin, ttUma],
40
+ ]);
41
+
42
+ onLoad(): void {
43
+ super.onLoad();
44
+
45
+ if (AnalyticsMgr.Instance === null) {
46
+ AnalyticsMgr.Instance = this;
47
+ } else {
48
+ this.destroy();
49
+ return;
50
+ }
51
+
52
+ ServiceLocator.Instance.register("AnalyticsMgr", this);
53
+
54
+ // 注意:SDK 内部已注册 wx.onShow/onHide 自动调用 resume/pause
55
+ // 无需在 AnalyticsMgr 监听生命周期事件
56
+ }
57
+
58
+ onDestroy(): void {
59
+ super.onDestroy();
60
+ }
61
+
62
+ /**
63
+ * 初始化统计管理器
64
+ * @param umengConfig 友盟 SDK 配置
65
+ *
66
+ * 自动判断当前平台是否在 appKeyMap 中配置了 appKey:
67
+ * - 有配置 → 启用统计并初始化 SDK
68
+ * - 无配置 → 禁用统计,跳过初始化
69
+ */
70
+ public init(umengConfig: UmengConfig): void {
71
+ if (this._isInited) {
72
+ LogUtils.Instance.warn(AnalyticsMgr.TAG, "重复初始化");
73
+ return;
74
+ }
75
+
76
+ if (!umengConfig) {
77
+ LogUtils.Instance.error(AnalyticsMgr.TAG, FwkErrorCode.Analytics.ConfigError, {
78
+ operation: "init",
79
+ reason: "友盟配置缺失",
80
+ });
81
+ this._isInited = true;
82
+ return;
83
+ }
84
+
85
+ this._umengConfig = umengConfig;
86
+
87
+ // 根据当前平台是否配置了 appKey 和 SDK 来决定是否启用
88
+ const platformID: PlatformID = BaseMgr.Instance.getCurrentPlatformID();
89
+ const appKey = umengConfig.appKeyMap.get(platformID);
90
+ const sdk = AnalyticsMgr._sdkMap.get(platformID);
91
+
92
+ if (appKey && sdk) {
93
+ this._appKey = appKey;
94
+ this._sdk = sdk;
95
+ this._isEnabled = true;
96
+ this._initUmeng();
97
+ } else {
98
+ this._isEnabled = false;
99
+ if (!sdk) {
100
+ LogUtils.Instance.info(AnalyticsMgr.TAG, "当前平台无友盟SDK,统计功能已禁用", {
101
+ platformID: PlatformID[platformID],
102
+ });
103
+ } else {
104
+ LogUtils.Instance.info(AnalyticsMgr.TAG, "当前平台未配置友盟appKey,统计功能已禁用", {
105
+ platformID: PlatformID[platformID],
106
+ });
107
+ }
108
+ }
109
+
110
+ this._isInited = true;
111
+ LogUtils.Instance.info(AnalyticsMgr.TAG, "初始化完成", {
112
+ enabled: this._isEnabled,
113
+ platformID: PlatformID[platformID],
114
+ });
115
+ }
116
+
117
+ // ==================== 生命周期 ====================
118
+ // 注意:SDK 内部已自动处理 wx.onShow/onHide 的 resume/pause 调用
119
+
120
+ // ==================== 事件上报 ====================
121
+
122
+ /**
123
+ * 上报自定义事件
124
+ * @param eventId 事件 ID
125
+ * @param params 事件参数
126
+ */
127
+ public trackEvent(eventId: string | AnalyticsEventId, params?: EventParams): void {
128
+ if (!this._isEnabled) return;
129
+ try {
130
+ this._sdk.trackEvent(eventId, params);
131
+ LogUtils.Instance.info(AnalyticsMgr.TAG, "事件上报", {
132
+ eventId: eventId,
133
+ });
134
+ } catch (e) {
135
+ LogUtils.Instance.error(AnalyticsMgr.TAG, FwkErrorCode.Analytics.SDKCallFailed, {
136
+ operation: "trackEvent",
137
+ reason: String(e),
138
+ eventId: eventId,
139
+ });
140
+ }
141
+ }
142
+
143
+ /**
144
+ * 页面开始追踪
145
+ * @param pageName 页面名称
146
+ */
147
+ public trackPageStart(pageName: string): void {
148
+ if (!this._isEnabled) return;
149
+ try {
150
+ this._sdk.trackPageStart(pageName);
151
+ this._currentPage = pageName;
152
+ LogUtils.Instance.info(AnalyticsMgr.TAG, "页面开始", {
153
+ pageName: pageName,
154
+ });
155
+ } catch (e) {
156
+ LogUtils.Instance.error(AnalyticsMgr.TAG, FwkErrorCode.Analytics.SDKCallFailed, {
157
+ operation: "trackPageStart",
158
+ reason: String(e),
159
+ pageName: pageName,
160
+ });
161
+ }
162
+ }
163
+
164
+ /**
165
+ * 页面结束追踪
166
+ * @param pageName 页面名称
167
+ */
168
+ public trackPageEnd(pageName: string): void {
169
+ if (!this._isEnabled) return;
170
+ try {
171
+ this._sdk.trackPageEnd(pageName);
172
+ this._currentPage = "";
173
+ LogUtils.Instance.info(AnalyticsMgr.TAG, "页面结束", {
174
+ pageName: pageName,
175
+ });
176
+ } catch (e) {
177
+ LogUtils.Instance.error(AnalyticsMgr.TAG, FwkErrorCode.Analytics.SDKCallFailed, {
178
+ operation: "trackPageEnd",
179
+ reason: String(e),
180
+ pageName: pageName,
181
+ });
182
+ }
183
+ }
184
+
185
+ // ==================== 用户追踪 ====================
186
+
187
+ /**
188
+ * 设置用户 ID
189
+ * @param userId 用户 ID
190
+ * @param provider 提供商
191
+ */
192
+ public setUserId(userId: string, provider?: string): void {
193
+ if (!this._isEnabled) return;
194
+ try {
195
+ this._sdk.setUserid(userId, provider);
196
+ LogUtils.Instance.info(AnalyticsMgr.TAG, "设置用户ID", {
197
+ userId: userId,
198
+ provider: provider,
199
+ });
200
+ } catch (e) {
201
+ LogUtils.Instance.error(AnalyticsMgr.TAG, FwkErrorCode.Analytics.SDKCallFailed, {
202
+ operation: "setUserId",
203
+ reason: String(e),
204
+ });
205
+ }
206
+ }
207
+
208
+ /**
209
+ * 设置 OpenID
210
+ * @param openid 微信 openid
211
+ */
212
+ public setOpenId(openid: string): void {
213
+ if (!this._isEnabled) return;
214
+ try {
215
+ this._sdk.setOpenid(openid);
216
+ LogUtils.Instance.info(AnalyticsMgr.TAG, "设置OpenID");
217
+ } catch (e) {
218
+ LogUtils.Instance.error(AnalyticsMgr.TAG, FwkErrorCode.Analytics.SDKCallFailed, {
219
+ operation: "setOpenId",
220
+ reason: String(e),
221
+ });
222
+ }
223
+ }
224
+
225
+ /**
226
+ * 设置用户属性
227
+ * @param properties 用户属性
228
+ */
229
+ public setUserProperties(properties: UserProperties): void {
230
+ if (!this._isEnabled) return;
231
+ try {
232
+ this._sdk.setUserInfo(properties);
233
+ LogUtils.Instance.info(AnalyticsMgr.TAG, "设置用户属性");
234
+ } catch (e) {
235
+ LogUtils.Instance.error(AnalyticsMgr.TAG, FwkErrorCode.Analytics.SDKCallFailed, {
236
+ operation: "setUserProperties",
237
+ reason: String(e),
238
+ });
239
+ }
240
+ }
241
+
242
+ // ==================== 游戏特定事件 ====================
243
+
244
+ /** 关卡开始 */
245
+ public trackLevelStart(level: string | number, extraParams?: EventParams): void {
246
+ const params: Record<string, any> = {
247
+ [AnalyticsParamKey.Level]: level,
248
+ ...this._flattenParams(extraParams),
249
+ };
250
+ this.trackEvent(AnalyticsEventId.LevelStart, params);
251
+ }
252
+
253
+ /** 关卡完成 */
254
+ public trackLevelComplete(level: string | number, score?: number, duration?: number, extraParams?: EventParams): void {
255
+ const params: Record<string, any> = {
256
+ [AnalyticsParamKey.Level]: level,
257
+ ...(score !== undefined ? { [AnalyticsParamKey.Score]: score } : {}),
258
+ ...(duration !== undefined ? { [AnalyticsParamKey.Duration]: duration } : {}),
259
+ ...this._flattenParams(extraParams),
260
+ };
261
+ this.trackEvent(AnalyticsEventId.LevelComplete, params);
262
+ }
263
+
264
+ /** 关卡失败 */
265
+ public trackLevelFail(level: string | number, reason?: string, extraParams?: EventParams): void {
266
+ const params: Record<string, any> = {
267
+ [AnalyticsParamKey.Level]: level,
268
+ ...(reason ? { [AnalyticsParamKey.Reason]: reason } : {}),
269
+ ...this._flattenParams(extraParams),
270
+ };
271
+ this.trackEvent(AnalyticsEventId.LevelFail, params);
272
+ }
273
+
274
+ /** 广告展示 */
275
+ public trackAdShow(adType: string, adId: string, extraParams?: EventParams): void {
276
+ const params: Record<string, any> = {
277
+ [AnalyticsParamKey.AdType]: adType,
278
+ [AnalyticsParamKey.AdId]: adId,
279
+ ...this._flattenParams(extraParams),
280
+ };
281
+ this.trackEvent(AnalyticsEventId.AdShow, params);
282
+ }
283
+
284
+ /** 广告点击 */
285
+ public trackAdClick(adType: string, adId: string, extraParams?: EventParams): void {
286
+ const params: Record<string, any> = {
287
+ [AnalyticsParamKey.AdType]: adType,
288
+ [AnalyticsParamKey.AdId]: adId,
289
+ ...this._flattenParams(extraParams),
290
+ };
291
+ this.trackEvent(AnalyticsEventId.AdClick, params);
292
+ }
293
+
294
+ /** 分享 */
295
+ public trackShare(shareType: string, shareContent?: string): void {
296
+ const params: Record<string, any> = {
297
+ [AnalyticsParamKey.ShareType]: shareType,
298
+ ...(shareContent ? { [AnalyticsParamKey.ShareType]: shareContent } : {}),
299
+ };
300
+ this.trackEvent(AnalyticsEventId.Share, params);
301
+ }
302
+
303
+ // ==================== 状态查询 ====================
304
+
305
+ /** 是否已初始化 */
306
+ public get isInited(): boolean {
307
+ return this._isInited;
308
+ }
309
+
310
+ /** 是否启用 */
311
+ public get isEnabled(): boolean {
312
+ return this._isEnabled;
313
+ }
314
+
315
+ /** 当前页面 */
316
+
317
+ // ==================== 私有方法 ====================
318
+
319
+ /** 初始化友盟 SDK */
320
+ private _initUmeng(): void {
321
+ try {
322
+ const sdkConfig: UmaInitConfig = {
323
+ appKey: this._appKey,
324
+ useOpenid: this._umengConfig.useOpenid,
325
+ autoGetOpenid: this._umengConfig.autoGetOpenid,
326
+ debug: this._umengConfig.debug,
327
+ };
328
+ this._sdk.init(sdkConfig);
329
+ LogUtils.Instance.info(AnalyticsMgr.TAG, "友盟SDK初始化成功", {
330
+ appKey: this._appKey,
331
+ useOpenid: sdkConfig.useOpenid,
332
+ debug: sdkConfig.debug,
333
+ });
334
+ } catch (e) {
335
+ LogUtils.Instance.error(AnalyticsMgr.TAG, FwkErrorCode.Analytics.SDKInitFailed, {
336
+ operation: "_initUmeng",
337
+ reason: String(e),
338
+ appKey: this._appKey,
339
+ });
340
+ }
341
+ }
342
+
343
+
344
+
345
+ /** 展开 EventParams 为普通对象 */
346
+ private _flattenParams(params?: EventParams): Record<string, any> {
347
+ if (!params) return {};
348
+ if (typeof params === 'string') return { value: params };
349
+ return params as Record<string, any>;
350
+ }
351
+ }
@@ -1,4 +1,4 @@
1
- import { AudioClip, AudioSource } from "cc";
1
+ import { AudioClip, AudioSource, Node } from "cc";
2
2
  import { LogUtils } from "../Utils/LogUtils";
3
3
  import { PlatformID } from "../Definition/SystemDefinition";
4
4
  import { FrameworkBase } from "../Definition/FrameworkBase";
@@ -15,7 +15,9 @@ export class AudioMgr extends BaseMgr {
15
15
  static TAG: string = "AudioMgr";
16
16
 
17
17
  private effectSwitch: boolean = false;
18
- private effectAudioSource: AudioSource = null;
18
+ private effectPool: AudioSource[] = [];
19
+ /** 音效池最大同时播放数量 */
20
+ private static readonly EFFECT_POOL_SIZE: number = 8;
19
21
 
20
22
  private musicSwitch: boolean = false;
21
23
  private musicAudioSource: AudioSource = null;
@@ -39,11 +41,11 @@ export class AudioMgr extends BaseMgr {
39
41
  * 初始化音频管理器
40
42
  */
41
43
  public init(): void {
42
- this.effectAudioSource = this.node.addComponent(AudioSource);
43
44
  this.musicAudioSource = this.node.addComponent(AudioSource);
44
45
 
46
+ this._initEffectPool();
47
+
45
48
  this.effectSwitch = false;
46
- this.effectAudioSource.volume = 0;
47
49
 
48
50
  this.musicSwitch = false;
49
51
  this.musicAudioSource.volume = 0;
@@ -163,7 +165,9 @@ export class AudioMgr extends BaseMgr {
163
165
  LogUtils.Instance.info(AudioMgr.TAG, "开启音效");
164
166
 
165
167
  this.effectSwitch = true;
166
- this.effectAudioSource.volume = 1;
168
+ for (let i = 0; i < this.effectPool.length; i++) {
169
+ this.effectPool[i].volume = 1;
170
+ }
167
171
  }
168
172
 
169
173
  /** 关闭音效 */
@@ -175,7 +179,52 @@ export class AudioMgr extends BaseMgr {
175
179
  LogUtils.Instance.info(AudioMgr.TAG, "关闭音效");
176
180
 
177
181
  this.effectSwitch = false;
178
- this.effectAudioSource.volume = 0;
182
+ for (let i = 0; i < this.effectPool.length; i++) {
183
+ this.effectPool[i].volume = 0;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * 初始化音效池(内部方法)
189
+ */
190
+ private _initEffectPool(): void {
191
+ for (let i = 0; i < AudioMgr.EFFECT_POOL_SIZE; i++) {
192
+ let node: Node = new Node("EffectAudio_" + i);
193
+ node.setParent(this.node);
194
+ let audioSource: AudioSource = node.addComponent(AudioSource);
195
+ audioSource.volume = 0;
196
+ // 监听 ended 事件,播完后清 clip 以便下次复用
197
+ audioSource.node.on(AudioSource.EventType.ENDED, () => {
198
+ audioSource.clip = null;
199
+ });
200
+ this.effectPool.push(audioSource);
201
+ }
202
+ }
203
+
204
+ /**
205
+ * 从池中找空闲 AudioSource 播放音效(内部方法)
206
+ */
207
+ private _playEffectWithPool(clip: AudioClip): void {
208
+ // 找一个当前未在播放的 AudioSource
209
+ let audioSource: AudioSource = null;
210
+ for (let i = 0; i < this.effectPool.length; i++) {
211
+ if (!this.effectPool[i].playing) {
212
+ audioSource = this.effectPool[i];
213
+ break;
214
+ }
215
+ }
216
+
217
+ // 池已满,跳过此次播放
218
+ if (audioSource === null) {
219
+ LogUtils.Instance.warn(AudioMgr.TAG, "音效池已满(8个全部占用),跳过此次播放", {
220
+ operation: "_playEffectWithPool",
221
+ poolSize: this.effectPool.length
222
+ });
223
+ return;
224
+ }
225
+
226
+ audioSource.clip = clip;
227
+ audioSource.play();
179
228
  }
180
229
 
181
230
  /**
@@ -201,7 +250,7 @@ export class AudioMgr extends BaseMgr {
201
250
  });
202
251
  return;
203
252
  }
204
- this.effectAudioSource.playOneShot(audioClip);
253
+ this._playEffectWithPool(audioClip);
205
254
  }
206
255
  }
207
256
 
@@ -625,6 +625,36 @@ export class InputMgr extends BaseMgr {
625
625
  }
626
626
  }
627
627
 
628
+ /**
629
+ * 监听键盘按下(一次性)
630
+ * 触发一次后自动移除监听
631
+ * @param keyCode 按键码(KeyCode),KeyCode.NONE 表示监听所有按键
632
+ * @param callback 回调函数
633
+ * @param target 回调目标
634
+ */
635
+ public onceKeyDown(keyCode: KeyCode, callback: (event: EventKeyboard) => void, target: any): void {
636
+ let onceCallback = (event: EventKeyboard) => {
637
+ this.offKeyDown(keyCode, onceCallback, target);
638
+ callback.apply(target, [event]);
639
+ };
640
+ this.onKeyDown(keyCode, onceCallback, target);
641
+ }
642
+
643
+ /**
644
+ * 监听键盘抬起(一次性)
645
+ * 触发一次后自动移除监听
646
+ * @param keyCode 按键码(KeyCode),KeyCode.NONE 表示监听所有按键
647
+ * @param callback 回调函数
648
+ * @param target 回调目标
649
+ */
650
+ public onceKeyUp(keyCode: KeyCode, callback: (event: EventKeyboard) => void, target: any): void {
651
+ let onceCallback = (event: EventKeyboard) => {
652
+ this.offKeyUp(keyCode, onceCallback, target);
653
+ callback.apply(target, [event]);
654
+ };
655
+ this.onKeyUp(keyCode, onceCallback, target);
656
+ }
657
+
628
658
  /**
629
659
  * 判断按键是否处于按下状态
630
660
  * @param keyCode 按键码(KeyCode)
@@ -891,6 +921,22 @@ export class InputMgr extends BaseMgr {
891
921
  }
892
922
  }
893
923
 
924
+ /**
925
+ * 监听按钮点击(一次性)
926
+ * 触发一次后自动移除监听
927
+ * @param button 按钮节点
928
+ * @param callback 回调函数
929
+ * @param target 回调目标
930
+ * @param preventAccidental 是否防误触(连续点击),默认 true
931
+ */
932
+ public onceButtonClick(button: Node, callback: () => void, target: any, preventAccidental: boolean = true): void {
933
+ let onceCallback = () => {
934
+ this.offButtonClick(button, onceCallback, target);
935
+ callback.apply(target);
936
+ };
937
+ this.onButtonClick(button, onceCallback, target, preventAccidental);
938
+ }
939
+
894
940
  // ==================== 节点触摸事件(对外) ====================
895
941
 
896
942
  /**
@@ -1019,6 +1065,21 @@ export class InputMgr extends BaseMgr {
1019
1065
  }
1020
1066
  }
1021
1067
 
1068
+ /**
1069
+ * 监听节点触摸结束(一次性)
1070
+ * 触发一次后自动移除监听
1071
+ * @param node 节点
1072
+ * @param callback 回调函数
1073
+ * @param target 回调目标
1074
+ */
1075
+ public onceNodeTouchEnd(node: Node, callback: (touch: Touch) => void, target: any): void {
1076
+ let onceCallback = (touch: Touch) => {
1077
+ this.offNodeTouchEnd(node, onceCallback, target);
1078
+ callback.apply(target, [touch]);
1079
+ };
1080
+ this.onNodeTouchEnd(node, onceCallback, target);
1081
+ }
1082
+
1022
1083
  /**
1023
1084
  * 移除节点触摸事件
1024
1085
  */
@@ -25,6 +25,8 @@ export interface IPoolStatistics {
25
25
  nowActiveCount: number;
26
26
  /** 历史最大活跃节点数 */
27
27
  maxActiveCount: number;
28
+ /** 当前可用节点数 */
29
+ availableCount: number;
28
30
  /** 最后访问时间 */
29
31
  lastAccessTime: number;
30
32
  }
@@ -49,6 +51,7 @@ export class PoolInfo {
49
51
  reusedCount: 0,
50
52
  nowActiveCount: 0,
51
53
  maxActiveCount: 0,
54
+ availableCount: 0,
52
55
  lastAccessTime: Date.now(),
53
56
  };
54
57
  }
@@ -229,8 +232,9 @@ export class NodePoolMgr extends BaseMgr {
229
232
  reusedCount: 0,
230
233
  nowActiveCount: 0,
231
234
  maxActiveCount: 0,
235
+ availableCount: 0,
232
236
  lastAccessTime: Date.now(),
233
- }
237
+ };
234
238
  }
235
239
  }
236
240
 
@@ -475,6 +479,13 @@ export class NodePoolMgr extends BaseMgr {
475
479
  }
476
480
 
477
481
  let poolInfo: PoolInfo = this._poolInfoMap.get(poolName);
482
+ if (!poolInfo) {
483
+ LogUtils.Instance.error(NodePoolMgr.TAG, FwkErrorCode.NodePool.NotFound, {
484
+ operation: "_destoryNode",
485
+ poolName: poolName,
486
+ reason: "节点池信息获取失败"
487
+ });
488
+ }
478
489
 
479
490
  if (this.uiMgr === null) {
480
491
  this.uiMgr = ServiceLocator.Instance.get<UIMgr>("UIMgr");
@@ -545,6 +556,52 @@ export class NodePoolMgr extends BaseMgr {
545
556
  node.scale = Vec3.ONE;
546
557
  }
547
558
 
559
+ /**
560
+ * 检查节点池是否存在
561
+ * @param poolName 节点池名称
562
+ */
563
+ public hasPool(poolName: string): boolean {
564
+ return this._poolInfoMap.has(poolName);
565
+ }
566
+
567
+ /**
568
+ * 获取指定节点池的统计信息
569
+ * @param poolName 节点池名称
570
+ */
571
+ public getPoolStats(poolName: string): IPoolStatistics | null {
572
+ let poolInfo: PoolInfo = this._poolInfoMap.get(poolName);
573
+ if (!poolInfo) {
574
+ return null;
575
+ }
576
+
577
+ return {
578
+ createdCount: poolInfo.statistics.createdCount,
579
+ reusedCount: poolInfo.statistics.reusedCount,
580
+ nowActiveCount: poolInfo.statistics.nowActiveCount,
581
+ maxActiveCount: poolInfo.statistics.maxActiveCount,
582
+ availableCount: poolInfo.pool.size(),
583
+ lastAccessTime: poolInfo.statistics.lastAccessTime,
584
+ };
585
+ }
586
+
587
+ /**
588
+ * 获取所有节点池的统计信息
589
+ */
590
+ public getAllPoolStats(): Map<string, IPoolStatistics> {
591
+ let result: Map<string, IPoolStatistics> = new Map<string, IPoolStatistics>();
592
+ this._poolInfoMap.forEach((poolInfo: PoolInfo, poolName: string) => {
593
+ result.set(poolName, {
594
+ createdCount: poolInfo.statistics.createdCount,
595
+ reusedCount: poolInfo.statistics.reusedCount,
596
+ nowActiveCount: poolInfo.statistics.nowActiveCount,
597
+ maxActiveCount: poolInfo.statistics.maxActiveCount,
598
+ availableCount: poolInfo.pool.size(),
599
+ lastAccessTime: poolInfo.statistics.lastAccessTime,
600
+ });
601
+ });
602
+ return result;
603
+ }
604
+
548
605
  /** 打印节点池状态(调试用) */
549
606
  private _printPoolState(poolName: string): void {
550
607
  let poolInfo: PoolInfo = this._poolInfoMap.get(poolName);
@@ -573,6 +630,7 @@ export class NodePoolMgr extends BaseMgr {
573
630
  reusedCount: poolInfo.statistics.reusedCount,
574
631
  nowActiveCount: poolInfo.statistics.nowActiveCount,
575
632
  maxActiveCount: poolInfo.statistics.maxActiveCount,
633
+ availableCount: poolInfo.pool.size(),
576
634
  lastAccessTime: poolInfo.statistics.lastAccessTime
577
635
  });
578
636
  }