@nimbus2d/core 0.0.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.
Files changed (85) hide show
  1. package/dist/index.mjs +18433 -0
  2. package/package.json +39 -0
  3. package/src/base/EventEmitter.js +187 -0
  4. package/src/base/Subsystem.js +183 -0
  5. package/src/base/index.js +6 -0
  6. package/src/core/Application/AppState.js +27 -0
  7. package/src/core/Application/Application.js +480 -0
  8. package/src/core/Application/DefaultConfig.js +63 -0
  9. package/src/core/Application/index.js +7 -0
  10. package/src/core/AssetManager/AssetManager.js +711 -0
  11. package/src/core/AssetManager/AssetTypes.js +108 -0
  12. package/src/core/AssetManager/adapters/IResourceAdapter.js +50 -0
  13. package/src/core/AssetManager/adapters/PixiAdapter.js +90 -0
  14. package/src/core/AssetManager/core/RefCounter.js +167 -0
  15. package/src/core/AssetManager/core/ResourceCache.js +281 -0
  16. package/src/core/AssetManager/core/ResourceEntry.js +191 -0
  17. package/src/core/AssetManager/core/ResourceEventBus.js +127 -0
  18. package/src/core/AssetManager/handlers/IResourceHandler.js +45 -0
  19. package/src/core/AssetManager/index.js +26 -0
  20. package/src/core/AssetManager/plugins/IResourcePlugin.js +56 -0
  21. package/src/core/Camera/Camera.js +534 -0
  22. package/src/core/Camera/CameraTypes.js +59 -0
  23. package/src/core/Camera/index.js +13 -0
  24. package/src/core/ConfigSystem/ConfigLayer.js +230 -0
  25. package/src/core/ConfigSystem/ConfigLayerType.js +31 -0
  26. package/src/core/ConfigSystem/ConfigMerger.js +50 -0
  27. package/src/core/ConfigSystem/ConfigPath.js +96 -0
  28. package/src/core/ConfigSystem/ConfigSystem.js +378 -0
  29. package/src/core/ConfigSystem/ConfigTypes.js +29 -0
  30. package/src/core/ConfigSystem/ConfigWatcher.js +84 -0
  31. package/src/core/ConfigSystem/index.js +19 -0
  32. package/src/core/EventBus/EventBus.js +797 -0
  33. package/src/core/EventBus/Interceptor.js +26 -0
  34. package/src/core/EventBus/PerformanceStats.js +105 -0
  35. package/src/core/EventBus/index.js +7 -0
  36. package/src/core/Lifecycle/Lifecycle.js +265 -0
  37. package/src/core/Lifecycle/LifecycleState.js +31 -0
  38. package/src/core/Lifecycle/index.js +6 -0
  39. package/src/core/Logger/ConsoleHandler.js +55 -0
  40. package/src/core/Logger/LogHandler.js +34 -0
  41. package/src/core/Logger/LogLevel.js +38 -0
  42. package/src/core/Logger/Logger.js +234 -0
  43. package/src/core/Logger/index.js +8 -0
  44. package/src/core/PluginSystem/Plugin.js +156 -0
  45. package/src/core/PluginSystem/PluginEntry.js +102 -0
  46. package/src/core/PluginSystem/PluginState.js +30 -0
  47. package/src/core/PluginSystem/PluginSystem.js +530 -0
  48. package/src/core/PluginSystem/index.js +8 -0
  49. package/src/core/PoolManager/DefaultConfig.js +19 -0
  50. package/src/core/PoolManager/ObjectPool.js +326 -0
  51. package/src/core/PoolManager/PoolManager.js +253 -0
  52. package/src/core/PoolManager/PoolTypes.js +92 -0
  53. package/src/core/PoolManager/index.js +15 -0
  54. package/src/core/SceneManager/FadeTransition.js +64 -0
  55. package/src/core/SceneManager/MaskTransition.js +234 -0
  56. package/src/core/SceneManager/Scene.js +221 -0
  57. package/src/core/SceneManager/SceneManager.js +658 -0
  58. package/src/core/SceneManager/SceneState.js +29 -0
  59. package/src/core/SceneManager/SlideTransition.js +113 -0
  60. package/src/core/SceneManager/Transition.js +102 -0
  61. package/src/core/SceneManager/ZoomTransition.js +119 -0
  62. package/src/core/SceneManager/index.js +12 -0
  63. package/src/core/SpriteGPULayer/SpriteGPULayer.js +321 -0
  64. package/src/core/SpriteGPULayer/SpriteGPULayerTypes.js +49 -0
  65. package/src/core/SpriteGPULayer/index.js +11 -0
  66. package/src/core/Timeline/Clip.js +101 -0
  67. package/src/core/Timeline/DefaultConfig.js +41 -0
  68. package/src/core/Timeline/Easing.js +176 -0
  69. package/src/core/Timeline/Timeline.js +458 -0
  70. package/src/core/Timeline/TimelineTypes.js +89 -0
  71. package/src/core/Timeline/Track.js +202 -0
  72. package/src/core/Timeline/index.js +22 -0
  73. package/src/errors/ConfigError.js +47 -0
  74. package/src/errors/LifecycleError.js +58 -0
  75. package/src/errors/NimbusError.js +43 -0
  76. package/src/errors/PluginError.js +138 -0
  77. package/src/errors/ResourceError.js +166 -0
  78. package/src/errors/SceneError.js +124 -0
  79. package/src/errors/SubsystemError.js +68 -0
  80. package/src/errors/index.js +53 -0
  81. package/src/index.js +86 -0
  82. package/src/types/index.js +141 -0
  83. package/src/utils/MemoryChecker.js +374 -0
  84. package/src/utils/Validator.js +60 -0
  85. package/src/utils/index.js +84 -0
@@ -0,0 +1,658 @@
1
+ // ============================================================================
2
+ // Nimbus2D - SceneManager 场景管理
3
+ // ----------------------------------------------------------------------------
4
+ // 职责:
5
+ // 1. 多活动场景并行 + 独立 stage + 顺序叠加
6
+ // 2. 场景栈 push/pop(与多场景同时支持)
7
+ // 3. 完整生命周期 API:register/start/stop/pause/resume/setVisible/setZOrder
8
+ // 4. 过渡效果核心内置 + 可扩展(FadeTransition、SlideTransition)
9
+ // 5. Scene 基类继承 EventEmitter + 5 个生命周期方法
10
+ // 6. 场景间通信仅通过 EventBus
11
+ // 7. Scene 内置资源引用 + 自动释放
12
+ //
13
+ // 继承关系:
14
+ // EventEmitter → Subsystem → SceneManager(Lifecycle 内第三个初始化)
15
+ // ============================================================================
16
+
17
+ import { Subsystem } from '../../base/Subsystem.js'
18
+ import { SceneState } from './SceneState.js'
19
+ import { SceneError, SceneNotFoundError, SceneDuplicateError, SceneStateError } from '../../errors/SceneError.js'
20
+ import { FadeTransition } from './FadeTransition.js'
21
+ import { SlideTransition } from './SlideTransition.js'
22
+ import { MaskTransition } from './MaskTransition.js'
23
+ import { ZoomTransition } from './ZoomTransition.js'
24
+
25
+ class SceneManager extends Subsystem {
26
+ /**
27
+ * 构造函数
28
+ * @param {import('../EventBus/EventBus.js').EventBus} eventBus - 全局事件总线
29
+ * @param {import('../ConfigSystem/ConfigSystem.js').ConfigSystem} config - 配置系统
30
+ * @param {import('../Logger/Logger.js').Logger} logger - 日志系统
31
+ * @param {import('../AssetManager/AssetManager.js').AssetManager} assetManager - 资源管理
32
+ * @param {object} pixi - PixiJS Application 实例
33
+ */
34
+ constructor(eventBus, config, logger, assetManager, pixi) {
35
+ super(eventBus, config, logger)
36
+
37
+ /** @type {import('../AssetManager/AssetManager.js').AssetManager} 资源管理器 */
38
+ this._assetManager = assetManager
39
+
40
+ /** @type {object} PixiJS Application 实例 */
41
+ this._pixi = pixi
42
+
43
+ /** @type {Map<string, import('./Scene.js').Scene>} 场景映射(name → scene) */
44
+ this._scenes = new Map()
45
+
46
+ /** @type {Set<string>} 运行中的场景名称集合 */
47
+ this._activeScenes = new Set()
48
+
49
+ /** @type {string[]} 场景栈 */
50
+ this._stack = []
51
+ }
52
+
53
+ // --------------------------------------------------------------------------
54
+ // 场景注册
55
+ // --------------------------------------------------------------------------
56
+
57
+ /**
58
+ * 注册场景
59
+ * @param {import('./Scene.js').Scene} scene - 场景实例
60
+ * @returns {Promise<void>}
61
+ * @throws {SceneDuplicateError} 场景重复注册
62
+ */
63
+ async register(scene) {
64
+ if (this._scenes.has(scene.name)) {
65
+ throw new SceneDuplicateError(scene.name)
66
+ }
67
+
68
+ // 注入 AssetManager 引用(决策 106)
69
+ scene._setAssetManager(this._assetManager)
70
+
71
+ // 设置场景状态为 REGISTERED(决策 106)
72
+ scene._setState(SceneState.REGISTERED)
73
+
74
+ this._scenes.set(scene.name, scene)
75
+
76
+ this.emit('scene:registered', { name: scene.name, scene })
77
+ }
78
+
79
+ /**
80
+ * 注销场景(销毁实例)
81
+ * @param {string} name - 场景名称
82
+ * @returns {Promise<void>}
83
+ */
84
+ async unregister(name) {
85
+ const scene = this._getSceneOrThrow(name)
86
+
87
+ // 如果场景正在运行,先停止
88
+ if (scene.state === SceneState.RUNNING || scene.state === SceneState.PAUSED) {
89
+ await this.stop(name)
90
+ }
91
+
92
+ // 从栈中移除
93
+ this._stack = this._stack.filter((n) => n !== name)
94
+
95
+ // 从运行集合移除
96
+ this._activeScenes.delete(name)
97
+
98
+ // 从 pixi stage 移除
99
+ if (scene.stage && this._pixi && this._pixi.stage) {
100
+ this._pixi.stage.removeChild(scene.stage)
101
+ }
102
+
103
+ // 销毁场景
104
+ await scene.destroy()
105
+
106
+ // 从映射移除
107
+ this._scenes.delete(name)
108
+
109
+ this.emit('scene:unregistered', { name })
110
+ }
111
+
112
+ // --------------------------------------------------------------------------
113
+ // 场景生命周期
114
+ // --------------------------------------------------------------------------
115
+
116
+ /**
117
+ * 启动场景
118
+ * @param {string} name - 场景名称
119
+ * @returns {Promise<void>}
120
+ */
121
+ async start(name) {
122
+ const scene = this._getSceneOrThrow(name)
123
+
124
+ // 状态检查
125
+ if (scene.state !== SceneState.REGISTERED && scene.state !== SceneState.STOPPED) {
126
+ throw new SceneStateError(name, scene.state, `${SceneState.REGISTERED} 或 ${SceneState.STOPPED}`)
127
+ }
128
+
129
+ // 记录是否为首次启动(REGISTERED → STARTING),用于决定是否调用 onCreate
130
+ const isFirstStart = scene.state === SceneState.REGISTERED
131
+
132
+ // 设置为启动中
133
+ scene._setState(SceneState.STARTING)
134
+
135
+ try {
136
+ // 首次启动调用 onCreate(仅一次,REGISTERED → STARTING 时调用)
137
+ if (isFirstStart) {
138
+ await scene.onCreate()
139
+ }
140
+
141
+ // 调用 onStart(每次 start 都调用)
142
+ await scene.onStart()
143
+
144
+ // 设置为运行中
145
+ scene._setState(SceneState.RUNNING)
146
+
147
+ // 添加到运行集合
148
+ this._activeScenes.add(name)
149
+
150
+ // 添加到 pixi stage
151
+ if (scene.stage && this._pixi && this._pixi.stage) {
152
+ this._pixi.stage.addChild(scene.stage)
153
+ }
154
+
155
+ this.emit('scene:start', { name, scene })
156
+ } catch (e) {
157
+ // 启动失败回滚状态
158
+ scene._setState(SceneState.STOPPED)
159
+ this.emit('scene:error', { name, error: e })
160
+ throw e
161
+ }
162
+ }
163
+
164
+ /**
165
+ * 停止场景
166
+ * @param {string} name - 场景名称
167
+ * @returns {Promise<void>}
168
+ */
169
+ async stop(name) {
170
+ const scene = this._getSceneOrThrow(name)
171
+
172
+ // 状态检查
173
+ if (scene.state !== SceneState.RUNNING && scene.state !== SceneState.PAUSED) {
174
+ throw new SceneStateError(name, scene.state, `${SceneState.RUNNING} 或 ${SceneState.PAUSED}`)
175
+ }
176
+
177
+ try {
178
+ // 调用 onStop
179
+ await scene.onStop()
180
+
181
+ // 设置为已停止
182
+ scene._setState(SceneState.STOPPED)
183
+
184
+ // 从运行集合移除
185
+ this._activeScenes.delete(name)
186
+
187
+ // 从 pixi stage 移除
188
+ if (scene.stage && this._pixi && this._pixi.stage) {
189
+ this._pixi.stage.removeChild(scene.stage)
190
+ }
191
+
192
+ this.emit('scene:stop', { name, scene })
193
+ } catch (e) {
194
+ this.emit('scene:error', { name, error: e })
195
+ throw e
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 暂停场景
201
+ * @param {string} name - 场景名称
202
+ */
203
+ pause(name) {
204
+ const scene = this._getSceneOrThrow(name)
205
+
206
+ if (scene.state !== SceneState.RUNNING) {
207
+ throw new SceneStateError(name, scene.state, SceneState.RUNNING)
208
+ }
209
+
210
+ scene._setState(SceneState.PAUSED)
211
+ scene.onPause()
212
+
213
+ this.emit('scene:pause', { name, scene })
214
+ }
215
+
216
+ /**
217
+ * 恢复场景
218
+ * @param {string} name - 场景名称
219
+ */
220
+ resume(name) {
221
+ const scene = this._getSceneOrThrow(name)
222
+
223
+ if (scene.state !== SceneState.PAUSED) {
224
+ throw new SceneStateError(name, scene.state, SceneState.PAUSED)
225
+ }
226
+
227
+ scene._setState(SceneState.RUNNING)
228
+ scene.onResume()
229
+
230
+ this.emit('scene:resume', { name, scene })
231
+ }
232
+
233
+ // --------------------------------------------------------------------------
234
+ // 场景栈
235
+ // --------------------------------------------------------------------------
236
+
237
+ /**
238
+ * 入栈(启动并置于栈顶)
239
+ * @param {string} name - 场景名称
240
+ * @param {import('./Transition.js').Transition} [transition] - 过渡效果
241
+ * @returns {Promise<void>}
242
+ */
243
+ async push(name, transition = null) {
244
+ // 如果场景未注册,先注册(需用户自行注册)
245
+ if (!this._scenes.has(name)) {
246
+ throw new SceneNotFoundError(name)
247
+ }
248
+
249
+ // 获取栈顶场景
250
+ const topName = this._stack.length > 0 ? this._stack[this._stack.length - 1] : null
251
+ const topScene = topName ? this._scenes.get(topName) : null
252
+
253
+ // 启动目标场景
254
+ await this.start(name)
255
+
256
+ // 入栈
257
+ this._stack.push(name)
258
+
259
+ // 执行过渡效果
260
+ if (transition && topScene) {
261
+ const toScene = this._scenes.get(name)
262
+ await this._doTransition(topScene, toScene, transition)
263
+ }
264
+
265
+ this.emit('scene:push', { name, stack: [...this._stack] })
266
+ }
267
+
268
+ /**
269
+ * 出栈(停止栈顶场景)
270
+ * @param {import('./Transition.js').Transition} [transition] - 过渡效果
271
+ * @returns {Promise<void>}
272
+ */
273
+ async pop(transition = null) {
274
+ if (this._stack.length === 0) return
275
+
276
+ // 弹出栈顶
277
+ const topName = this._stack.pop()
278
+ const topScene = this._scenes.get(topName)
279
+
280
+ // 获取新的栈顶场景
281
+ const newTopName = this._stack.length > 0 ? this._stack[this._stack.length - 1] : null
282
+ const newTopScene = newTopName ? this._scenes.get(newTopName) : null
283
+
284
+ // 执行过渡效果
285
+ if (transition && newTopScene) {
286
+ await this._doTransition(topScene, newTopScene, transition)
287
+ }
288
+
289
+ // 停止栈顶场景
290
+ await this.stop(topName)
291
+
292
+ this.emit('scene:pop', { name: topName, stack: [...this._stack] })
293
+ }
294
+
295
+ // --------------------------------------------------------------------------
296
+ // 视觉控制
297
+ // --------------------------------------------------------------------------
298
+
299
+ /**
300
+ * 设置场景可见性
301
+ * @param {string} name - 场景名称
302
+ * @param {boolean} visible - 是否可见
303
+ */
304
+ setVisible(name, visible) {
305
+ const scene = this._getSceneOrThrow(name)
306
+ scene._isVisible = visible
307
+ if (scene.stage) {
308
+ scene.stage.visible = visible
309
+ }
310
+ }
311
+
312
+ /**
313
+ * 设置场景层级
314
+ * @param {string} name - 场景名称
315
+ * @param {number} zIndex - 层级值
316
+ */
317
+ setZOrder(name, zIndex) {
318
+ const scene = this._getSceneOrThrow(name)
319
+ scene._zIndex = zIndex
320
+ if (scene.stage) {
321
+ scene.stage.zIndex = zIndex
322
+ }
323
+ }
324
+
325
+ // --------------------------------------------------------------------------
326
+ // 过渡效果
327
+ // --------------------------------------------------------------------------
328
+
329
+ /**
330
+ * 场景过渡
331
+ * @param {string} from - 源场景名称
332
+ * @param {string} to - 目标场景名称
333
+ * @param {import('./Transition.js').Transition} transition - 过渡效果
334
+ * @returns {Promise<void>}
335
+ */
336
+ async transition(from, to, transition) {
337
+ const fromScene = this._getSceneOrThrow(from)
338
+ const toScene = this._getSceneOrThrow(to)
339
+
340
+ // 确保目标场景已启动
341
+ if (toScene.state !== SceneState.RUNNING) {
342
+ await this.start(to)
343
+ }
344
+
345
+ await this._doTransition(fromScene, toScene, transition)
346
+ }
347
+
348
+ /**
349
+ * 带转场动画的场景切换(四期新增 1.5/3.3)
350
+ * 与 transition() 共存,提供更便捷的转场 API:
351
+ * 1. 支持字符串效果名('fade'/'slide'/'mask'/'zoom')
352
+ * 2. 支持自定义 Transition 实例
353
+ * 3. 自动解析当前活动场景作为源场景
354
+ * 4. 自动启动目标场景、停止源场景
355
+ * 5. 完成后自动清理转场资源
356
+ *
357
+ * @param {string} sceneName - 目标场景名称
358
+ * @param {string|import('./Transition.js').Transition} [effect='fade'] - 转场效果名称或实例
359
+ * @param {Object} [options={}] - 转场选项
360
+ * @param {number} [options.duration=500] - 转场持续时间(毫秒)
361
+ * @param {string} [options.easing='easeInOut'] - 缓动函数(linear/easeIn/easeOut/easeInOut)
362
+ * @param {Function} [options.onTransitionStart] - 转场开始回调 (currentScene, targetScene) => void
363
+ * @param {Function} [options.onTransitionEnd] - 转场结束回调 (currentScene, targetScene) => void
364
+ * @returns {Promise<void>}
365
+ * @throws {SceneNotFoundError} 目标场景未注册
366
+ */
367
+ async transitionTo(sceneName, effect = 'fade', options = {}) {
368
+ // 1. 获取目标场景(不存在则抛错)
369
+ const targetScene = this._getSceneOrThrow(sceneName)
370
+
371
+ // 2. 解析转场效果(字符串名 → Transition 实例)
372
+ const transition = this._resolveTransition(effect, options)
373
+
374
+ // 3. 获取当前活动场景作为源场景
375
+ const currentScene = this._getCurrentActiveScene()
376
+
377
+ // 4. 广播转场开始事件 + 触发开始回调
378
+ this.emit('scene:transition:start', {
379
+ from: currentScene ? currentScene.name : null,
380
+ to: sceneName
381
+ })
382
+ if (typeof options.onTransitionStart === 'function') {
383
+ try {
384
+ options.onTransitionStart(currentScene, targetScene)
385
+ } catch (e) {
386
+ // 用户回调异常不中断转场流程
387
+ }
388
+ }
389
+
390
+ try {
391
+ // 5. 启动目标场景(如果未启动)
392
+ if (targetScene.state !== SceneState.RUNNING) {
393
+ await this.start(sceneName)
394
+ }
395
+
396
+ // 6. 执行转场动画(直接调用 execute,避免 _doTransition 重复发射事件)
397
+ try {
398
+ await transition.execute(currentScene, targetScene)
399
+ } catch (e) {
400
+ // 转场动画异常不中断流程,记录到 scene:error 事件
401
+ this.emit('scene:error', { name: sceneName, error: e })
402
+ }
403
+
404
+ // 7. 停止源场景(如果与目标不同)
405
+ if (currentScene && currentScene !== targetScene) {
406
+ await this.stop(currentScene.name)
407
+ }
408
+ } finally {
409
+ // 8. 清理转场资源(如 MaskTransition 的 Graphics)
410
+ if (transition && typeof transition.destroy === 'function') {
411
+ try {
412
+ transition.destroy()
413
+ } catch (e) {
414
+ // 销毁异常忽略
415
+ }
416
+ }
417
+ }
418
+
419
+ // 9. 广播转场结束事件 + 触发结束回调
420
+ this.emit('scene:transition:end', {
421
+ from: currentScene ? currentScene.name : null,
422
+ to: sceneName
423
+ })
424
+ if (typeof options.onTransitionEnd === 'function') {
425
+ try {
426
+ options.onTransitionEnd(currentScene, targetScene)
427
+ } catch (e) {
428
+ // 用户回调异常不中断后续流程
429
+ }
430
+ }
431
+ }
432
+
433
+ // --------------------------------------------------------------------------
434
+ // 查询方法
435
+ // --------------------------------------------------------------------------
436
+
437
+ /**
438
+ * 获取场景实例
439
+ * @param {string} name - 场景名称
440
+ * @returns {import('./Scene.js').Scene|null}
441
+ */
442
+ get(name) {
443
+ return this._scenes.get(name) || null
444
+ }
445
+
446
+ /**
447
+ * 是否注册
448
+ * @param {string} name - 场景名称
449
+ * @returns {boolean}
450
+ */
451
+ has(name) {
452
+ return this._scenes.has(name)
453
+ }
454
+
455
+ /**
456
+ * 是否运行中
457
+ * @param {string} name - 场景名称
458
+ * @returns {boolean}
459
+ */
460
+ isActive(name) {
461
+ return this._activeScenes.has(name)
462
+ }
463
+
464
+ /**
465
+ * 列出所有场景名称
466
+ * @returns {string[]}
467
+ */
468
+ list() {
469
+ return [...this._scenes.keys()]
470
+ }
471
+
472
+ /**
473
+ * 列出运行中场景名称
474
+ * @returns {string[]}
475
+ */
476
+ listActive() {
477
+ return [...this._activeScenes]
478
+ }
479
+
480
+ /**
481
+ * 获取场景栈
482
+ * @returns {string[]}
483
+ */
484
+ getStack() {
485
+ return [...this._stack]
486
+ }
487
+
488
+ // --------------------------------------------------------------------------
489
+ // 生命周期(继承自 Subsystem)
490
+ // --------------------------------------------------------------------------
491
+
492
+ /**
493
+ * 初始化
494
+ * @returns {Promise<void>}
495
+ */
496
+ async initialize() {
497
+ await super.initialize()
498
+ }
499
+
500
+ /**
501
+ * 每帧更新(仅更新运行中且未暂停的场景)
502
+ * @param {number} delta - 帧间隔(秒)
503
+ */
504
+ update(delta) {
505
+ for (const name of this._activeScenes) {
506
+ const scene = this._scenes.get(name)
507
+ if (scene && scene.state === SceneState.RUNNING) {
508
+ try {
509
+ scene.update(delta)
510
+ } catch (e) {
511
+ this.emit('scene:error', { name, error: e })
512
+ }
513
+ }
514
+ }
515
+ }
516
+
517
+ /**
518
+ * 销毁所有场景
519
+ * @returns {Promise<void>}
520
+ */
521
+ async destroy() {
522
+ if (this._isDestroyed) return
523
+
524
+ // 反向停止所有运行中的场景
525
+ for (const name of [...this._activeScenes]) {
526
+ try {
527
+ const scene = this._scenes.get(name)
528
+ if (scene) {
529
+ await scene.onStop()
530
+ scene._setState(SceneState.STOPPED)
531
+ if (scene.stage && this._pixi && this._pixi.stage) {
532
+ this._pixi.stage.removeChild(scene.stage)
533
+ }
534
+ }
535
+ } catch (e) {
536
+ // 销毁阶段错误不中断
537
+ }
538
+ }
539
+
540
+ // 销毁所有场景
541
+ for (const [name, scene] of this._scenes) {
542
+ try {
543
+ await scene.destroy()
544
+ this.emit('scene:destroyed', { name })
545
+ } catch (e) {
546
+ // 忽略销毁错误
547
+ }
548
+ }
549
+
550
+ this._scenes.clear()
551
+ this._activeScenes.clear()
552
+ this._stack = []
553
+ this._assetManager = null
554
+ this._pixi = null
555
+
556
+ await super.destroy()
557
+ }
558
+
559
+ // --------------------------------------------------------------------------
560
+ // 私有方法
561
+ // --------------------------------------------------------------------------
562
+
563
+ /**
564
+ * 获取场景实例,不存在则抛错
565
+ * @param {string} name - 场景名称
566
+ * @returns {import('./Scene.js').Scene}
567
+ * @throws {SceneNotFoundError}
568
+ * @private
569
+ */
570
+ _getSceneOrThrow(name) {
571
+ const scene = this._scenes.get(name)
572
+ if (!scene) {
573
+ throw new SceneNotFoundError(name)
574
+ }
575
+ return scene
576
+ }
577
+
578
+ /**
579
+ * 执行过渡效果
580
+ * @param {import('./Scene.js').Scene} fromScene - 源场景
581
+ * @param {import('./Scene.js').Scene} toScene - 目标场景
582
+ * @param {import('./Transition.js').Transition} transition - 过渡效果
583
+ * @returns {Promise<void>}
584
+ * @private
585
+ */
586
+ async _doTransition(fromScene, toScene, transition) {
587
+ if (!transition) return
588
+
589
+ this.emit('scene:transition:start', {
590
+ from: fromScene ? fromScene.name : null,
591
+ to: toScene ? toScene.name : null
592
+ })
593
+
594
+ try {
595
+ await transition.execute(fromScene, toScene)
596
+ } catch (e) {
597
+ // 过渡错误不中断流程
598
+ }
599
+
600
+ this.emit('scene:transition:end', {
601
+ from: fromScene ? fromScene.name : null,
602
+ to: toScene ? toScene.name : null
603
+ })
604
+ }
605
+
606
+ /**
607
+ * 解析转场效果(四期新增 1.5/3.3)
608
+ * 支持两种输入:
609
+ * 1. 字符串效果名:'fade'/'slide'/'mask'/'zoom',自动创建对应 Transition 实例
610
+ * 2. Transition 实例:直接返回(用户自定义转场)
611
+ * 未知的效果名降级到 FadeTransition
612
+ *
613
+ * @param {string|import('./Transition.js').Transition} effect - 转场效果名称或实例
614
+ * @param {Object} options - 转场选项(传给 Transition 构造函数)
615
+ * @returns {import('./Transition.js').Transition} Transition 实例
616
+ * @private
617
+ */
618
+ _resolveTransition(effect, options) {
619
+ // 字符串效果名:根据映射创建实例
620
+ if (typeof effect === 'string') {
621
+ switch (effect) {
622
+ case 'fade':
623
+ return new FadeTransition(options)
624
+ case 'slide':
625
+ return new SlideTransition(options)
626
+ case 'mask':
627
+ return new MaskTransition(options)
628
+ case 'zoom':
629
+ return new ZoomTransition(options)
630
+ default:
631
+ // 未知效果名降级到 FadeTransition(最通用)
632
+ return new FadeTransition(options)
633
+ }
634
+ }
635
+ // 非字符串:假设是 Transition 实例,直接返回
636
+ return effect
637
+ }
638
+
639
+ /**
640
+ * 获取当前活动场景(四期新增 1.5/3.3)
641
+ * 遍历 _activeScenes,返回第一个 RUNNING 状态的场景
642
+ * 用于 transitionTo() 自动解析源场景
643
+ *
644
+ * @returns {import('./Scene.js').Scene|null} 当前活动场景,无则返回 null
645
+ * @private
646
+ */
647
+ _getCurrentActiveScene() {
648
+ for (const name of this._activeScenes) {
649
+ const scene = this._scenes.get(name)
650
+ if (scene && scene.state === SceneState.RUNNING) {
651
+ return scene
652
+ }
653
+ }
654
+ return null
655
+ }
656
+ }
657
+
658
+ export { SceneManager }
@@ -0,0 +1,29 @@
1
+ // ============================================================================
2
+ // Nimbus2D - 场景状态枚举
3
+ // ----------------------------------------------------------------------------
4
+ // 定义场景的所有合法状态。
5
+ //
6
+ // 状态转换流程:
7
+ // 无 → REGISTERED → STARTING → RUNNING ⇄ PAUSED → STOPPED → DESTROYED
8
+ // ============================================================================
9
+
10
+ /**
11
+ * 场景状态枚举
12
+ * @enum {string}
13
+ */
14
+ const SceneState = {
15
+ /** 已注册(SceneManager.register 后) */
16
+ REGISTERED: 'REGISTERED',
17
+ /** 启动中 */
18
+ STARTING: 'STARTING',
19
+ /** 运行中 */
20
+ RUNNING: 'RUNNING',
21
+ /** 已暂停 */
22
+ PAUSED: 'PAUSED',
23
+ /** 已停止(可重新 start) */
24
+ STOPPED: 'STOPPED',
25
+ /** 已销毁(不可恢复) */
26
+ DESTROYED: 'DESTROYED'
27
+ }
28
+
29
+ export { SceneState }