@chiyou/minigame-framework 1.3.0 → 1.3.1

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chiyou/minigame-framework",
3
- "version": "1.3.0",
3
+ "version": "1.3.1",
4
4
  "description": "基于 Cocos Creator 3.x 的小游戏开发框架,支持多平台发布",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -265,12 +265,38 @@ export class PlatformAdapterBilibili extends AbsPlatformAdapter {
265
265
  }
266
266
 
267
267
  public getScreenInfo(): ScreenInfo {
268
- let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0);
268
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
269
+ left: 0, right: 0, top: 0,bottom: 0,
270
+ width: 0, height: 0
271
+ });
269
272
 
270
273
  if (window["bl"] && window["bl"].getSystemInfoSync) {
271
274
  let systemInfo: any = window["bl"].getSystemInfoSync();
272
275
  if (systemInfo !== null) {
273
- screenInfo = new ScreenInfo(screenInfo.windowWidth, screenInfo.windowHeight, systemInfo.screenWidth, systemInfo.screenHeight);
276
+ screenInfo.screenWidth = systemInfo.screenWidth;
277
+ screenInfo.screenHeight = systemInfo.screenHeight;
278
+ screenInfo.windowWidth = systemInfo.windowWidth;
279
+ screenInfo.windowHeight = systemInfo.windowHeight;
280
+
281
+ if (systemInfo.safeArea !== null) {
282
+ screenInfo.safeArea = {
283
+ left: systemInfo.safeArea.left,
284
+ right: systemInfo.safeArea.right,
285
+ top: systemInfo.safeArea.top,
286
+ bottom: systemInfo.safeArea.bottom,
287
+ width: systemInfo.safeArea.width,
288
+ height: systemInfo.safeArea.height,
289
+ }
290
+ } else {
291
+ screenInfo.safeArea = {
292
+ left: 0,
293
+ right: systemInfo.screenWidth,
294
+ top: 0,
295
+ bottom: systemInfo.screenHeight,
296
+ width: systemInfo.screenWidth,
297
+ height: systemInfo.screenHeight,
298
+ }
299
+ }
274
300
  }
275
301
  }
276
302
 
@@ -113,7 +113,14 @@ export class PlatformAdapterDesktopBrowser extends AbsPlatformAdapter {
113
113
  }
114
114
 
115
115
  public getScreenInfo(): ScreenInfo {
116
- let screenInfo: ScreenInfo = new ScreenInfo(screen.width, screen.height, screen.width, screen.height);
116
+ let screenWidth = screen.width;
117
+ let screenHeight = screen.height;
118
+
119
+ let screenInfo: ScreenInfo = new ScreenInfo(
120
+ screenWidth, screenHeight, screenWidth, screenHeight, {
121
+ left: 0, right: screenWidth, top: 0,bottom: screenHeight,
122
+ width: screenWidth, height: screenHeight
123
+ });
117
124
 
118
125
  LogUtils.Instance.info(PlatformAdapterDesktopBrowser.TAG, "获取屏幕信息", {
119
126
  operation: "getScreenInfo",
@@ -281,12 +281,38 @@ export class PlatformAdapterDouYin extends AbsPlatformAdapter {
281
281
  }
282
282
 
283
283
  public getScreenInfo(): ScreenInfo {
284
- let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0);
284
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
285
+ left: 0, right: 0, top: 0,bottom: 0,
286
+ width: 0, height: 0
287
+ });
285
288
 
286
289
  if (window["tt"] && window["tt"].getSystemInfoSync) {
287
290
  let systemInfo: any = window["tt"].getSystemInfoSync();
288
291
  if (systemInfo !== null) {
289
- screenInfo = new ScreenInfo(systemInfo.windowWidth, systemInfo.windowHeight, systemInfo.screenWidth, systemInfo.screenHeight);
292
+ screenInfo.screenWidth = systemInfo.screenWidth;
293
+ screenInfo.screenHeight = systemInfo.screenHeight;
294
+ screenInfo.windowWidth = systemInfo.windowWidth;
295
+ screenInfo.windowHeight = systemInfo.windowHeight;
296
+
297
+ if (systemInfo.safeArea !== null) {
298
+ screenInfo.safeArea = {
299
+ left: systemInfo.safeArea.left,
300
+ right: systemInfo.safeArea.right,
301
+ top: systemInfo.safeArea.top,
302
+ bottom: systemInfo.safeArea.bottom,
303
+ width: systemInfo.safeArea.width,
304
+ height: systemInfo.safeArea.height,
305
+ }
306
+ } else {
307
+ screenInfo.safeArea = {
308
+ left: 0,
309
+ right: systemInfo.screenWidth,
310
+ top: 0,
311
+ bottom: systemInfo.screenHeight,
312
+ width: systemInfo.screenWidth,
313
+ height: systemInfo.screenHeight,
314
+ }
315
+ }
290
316
  }
291
317
  }
292
318
 
@@ -187,12 +187,38 @@ export class PlatformAdapterHonor extends AbsPlatformAdapter {
187
187
  }
188
188
 
189
189
  public getScreenInfo(): ScreenInfo {
190
- let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0);
190
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
191
+ left: 0, right: 0, top: 0,bottom: 0,
192
+ width: 0, height: 0
193
+ });
191
194
 
192
- if (window["qg"] && window["qg"].getWindowInfo) {
193
- let windowInfo: any = window["qg"].getWindowInfo();
194
- if (windowInfo !== null) {
195
- screenInfo = new ScreenInfo(windowInfo.windowWidth, windowInfo.windowHeight, windowInfo.screenWidth, windowInfo.screenHeight);
195
+ if (window["qg"] && window["qg"].getSystemInfoSync) {
196
+ let systemInfo: any = window["qg"].getSystemInfoSync();
197
+ if (systemInfo !== null) {
198
+ screenInfo.screenWidth = systemInfo.screenWidth;
199
+ screenInfo.screenHeight = systemInfo.screenHeight;
200
+ screenInfo.windowWidth = systemInfo.windowWidth;
201
+ screenInfo.windowHeight = systemInfo.windowHeight;
202
+
203
+ if (systemInfo.safeArea !== null) {
204
+ screenInfo.safeArea = {
205
+ left: systemInfo.safeArea.left,
206
+ right: systemInfo.safeArea.right,
207
+ top: systemInfo.safeArea.top,
208
+ bottom: systemInfo.safeArea.bottom,
209
+ width: systemInfo.safeArea.width,
210
+ height: systemInfo.safeArea.height,
211
+ }
212
+ } else {
213
+ screenInfo.safeArea = {
214
+ left: 0,
215
+ right: systemInfo.screenWidth,
216
+ top: 0,
217
+ bottom: systemInfo.screenHeight,
218
+ width: systemInfo.screenWidth,
219
+ height: systemInfo.screenHeight,
220
+ }
221
+ }
196
222
  }
197
223
  }
198
224
 
@@ -184,7 +184,40 @@ export class PlatformAdapterHuaWei extends AbsPlatformAdapter {
184
184
  }
185
185
 
186
186
  public getScreenInfo(): ScreenInfo {
187
- let screenInfo: ScreenInfo = new ScreenInfo(screen.availWidth, screen.availHeight, screen.width, screen.height);
187
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
188
+ left: 0, right: 0, top: 0,bottom: 0,
189
+ width: 0, height: 0
190
+ });
191
+
192
+ if (window["qg"] && window["qg"].getSystemInfoSync) {
193
+ let systemInfo: any = window["qg"].getSystemInfoSync();
194
+ if (systemInfo !== null) {
195
+ screenInfo.screenWidth = systemInfo.screenWidth;
196
+ screenInfo.screenHeight = systemInfo.screenHeight;
197
+ screenInfo.windowWidth = systemInfo.windowWidth;
198
+ screenInfo.windowHeight = systemInfo.windowHeight;
199
+
200
+ if (systemInfo.safeArea !== null) {
201
+ screenInfo.safeArea = {
202
+ left: systemInfo.safeArea.left,
203
+ right: systemInfo.safeArea.right,
204
+ top: systemInfo.safeArea.top,
205
+ bottom: systemInfo.safeArea.bottom,
206
+ width: systemInfo.safeArea.width,
207
+ height: systemInfo.safeArea.height,
208
+ }
209
+ } else {
210
+ screenInfo.safeArea = {
211
+ left: 0,
212
+ right: systemInfo.screenWidth,
213
+ top: 0,
214
+ bottom: systemInfo.screenHeight,
215
+ width: systemInfo.screenWidth,
216
+ height: systemInfo.screenHeight,
217
+ }
218
+ }
219
+ }
220
+ }
188
221
 
189
222
  LogUtils.Instance.info(PlatformAdapterHuaWei.TAG, "获取屏幕信息", {
190
223
  operation: "getScreenInfo",
@@ -222,12 +222,38 @@ export class PlatformAdapterKuaiShou extends AbsPlatformAdapter {
222
222
  }
223
223
 
224
224
  public getScreenInfo(): ScreenInfo {
225
- let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0);
225
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
226
+ left: 0, right: 0, top: 0,bottom: 0,
227
+ width: 0, height: 0
228
+ });
226
229
 
227
230
  if (window["ks"] && window["ks"].getSystemInfoSync) {
228
231
  let systemInfo: any = window["ks"].getSystemInfoSync();
229
232
  if (systemInfo !== null) {
230
- screenInfo = new ScreenInfo(systemInfo.windowWidth, systemInfo.windowHeight, systemInfo.screenWidth, systemInfo.screenHeight);
233
+ screenInfo.screenWidth = systemInfo.screenWidth;
234
+ screenInfo.screenHeight = systemInfo.screenHeight;
235
+ screenInfo.windowWidth = systemInfo.windowWidth;
236
+ screenInfo.windowHeight = systemInfo.windowHeight;
237
+
238
+ if (systemInfo.safeArea !== null) {
239
+ screenInfo.safeArea = {
240
+ left: systemInfo.safeArea.left,
241
+ right: systemInfo.safeArea.right,
242
+ top: systemInfo.safeArea.top,
243
+ bottom: systemInfo.safeArea.bottom,
244
+ width: systemInfo.safeArea.width,
245
+ height: systemInfo.safeArea.height,
246
+ }
247
+ } else {
248
+ screenInfo.safeArea = {
249
+ left: 0,
250
+ right: systemInfo.screenWidth,
251
+ top: 0,
252
+ bottom: systemInfo.screenHeight,
253
+ width: systemInfo.screenWidth,
254
+ height: systemInfo.screenHeight,
255
+ }
256
+ }
231
257
  }
232
258
  }
233
259
 
@@ -187,12 +187,27 @@ export class PlatformAdapterOppo extends AbsPlatformAdapter {
187
187
  }
188
188
 
189
189
  public getScreenInfo(): ScreenInfo {
190
- let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0);
190
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
191
+ left: 0, right: 0, top: 0,bottom: 0,
192
+ width: 0, height: 0
193
+ });
191
194
 
192
195
  if (window["qg"] && window["qg"].getSystemInfoSync) {
193
196
  let systemInfo: any = window["qg"].getSystemInfoSync();
194
197
  if (systemInfo !== null) {
195
- screenInfo = new ScreenInfo(systemInfo.windowWidth, systemInfo.windowHeight, systemInfo.screenWidth, systemInfo.screenHeight);
198
+ screenInfo.screenWidth = systemInfo.screenWidth;
199
+ screenInfo.screenHeight = systemInfo.screenHeight;
200
+ screenInfo.windowWidth = systemInfo.windowWidth;
201
+ screenInfo.windowHeight = systemInfo.windowHeight;
202
+
203
+ screenInfo.safeArea = {
204
+ left: 0,
205
+ right: systemInfo.screenWidth,
206
+ top: 0,
207
+ bottom: systemInfo.screenHeight,
208
+ width: systemInfo.screenWidth,
209
+ height: systemInfo.screenHeight,
210
+ }
196
211
  }
197
212
  }
198
213
 
@@ -202,12 +202,38 @@ export class PlatformAdapterTapTap extends AbsPlatformAdapter {
202
202
  }
203
203
 
204
204
  public getScreenInfo(): ScreenInfo {
205
- let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0);
205
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
206
+ left: 0, right: 0, top: 0,bottom: 0,
207
+ width: 0, height: 0
208
+ });
206
209
 
207
- if (window["tap"] && window["tap"].getSystemInfoSync) {
208
- let systemInfo: any = window["tap"].getSystemInfoSync();
209
- if (systemInfo !== null) {
210
- screenInfo = new ScreenInfo(systemInfo.windowWidth, systemInfo.windowHeight, systemInfo.screenWidth, systemInfo.screenHeight);
210
+ if (window["tap"] && window["tap"].getWindowInfo) {
211
+ let windowInfo: any = window["tap"].getWindowInfo();
212
+ if (windowInfo !== null) {
213
+ screenInfo.screenWidth = windowInfo.screenWidth;
214
+ screenInfo.screenHeight = windowInfo.screenHeight;
215
+ screenInfo.windowWidth = windowInfo.windowWidth;
216
+ screenInfo.windowHeight = windowInfo.windowHeight;
217
+
218
+ if (windowInfo.safeArea !== null) {
219
+ screenInfo.safeArea = {
220
+ left: windowInfo.safeArea.left,
221
+ right: windowInfo.safeArea.right,
222
+ top: windowInfo.safeArea.top,
223
+ bottom: windowInfo.safeArea.bottom,
224
+ width: windowInfo.safeArea.width,
225
+ height: windowInfo.safeArea.height,
226
+ }
227
+ } else {
228
+ screenInfo.safeArea = {
229
+ left: 0,
230
+ right: windowInfo.screenWidth,
231
+ top: 0,
232
+ bottom: windowInfo.screenHeight,
233
+ width: windowInfo.screenWidth,
234
+ height: windowInfo.screenHeight,
235
+ }
236
+ }
211
237
  }
212
238
  }
213
239
 
@@ -175,12 +175,40 @@ export class PlatformAdapterVivo extends AbsPlatformAdapter {
175
175
  }
176
176
 
177
177
  public getScreenInfo(): ScreenInfo {
178
- let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0);
178
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
179
+ left: 0, right: 0, top: 0,bottom: 0,
180
+ width: 0, height: 0
181
+ });
179
182
 
180
- if (window["qg"] && window["qg"].getWindowInfo) {
183
+ if (window["qg"] && window["qg"].getWindowInfo && window["qg"].getSystemInfoSync) {
184
+ let systemInfo: any = window["qg"].getSystemInfoSync();
181
185
  let windowInfo: any = window["qg"].getWindowInfo();
182
- if (windowInfo !== null) {
183
- screenInfo = new ScreenInfo(windowInfo.windowWidth, windowInfo.windowHeight, windowInfo.windowWidth, windowInfo.windowHeight);
186
+
187
+ if (windowInfo !== null && systemInfo !== null) {
188
+ screenInfo.screenWidth = systemInfo.screenWidth;
189
+ screenInfo.screenHeight = systemInfo.screenHeight;
190
+ screenInfo.windowWidth = windowInfo.windowWidth;
191
+ screenInfo.windowHeight = windowInfo.windowHeight;
192
+
193
+ if (windowInfo.safeArea !== null) {
194
+ screenInfo.safeArea = {
195
+ left: windowInfo.safeArea.left,
196
+ right: windowInfo.safeArea.right,
197
+ top: windowInfo.safeArea.top,
198
+ bottom: windowInfo.safeArea.bottom,
199
+ width: windowInfo.safeArea.width,
200
+ height: windowInfo.safeArea.height,
201
+ }
202
+ } else {
203
+ screenInfo.safeArea = {
204
+ left: 0,
205
+ right: systemInfo.screenWidth,
206
+ top: 0,
207
+ bottom: systemInfo.screenHeight,
208
+ width: systemInfo.screenWidth,
209
+ height: systemInfo.screenHeight,
210
+ }
211
+ }
184
212
  }
185
213
  }
186
214
 
@@ -342,12 +342,38 @@ export class PlatformAdapterWeiXin extends AbsPlatformAdapter {
342
342
  }
343
343
 
344
344
  public getScreenInfo(): ScreenInfo {
345
- let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0);
345
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
346
+ left: 0, right: 0, top: 0,bottom: 0,
347
+ width: 0, height: 0
348
+ });
346
349
 
347
350
  if (window["wx"] && window["wx"].getWindowInfo) {
348
351
  let windowInfo: any = window["wx"].getWindowInfo();
349
352
  if (windowInfo !== null) {
350
- screenInfo = new ScreenInfo(windowInfo.windowWidth, windowInfo.windowHeight, windowInfo.screenWidth, windowInfo.screenHeight);
353
+ screenInfo.screenWidth = windowInfo.screenWidth;
354
+ screenInfo.screenHeight = windowInfo.screenHeight;
355
+ screenInfo.windowWidth = windowInfo.windowWidth;
356
+ screenInfo.windowHeight = windowInfo.windowHeight;
357
+
358
+ if (windowInfo.safeArea !== null) {
359
+ screenInfo.safeArea = {
360
+ left: windowInfo.safeArea.left,
361
+ right: windowInfo.safeArea.right,
362
+ top: windowInfo.safeArea.top,
363
+ bottom: windowInfo.safeArea.bottom,
364
+ width: windowInfo.safeArea.width,
365
+ height: windowInfo.safeArea.height,
366
+ }
367
+ } else {
368
+ screenInfo.safeArea = {
369
+ left: 0,
370
+ right: windowInfo.screenWidth,
371
+ top: 0,
372
+ bottom: windowInfo.screenHeight,
373
+ width: windowInfo.screenWidth,
374
+ height: windowInfo.screenHeight,
375
+ }
376
+ }
351
377
  }
352
378
  }
353
379
 
@@ -182,12 +182,38 @@ export class PlatformAdapterXiaoMi extends AbsPlatformAdapter {
182
182
  }
183
183
 
184
184
  public getScreenInfo(): ScreenInfo {
185
- let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0);
185
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
186
+ left: 0, right: 0, top: 0,bottom: 0,
187
+ width: 0, height: 0
188
+ });
186
189
 
187
- if (window["qg"] && window["qg"].getWindowInfo) {
188
- let windowInfo: any = window["qg"].getWindowInfo();
189
- if (windowInfo !== null) {
190
- screenInfo = new ScreenInfo(windowInfo.windowWidth, windowInfo.windowHeight, windowInfo.windowWidth, windowInfo.windowHeight);
190
+ if (window["qg"] && window["qg"].getSystemInfoSync) {
191
+ let systemInfo: any = window["qg"].getSystemInfoSync();
192
+ if (systemInfo !== null) {
193
+ screenInfo.screenWidth = systemInfo.screenWidth;
194
+ screenInfo.screenHeight = systemInfo.screenHeight;
195
+ screenInfo.windowWidth = systemInfo.windowWidth;
196
+ screenInfo.windowHeight = systemInfo.windowHeight;
197
+
198
+ if (systemInfo.safeArea !== null) {
199
+ screenInfo.safeArea = {
200
+ left: systemInfo.safeArea.left,
201
+ right: systemInfo.safeArea.right,
202
+ top: systemInfo.safeArea.top,
203
+ bottom: systemInfo.safeArea.bottom,
204
+ width: systemInfo.safeArea.width,
205
+ height: systemInfo.safeArea.height,
206
+ }
207
+ } else {
208
+ screenInfo.safeArea = {
209
+ left: 0,
210
+ right: systemInfo.screenWidth,
211
+ top: 0,
212
+ bottom: systemInfo.screenHeight,
213
+ width: systemInfo.screenWidth,
214
+ height: systemInfo.screenHeight,
215
+ }
216
+ }
191
217
  }
192
218
  }
193
219
 
@@ -179,12 +179,38 @@ export class PlatformAdapterZhiFuBao extends AbsPlatformAdapter {
179
179
  }
180
180
 
181
181
  public getScreenInfo(): ScreenInfo {
182
- let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0);
182
+ let screenInfo: ScreenInfo = new ScreenInfo(0, 0, 0, 0, {
183
+ left: 0, right: 0, top: 0,bottom: 0,
184
+ width: 0, height: 0
185
+ });
183
186
 
184
- if (window["my"] && window["my"].getSystemInfoSync) {
185
- let systemInfo: any = window["my"].getSystemInfoSync();
186
- if (systemInfo !== null) {
187
- screenInfo = new ScreenInfo(systemInfo.windowWidth, systemInfo.windowHeight, systemInfo.screenWidth, systemInfo.screenHeight);
187
+ if (window["my"] && window["my"].getWindowInfo) {
188
+ let windowInfo: any = window["my"].getWindowInfo();
189
+ if (windowInfo !== null) {
190
+ screenInfo.screenWidth = windowInfo.screenWidth;
191
+ screenInfo.screenHeight = windowInfo.screenHeight;
192
+ screenInfo.windowWidth = windowInfo.windowWidth;
193
+ screenInfo.windowHeight = windowInfo.windowHeight;
194
+
195
+ if (windowInfo.safeArea !== null) {
196
+ screenInfo.safeArea = {
197
+ left: windowInfo.safeArea.left,
198
+ right: windowInfo.safeArea.right,
199
+ top: windowInfo.safeArea.top,
200
+ bottom: windowInfo.safeArea.bottom,
201
+ width: windowInfo.safeArea.width,
202
+ height: windowInfo.safeArea.height,
203
+ }
204
+ } else {
205
+ screenInfo.safeArea = {
206
+ left: 0,
207
+ right: windowInfo.screenWidth,
208
+ top: 0,
209
+ bottom: windowInfo.screenHeight,
210
+ width: windowInfo.screenWidth,
211
+ height: windowInfo.screenHeight,
212
+ }
213
+ }
188
214
  }
189
215
  }
190
216
 
@@ -25,17 +25,28 @@ export type AppItem = {
25
25
  appIDMap: Map<PlatformID, string>;
26
26
  }
27
27
 
28
+ export interface ISafeArea {
29
+ left: number; // 安全区左边界 X
30
+ right: number; // 安全区右边界 X
31
+ top: number; // 安全区上边界 Y(刘海高度)
32
+ bottom: number; // 安全区下边界 Y
33
+ width: number; // 安全区宽度,单位逻辑像素
34
+ height: number; // 安全区高度,单位逻辑像素
35
+ }
36
+
28
37
  export class ScreenInfo {
29
38
  windowWidth: number;
30
39
  windowHeight: number;
31
40
  screenWidth: number;
32
41
  screenHeight: number;
42
+ safeArea: ISafeArea;
33
43
 
34
- constructor(windowWidth: number, windowHeight: number, screenWidth: number, screenHeight: number) {
44
+ constructor(windowWidth: number, windowHeight: number, screenWidth: number, screenHeight: number, safeArea: ISafeArea) {
35
45
  this.windowWidth = windowWidth;
36
46
  this.windowHeight = windowHeight;
37
47
  this.screenWidth = screenWidth;
38
48
  this.screenHeight = screenHeight;
49
+ this.safeArea = safeArea;
39
50
  }
40
51
  }
41
52
 
@@ -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
  }
@@ -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
  }
@@ -1,8 +1,10 @@
1
1
  import { FwkErrorCode } from "../Definition/FwkErrorDefinition";
2
+ import { FrameworkBase } from "../Definition/FrameworkBase";
2
3
  import { TimerRepeat } from "../Definition/TimerDefinition";
3
4
  import { LogUtils } from "../Utils/LogUtils";
4
5
  import { ServiceLocator } from "../Utils/ServiceLocator";
5
6
  import { BaseMgr } from "./BaseMgr";
7
+ import { EventMgr } from "./EventMgr";
6
8
 
7
9
  interface ITimer {
8
10
  tick: number;
@@ -19,6 +21,7 @@ export class TimerMgr extends BaseMgr {
19
21
  static TAG: string = "TimerMgr";
20
22
 
21
23
  private timers: ITimer[] = [];
24
+ private _paused: boolean = false;
22
25
 
23
26
  onLoad(): void {
24
27
  super.onLoad();
@@ -31,6 +34,10 @@ export class TimerMgr extends BaseMgr {
31
34
  }
32
35
 
33
36
  ServiceLocator.Instance.register("TimerMgr", this);
37
+
38
+ // 监听生命周期事件,切后台暂停定时器
39
+ EventMgr.Instance.on(FrameworkBase.Message.LifeCycle_onGameHide, this._onGameHide, this);
40
+ EventMgr.Instance.on(FrameworkBase.Message.LifeCycle_onGameShow, this._onGameShow, this);
34
41
  }
35
42
 
36
43
  /**
@@ -38,10 +45,50 @@ export class TimerMgr extends BaseMgr {
38
45
  */
39
46
  public init(): void {
40
47
  this.timers = [];
48
+ this._paused = false;
41
49
 
42
50
  LogUtils.Instance.info(TimerMgr.TAG, "初始化完成");
43
51
  }
44
52
 
53
+ /**
54
+ * 暂停所有定时器
55
+ */
56
+ public pauseAll(): void {
57
+ if (this._paused) return;
58
+ this._paused = true;
59
+ LogUtils.Instance.info(TimerMgr.TAG, "定时器已暂停");
60
+ }
61
+
62
+ /**
63
+ * 恢复所有定时器
64
+ */
65
+ public resumeAll(): void {
66
+ if (!this._paused) return;
67
+ this._paused = false;
68
+ LogUtils.Instance.info(TimerMgr.TAG, "定时器已恢复");
69
+ }
70
+
71
+ /**
72
+ * 定时器是否暂停中
73
+ */
74
+ public isPaused(): boolean {
75
+ return this._paused;
76
+ }
77
+
78
+ private _onGameHide(): void {
79
+ this.pauseAll();
80
+ }
81
+
82
+ private _onGameShow(): void {
83
+ this.resumeAll();
84
+ }
85
+
86
+ onDestroy(): void {
87
+ EventMgr.Instance.off(FrameworkBase.Message.LifeCycle_onGameHide, this._onGameHide, this);
88
+ EventMgr.Instance.off(FrameworkBase.Message.LifeCycle_onGameShow, this._onGameShow, this);
89
+ super.onDestroy();
90
+ }
91
+
45
92
  /**
46
93
  * 添加定时器
47
94
  * @param delay 延迟时间(秒)
@@ -118,6 +165,8 @@ export class TimerMgr extends BaseMgr {
118
165
  }
119
166
 
120
167
  protected update(dt: number): void {
168
+ if (this._paused) return;
169
+
121
170
  for (let index = 0; index < this.timers.length; index++) {
122
171
  const item = this.timers[index];
123
172
  item.tick += dt;
@@ -1,10 +1,12 @@
1
- import { Component, Node, Button, Prefab, instantiate, screen, UITransform, v3, view, ResolutionPolicy, math, Tween, tween, Label, Color, Sprite, SpriteFrame, BlockInputEvents, Texture2D } from 'cc';
1
+ import { Component, Node, Button, Prefab, instantiate, screen, UITransform, v3, view, ResolutionPolicy, math, Tween, tween, Label, Color, Sprite, SpriteFrame, BlockInputEvents, Texture2D, Vec3 } from 'cc';
2
2
  import { FwkErrorCode } from '../Definition/FwkErrorDefinition';
3
3
  import { LogUtils } from '../Utils/LogUtils';
4
- import { ToastDuration } from '../Definition/UIDefinition';
4
+ import { ToastDuration, ToastPosition } from '../Definition/UIDefinition';
5
5
  import { BaseMgr } from './BaseMgr';
6
6
  import { ServiceLocator } from '../Utils/ServiceLocator';
7
7
  import type { ResMgr } from './ResMgr';
8
+ import type { SystemMgr } from './SystemMgr';
9
+ import type { InputMgr } from './InputMgr';
8
10
 
9
11
  /** UI 控制器基类 */
10
12
  export class UICtrl extends Component {
@@ -125,6 +127,8 @@ export class UIMgr extends BaseMgr {
125
127
  private toastHistoryMaxCount: number = 10;
126
128
 
127
129
  private resMgr: ResMgr = null;
130
+ private inputMgr: InputMgr = null;
131
+ private systemMgr: SystemMgr = null;
128
132
 
129
133
  /** 打印 UI 映射(调试用) */
130
134
  public print(): void {
@@ -283,6 +287,10 @@ export class UIMgr extends BaseMgr {
283
287
  this.resMgr = ServiceLocator.Instance.get<ResMgr>("ResMgr");
284
288
  }
285
289
 
290
+ if (this.inputMgr === null) {
291
+ this.inputMgr = ServiceLocator.Instance.get<InputMgr>("InputMgr");
292
+ }
293
+
286
294
  if (!this.resMgr) {
287
295
  return null;
288
296
  }
@@ -434,12 +442,18 @@ export class UIMgr extends BaseMgr {
434
442
  * 显示 Toast
435
443
  * @param content 显示内容
436
444
  * @param toastDuration 显示时长
445
+ * @param position 显示位置(ToastPosition 枚举或世界坐标 Vec3),默认居中
437
446
  */
438
- public showToast(content: string, toastDuration: ToastDuration = ToastDuration.Duration_Short): void {
447
+ public showToast(content: string, toastDuration: ToastDuration = ToastDuration.Duration_Short, position?: ToastPosition | Vec3): void {
439
448
  if (this.node_toastRoot === null) {
440
449
  return;
441
450
  }
442
451
 
452
+ // 延迟加载 SystemMgr(避免循环依赖)
453
+ if (this.systemMgr === null) {
454
+ this.systemMgr = ServiceLocator.Instance.get<SystemMgr>("SystemMgr");
455
+ }
456
+
443
457
  // 记录 Toast 历史
444
458
  this.toastHistory.push(content);
445
459
  if (this.toastHistory.length > this.toastHistoryMaxCount) {
@@ -463,6 +477,33 @@ export class UIMgr extends BaseMgr {
463
477
 
464
478
  this.node_toastRoot.active = true;
465
479
  this.node_toastRoot.getComponent(UITransform).width = textWidth + 80;
480
+
481
+ // 设置 Toast 位置
482
+ if (position !== undefined) {
483
+ if (position instanceof Vec3) {
484
+ // Vec3 世界坐标,转换为节点坐标
485
+ let localPos: Vec3 = this.node_toastRoot.getComponent(UITransform).convertToNodeSpaceAR(position);
486
+ this.node_toastRoot.setPosition(localPos.x, localPos.y);
487
+ } else {
488
+ // ToastPosition 枚举
489
+ let x: number = 0;
490
+ let y: number = 0;
491
+ if (this.systemMgr) {
492
+ let screenInfo = this.systemMgr.getScreenInfo();
493
+ let screenH: number = screenInfo.windowHeight;
494
+ let edgeOffset: number = 150; // 距屏幕边缘的距离
495
+ if (position === ToastPosition.Top) {
496
+ y = screenH / 2 - edgeOffset;
497
+ } else if (position === ToastPosition.Bottom) {
498
+ y = -screenH / 2 + edgeOffset;
499
+ }
500
+ // Center: x=0, y=0
501
+ }
502
+ // systemMgr 为空时默认居中(x=0, y=0)
503
+ this.node_toastRoot.setPosition(x, y);
504
+ }
505
+ }
506
+
466
507
  this.tween_toast = tween(this.node_toastRoot)
467
508
  .delay(showTime)
468
509
  .call(() => {
@@ -483,13 +524,10 @@ export class UIMgr extends BaseMgr {
483
524
  /**
484
525
  * 打开 Dialog
485
526
  * @param ui_name UI 名称
486
- * @param maskColor 遮罩颜色
487
- * @return Dialog 节点
527
+ * @param maskColor 遮罩颜色(默认半透明黑)
528
+ * @param closeOnMaskClick 点击遮罩是否关闭 Dialog(默认 false)
488
529
  */
489
- public openDialog(ui_name: string, maskColor?: Color): Node {
490
- if (!maskColor) {
491
- maskColor = new Color(51, 51, 51, 230);
492
- }
530
+ public openDialog(ui_name: string, maskColor: Color = new Color(51, 51, 51, 230), closeOnMaskClick: boolean = false): Node {
493
531
 
494
532
  const whitePixelData = new Uint8Array([maskColor.r, maskColor.g, maskColor.b, maskColor.a]);
495
533
  const texture = new Texture2D();
@@ -519,6 +557,18 @@ export class UIMgr extends BaseMgr {
519
557
  if (node_dialog) {
520
558
  this.node_dialogRoot.addChild(node_agent);
521
559
  this.node_dialogRoot.active = true;
560
+
561
+ // 点击遮罩关闭 Dialog
562
+ if (closeOnMaskClick) {
563
+ let maskTouchCallback = () => {
564
+ this.closeDialog(ui_name, node_dialog);
565
+ };
566
+ // 将回调存到 node_mask 上,方便 closeDialog 时清理
567
+ (node_mask as any)._maskTouchCallback = maskTouchCallback;
568
+ if (this.inputMgr) {
569
+ this.inputMgr.onNodeTouchEnd(node_mask, maskTouchCallback, this);
570
+ }
571
+ }
522
572
  } else {
523
573
  node_agent.destroy();
524
574
  }
@@ -546,6 +596,14 @@ export class UIMgr extends BaseMgr {
546
596
 
547
597
  if (hasNode) {
548
598
  let node_agent: Node = node.parent;
599
+ // 清理点击遮罩关闭的监听器(如果存在)
600
+ let node_mask: Node = node_agent.getChildByName("Node_Mask");
601
+ if (node_mask && (node_mask as any)._maskTouchCallback) {
602
+ if (this.inputMgr) {
603
+ this.inputMgr.offNodeTouchEnd(node_mask, (node_mask as any)._maskTouchCallback, this);
604
+ }
605
+ (node_mask as any)._maskTouchCallback = null;
606
+ }
549
607
  this.destroy_ui(ui_name, node);
550
608
  node_agent.destroy();
551
609
  }
@@ -574,6 +632,14 @@ export class UIMgr extends BaseMgr {
574
632
  if (child.name !== "Node_Mask") {
575
633
  let uiName = this._nodeToUIName(child);
576
634
  if (uiName) {
635
+ // 清理点击遮罩关闭的监听器(如果存在)
636
+ let node_mask: Node = agent.getChildByName("Node_Mask");
637
+ if (node_mask && (node_mask as any)._maskTouchCallback) {
638
+ if (this.inputMgr) {
639
+ this.inputMgr.offNodeTouchEnd(node_mask, (node_mask as any)._maskTouchCallback, this);
640
+ }
641
+ (node_mask as any)._maskTouchCallback = null;
642
+ }
577
643
  this.destroy_ui(uiName, child);
578
644
  closedCount++;
579
645
  break;
@@ -9,6 +9,7 @@ export class LogUtils {
9
9
  static TAG: string = "LogUtils";
10
10
 
11
11
  private isDebug: boolean = false;
12
+ private _timers: Map<string, number> = new Map();
12
13
 
13
14
  /**
14
15
  * 初始化日志模块
@@ -133,6 +134,32 @@ export class LogUtils {
133
134
  return `[${level}][${tag}][${timestamp}]: ${content}`;
134
135
  }
135
136
 
137
+ /**
138
+ * 开始计时
139
+ * @param label 计时标签
140
+ */
141
+ public timeStart(label: string): void {
142
+ if (!this.isDebug) return;
143
+ this._timers.set(label, Date.now());
144
+ console.log(this.formatLog("TIMER", label, "计时开始"));
145
+ }
146
+
147
+ /**
148
+ * 结束计时并输出耗时
149
+ * @param label 计时标签(需与 timeStart 匹配)
150
+ */
151
+ public timeEnd(label: string): void {
152
+ if (!this.isDebug) return;
153
+ const startTime = this._timers.get(label);
154
+ if (startTime === undefined) {
155
+ console.warn(this.formatLog("TIMER", label, "未找到计时起点"));
156
+ return;
157
+ }
158
+ const elapsed = Date.now() - startTime;
159
+ this._timers.delete(label);
160
+ console.log(this.formatLog("TIMER", label, `耗时 ${elapsed}ms`));
161
+ }
162
+
136
163
  /**
137
164
  * 格式化值
138
165
  */
@@ -23,4 +23,115 @@ export class ObjectUtils {
23
23
 
24
24
  return keysA.every(key => ObjectUtils.deepEqual(a[key], b[key]));
25
25
  }
26
+
27
+ // ======================== Base64 编解码 ========================
28
+
29
+ private static readonly BASE64_CHARS: string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
30
+
31
+ /**
32
+ * 将字符串转为 UTF-8 字节数组
33
+ */
34
+ private static utf8ToBytes(str: string): number[] {
35
+ const bytes: number[] = [];
36
+ for (let i = 0; i < str.length; i++) {
37
+ const code = str.charCodeAt(i);
38
+ if (code < 0x80) {
39
+ bytes.push(code);
40
+ } else if (code < 0x800) {
41
+ bytes.push(0xC0 | (code >> 6));
42
+ bytes.push(0x80 | (code & 0x3F));
43
+ } else if (code >= 0xD800 && code <= 0xDBFF) {
44
+ // 代理对(emoji 等)
45
+ const hi = code;
46
+ const lo = str.charCodeAt(++i);
47
+ const codePoint = ((hi - 0xD800) << 10) + (lo - 0xDC00) + 0x10000;
48
+ bytes.push(0xF0 | (codePoint >> 18));
49
+ bytes.push(0x80 | ((codePoint >> 12) & 0x3F));
50
+ bytes.push(0x80 | ((codePoint >> 6) & 0x3F));
51
+ bytes.push(0x80 | (codePoint & 0x3F));
52
+ } else {
53
+ bytes.push(0xE0 | (code >> 12));
54
+ bytes.push(0x80 | ((code >> 6) & 0x3F));
55
+ bytes.push(0x80 | (code & 0x3F));
56
+ }
57
+ }
58
+ return bytes;
59
+ }
60
+
61
+ /**
62
+ * 将 UTF-8 字节数组转为字符串
63
+ */
64
+ private static utf8FromBytes(bytes: number[]): string {
65
+ let result = "";
66
+ let i = 0;
67
+ while (i < bytes.length) {
68
+ const b1 = bytes[i++];
69
+ if (b1 < 0x80) {
70
+ result += String.fromCharCode(b1);
71
+ } else if (b1 < 0xE0) {
72
+ const b2 = bytes[i++];
73
+ result += String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
74
+ } else if (b1 < 0xF0) {
75
+ const b2 = bytes[i++];
76
+ const b3 = bytes[i++];
77
+ result += String.fromCharCode(((b1 & 0x0F) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
78
+ } else {
79
+ const b2 = bytes[i++];
80
+ const b3 = bytes[i++];
81
+ const b4 = bytes[i++];
82
+ const codePoint = ((b1 & 0x07) << 18) | ((b2 & 0x3F) << 12) | ((b3 & 0x3F) << 6) | (b4 & 0x3F);
83
+ // 转为代理对
84
+ const offset = codePoint - 0x10000;
85
+ result += String.fromCharCode(0xD800 + (offset >> 10), 0xDC00 + (offset & 0x3FF));
86
+ }
87
+ }
88
+ return result;
89
+ }
90
+
91
+ /**
92
+ * Base64 编码(纯 JS 实现,兼容所有小游戏/快应用平台,支持中文和 emoji)
93
+ * @param str 要编码的字符串
94
+ * @returns Base64 编码后的字符串
95
+ */
96
+ public static base64Encode(str: string): string {
97
+ const bytes = ObjectUtils.utf8ToBytes(str);
98
+ const chars = ObjectUtils.BASE64_CHARS;
99
+ let result = "";
100
+ for (let i = 0; i < bytes.length; i += 3) {
101
+ const a = bytes[i];
102
+ const b = i + 1 < bytes.length ? bytes[i + 1] : 0;
103
+ const c = i + 2 < bytes.length ? bytes[i + 2] : 0;
104
+ result += chars[(a >> 2) & 0x3F];
105
+ result += chars[((a << 4) | (b >> 4)) & 0x3F];
106
+ result += i + 1 < bytes.length ? chars[((b << 2) | (c >> 6)) & 0x3F] : "=";
107
+ result += i + 2 < bytes.length ? chars[c & 0x3F] : "=";
108
+ }
109
+ return result;
110
+ }
111
+
112
+ /**
113
+ * Base64 解码(纯 JS 实现,兼容所有小游戏/快应用平台,支持中文和 emoji)
114
+ * @param str Base64 编码的字符串
115
+ * @returns 解码后的原始字符串
116
+ */
117
+ public static base64Decode(str: string): string {
118
+ const chars = ObjectUtils.BASE64_CHARS;
119
+ const bytes: number[] = [];
120
+ // 去除空白字符
121
+ str = str.replace(/\s/g, "");
122
+ for (let i = 0; i < str.length; i += 4) {
123
+ const a = chars.indexOf(str[i]);
124
+ const b = chars.indexOf(str[i + 1]);
125
+ const c = str[i + 2] === "=" ? 0 : chars.indexOf(str[i + 2]);
126
+ const d = str[i + 3] === "=" ? 0 : chars.indexOf(str[i + 3]);
127
+ bytes.push((a << 2) | (b >> 4));
128
+ if (str[i + 2] !== "=") {
129
+ bytes.push(((b & 0xF) << 4) | (c >> 2));
130
+ }
131
+ if (str[i + 3] !== "=") {
132
+ bytes.push(((c & 0x3) << 6) | d);
133
+ }
134
+ }
135
+ return ObjectUtils.utf8FromBytes(bytes);
136
+ }
26
137
  }