@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.
- package/dist/index.mjs +18433 -0
- package/package.json +39 -0
- package/src/base/EventEmitter.js +187 -0
- package/src/base/Subsystem.js +183 -0
- package/src/base/index.js +6 -0
- package/src/core/Application/AppState.js +27 -0
- package/src/core/Application/Application.js +480 -0
- package/src/core/Application/DefaultConfig.js +63 -0
- package/src/core/Application/index.js +7 -0
- package/src/core/AssetManager/AssetManager.js +711 -0
- package/src/core/AssetManager/AssetTypes.js +108 -0
- package/src/core/AssetManager/adapters/IResourceAdapter.js +50 -0
- package/src/core/AssetManager/adapters/PixiAdapter.js +90 -0
- package/src/core/AssetManager/core/RefCounter.js +167 -0
- package/src/core/AssetManager/core/ResourceCache.js +281 -0
- package/src/core/AssetManager/core/ResourceEntry.js +191 -0
- package/src/core/AssetManager/core/ResourceEventBus.js +127 -0
- package/src/core/AssetManager/handlers/IResourceHandler.js +45 -0
- package/src/core/AssetManager/index.js +26 -0
- package/src/core/AssetManager/plugins/IResourcePlugin.js +56 -0
- package/src/core/Camera/Camera.js +534 -0
- package/src/core/Camera/CameraTypes.js +59 -0
- package/src/core/Camera/index.js +13 -0
- package/src/core/ConfigSystem/ConfigLayer.js +230 -0
- package/src/core/ConfigSystem/ConfigLayerType.js +31 -0
- package/src/core/ConfigSystem/ConfigMerger.js +50 -0
- package/src/core/ConfigSystem/ConfigPath.js +96 -0
- package/src/core/ConfigSystem/ConfigSystem.js +378 -0
- package/src/core/ConfigSystem/ConfigTypes.js +29 -0
- package/src/core/ConfigSystem/ConfigWatcher.js +84 -0
- package/src/core/ConfigSystem/index.js +19 -0
- package/src/core/EventBus/EventBus.js +797 -0
- package/src/core/EventBus/Interceptor.js +26 -0
- package/src/core/EventBus/PerformanceStats.js +105 -0
- package/src/core/EventBus/index.js +7 -0
- package/src/core/Lifecycle/Lifecycle.js +265 -0
- package/src/core/Lifecycle/LifecycleState.js +31 -0
- package/src/core/Lifecycle/index.js +6 -0
- package/src/core/Logger/ConsoleHandler.js +55 -0
- package/src/core/Logger/LogHandler.js +34 -0
- package/src/core/Logger/LogLevel.js +38 -0
- package/src/core/Logger/Logger.js +234 -0
- package/src/core/Logger/index.js +8 -0
- package/src/core/PluginSystem/Plugin.js +156 -0
- package/src/core/PluginSystem/PluginEntry.js +102 -0
- package/src/core/PluginSystem/PluginState.js +30 -0
- package/src/core/PluginSystem/PluginSystem.js +530 -0
- package/src/core/PluginSystem/index.js +8 -0
- package/src/core/PoolManager/DefaultConfig.js +19 -0
- package/src/core/PoolManager/ObjectPool.js +326 -0
- package/src/core/PoolManager/PoolManager.js +253 -0
- package/src/core/PoolManager/PoolTypes.js +92 -0
- package/src/core/PoolManager/index.js +15 -0
- package/src/core/SceneManager/FadeTransition.js +64 -0
- package/src/core/SceneManager/MaskTransition.js +234 -0
- package/src/core/SceneManager/Scene.js +221 -0
- package/src/core/SceneManager/SceneManager.js +658 -0
- package/src/core/SceneManager/SceneState.js +29 -0
- package/src/core/SceneManager/SlideTransition.js +113 -0
- package/src/core/SceneManager/Transition.js +102 -0
- package/src/core/SceneManager/ZoomTransition.js +119 -0
- package/src/core/SceneManager/index.js +12 -0
- package/src/core/SpriteGPULayer/SpriteGPULayer.js +321 -0
- package/src/core/SpriteGPULayer/SpriteGPULayerTypes.js +49 -0
- package/src/core/SpriteGPULayer/index.js +11 -0
- package/src/core/Timeline/Clip.js +101 -0
- package/src/core/Timeline/DefaultConfig.js +41 -0
- package/src/core/Timeline/Easing.js +176 -0
- package/src/core/Timeline/Timeline.js +458 -0
- package/src/core/Timeline/TimelineTypes.js +89 -0
- package/src/core/Timeline/Track.js +202 -0
- package/src/core/Timeline/index.js +22 -0
- package/src/errors/ConfigError.js +47 -0
- package/src/errors/LifecycleError.js +58 -0
- package/src/errors/NimbusError.js +43 -0
- package/src/errors/PluginError.js +138 -0
- package/src/errors/ResourceError.js +166 -0
- package/src/errors/SceneError.js +124 -0
- package/src/errors/SubsystemError.js +68 -0
- package/src/errors/index.js +53 -0
- package/src/index.js +86 -0
- package/src/types/index.js +141 -0
- package/src/utils/MemoryChecker.js +374 -0
- package/src/utils/Validator.js +60 -0
- package/src/utils/index.js +84 -0
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Nimbus2D - Timeline 时间线主类
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// 职责:
|
|
5
|
+
// 1. 管理多条 Track,编排时间线动画
|
|
6
|
+
// 2. 提供播放控制(play/pause/stop/seek)
|
|
7
|
+
// 3. 支持循环(loop/loopCount)、反向播放(autoReverse)、自动销毁(autoDestroy)
|
|
8
|
+
// 4. 通过 EventBus 广播时间线事件(play/pause/stop/seek/loop/complete/destroy)
|
|
9
|
+
// 5. 每帧 update 推进所有 Track 的当前时间
|
|
10
|
+
//
|
|
11
|
+
// 设计决策:
|
|
12
|
+
// - 不继承任何类:Timeline 是独立的编排器
|
|
13
|
+
// - EventBus 注入:通过公开方法 setEventBus 注入(不放在构造函数)
|
|
14
|
+
// 避免 Timeline 构造时强制依赖 EventBus,符合依赖注入最佳实践
|
|
15
|
+
// - 事件发射 try-catch:避免事件总线异常影响主流程
|
|
16
|
+
// - 销毁幂等:通过 _isDestroyed 标志避免重复销毁导致崩溃
|
|
17
|
+
// - autoReverse + loop 计数:正向到终点 + 反向到起点 = 1 次完整循环
|
|
18
|
+
// 符合用户直觉(设置 loopCount=2 看到 2 次往返动画)
|
|
19
|
+
// - autoReverse + 非 loop:正向到终点 → 反向到起点 → 完成
|
|
20
|
+
// - duration 自动计算:addTrack/removeTrack 后重新计算总时长
|
|
21
|
+
// - currentTime 范围约束:[0, duration],避免越界
|
|
22
|
+
// - 单 Track 异常隔离:try-catch 包裹 track.evaluate,避免影响其他 Track
|
|
23
|
+
//
|
|
24
|
+
// 四期新增(任务 1.6/4.4):Timeline 动画引擎主类
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
import { Track } from './Track.js'
|
|
28
|
+
import { TimelineEventType, TimelineState, TimelineError } from './TimelineTypes.js'
|
|
29
|
+
|
|
30
|
+
class Timeline {
|
|
31
|
+
/**
|
|
32
|
+
* 创建 Timeline 实例
|
|
33
|
+
* @param {Object} [options={}] - 配置
|
|
34
|
+
* @param {boolean} [options.loop=false] - 是否循环
|
|
35
|
+
* @param {number} [options.loopCount=Infinity] - 循环次数(Infinity 表示无限)
|
|
36
|
+
* @param {boolean} [options.autoReverse=false] - 反向播放(到达终点后反向回到起点)
|
|
37
|
+
* @param {boolean} [options.autoDestroy=false] - 完成后自动销毁
|
|
38
|
+
*/
|
|
39
|
+
constructor(options = {}) {
|
|
40
|
+
/** @type {Track[]} 轨道列表 */
|
|
41
|
+
this._tracks = []
|
|
42
|
+
|
|
43
|
+
/** @type {boolean} 是否播放中(含暂停状态) */
|
|
44
|
+
this._isPlaying = false
|
|
45
|
+
|
|
46
|
+
/** @type {boolean} 是否暂停 */
|
|
47
|
+
this._isPaused = false
|
|
48
|
+
|
|
49
|
+
/** @type {number} 当前时间(秒) */
|
|
50
|
+
this._currentTime = 0
|
|
51
|
+
|
|
52
|
+
/** @type {number} 总时长(秒,自动计算) */
|
|
53
|
+
this._duration = 0
|
|
54
|
+
|
|
55
|
+
/** @type {boolean} 是否循环 */
|
|
56
|
+
this._loop = options.loop ?? false
|
|
57
|
+
|
|
58
|
+
/** @type {number} 循环次数上限(必须为正数,否则降级到 Infinity) */
|
|
59
|
+
this._loopCount = (typeof options.loopCount === 'number' && options.loopCount > 0)
|
|
60
|
+
? options.loopCount
|
|
61
|
+
: Infinity
|
|
62
|
+
|
|
63
|
+
/** @type {number} 当前已完成循环次数 */
|
|
64
|
+
this._currentLoop = 0
|
|
65
|
+
|
|
66
|
+
/** @type {boolean} 是否反向播放 */
|
|
67
|
+
this._autoReverse = options.autoReverse ?? false
|
|
68
|
+
|
|
69
|
+
/** @type {boolean} 完成后自动销毁 */
|
|
70
|
+
this._autoDestroy = options.autoDestroy ?? false
|
|
71
|
+
|
|
72
|
+
/** @type {number} 播放方向(1=正向,-1=反向) */
|
|
73
|
+
this._direction = 1
|
|
74
|
+
|
|
75
|
+
/** @type {EventBus|null} 事件总线(通过 setEventBus 注入) */
|
|
76
|
+
this._eventBus = null
|
|
77
|
+
|
|
78
|
+
/** @type {boolean} 是否已销毁 */
|
|
79
|
+
this._isDestroyed = false
|
|
80
|
+
|
|
81
|
+
/** @type {string} 当前状态(TimelineState 枚举值) */
|
|
82
|
+
this._state = TimelineState.IDLE
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --------------------------------------------------------------------------
|
|
86
|
+
// 轨道管理
|
|
87
|
+
// --------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 添加轨道
|
|
91
|
+
* @param {Track} track - 属性轨道
|
|
92
|
+
* @returns {Timeline} this(链式)
|
|
93
|
+
* @throws {TimelineError} track 不是 Track 实例
|
|
94
|
+
*/
|
|
95
|
+
addTrack(track) {
|
|
96
|
+
if (this._isDestroyed) return this
|
|
97
|
+
if (!(track instanceof Track)) {
|
|
98
|
+
throw new TimelineError('addTrack 参数必须是 Track 实例', 'TIMELINE_INVALID_TRACK')
|
|
99
|
+
}
|
|
100
|
+
this._tracks.push(track)
|
|
101
|
+
this._recalculateDuration()
|
|
102
|
+
return this
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 移除轨道
|
|
107
|
+
* @param {Track} track - 属性轨道
|
|
108
|
+
* @returns {boolean} 是否移除成功
|
|
109
|
+
*/
|
|
110
|
+
removeTrack(track) {
|
|
111
|
+
if (this._isDestroyed) return false
|
|
112
|
+
const index = this._tracks.indexOf(track)
|
|
113
|
+
if (index === -1) return false
|
|
114
|
+
this._tracks.splice(index, 1)
|
|
115
|
+
this._recalculateDuration()
|
|
116
|
+
return true
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 清空所有轨道(不销毁 Track 实例,由调用方负责)
|
|
121
|
+
*/
|
|
122
|
+
clearTracks() {
|
|
123
|
+
if (this._isDestroyed) return
|
|
124
|
+
this._tracks.length = 0
|
|
125
|
+
this._recalculateDuration()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* 获取轨道数量
|
|
130
|
+
* @returns {number}
|
|
131
|
+
*/
|
|
132
|
+
get trackCount() {
|
|
133
|
+
return this._tracks.length
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// --------------------------------------------------------------------------
|
|
137
|
+
// 播放控制
|
|
138
|
+
// --------------------------------------------------------------------------
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 开始播放
|
|
142
|
+
* 若已暂停则恢复播放;若已完成则从头开始播放
|
|
143
|
+
* @returns {Timeline} this(链式)
|
|
144
|
+
*/
|
|
145
|
+
play() {
|
|
146
|
+
if (this._isDestroyed) return this
|
|
147
|
+
// 已在播放中(非暂停):无操作
|
|
148
|
+
if (this._isPlaying && !this._isPaused) return this
|
|
149
|
+
|
|
150
|
+
// 从完成状态恢复:重置循环计数、方向、时间
|
|
151
|
+
if (this._state === TimelineState.COMPLETED) {
|
|
152
|
+
this._currentTime = 0
|
|
153
|
+
this._currentLoop = 0
|
|
154
|
+
this._direction = 1
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this._isPlaying = true
|
|
158
|
+
this._isPaused = false
|
|
159
|
+
this._state = TimelineState.PLAYING
|
|
160
|
+
this._emit(TimelineEventType.PLAY, { timeline: this })
|
|
161
|
+
return this
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 暂停播放
|
|
166
|
+
* @returns {Timeline} this(链式)
|
|
167
|
+
*/
|
|
168
|
+
pause() {
|
|
169
|
+
if (this._isDestroyed) return this
|
|
170
|
+
// 未播放或已暂停:无操作
|
|
171
|
+
if (!this._isPlaying || this._isPaused) return this
|
|
172
|
+
|
|
173
|
+
this._isPaused = true
|
|
174
|
+
this._state = TimelineState.PAUSED
|
|
175
|
+
this._emit(TimelineEventType.PAUSE, { timeline: this })
|
|
176
|
+
return this
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 停止播放并重置时间
|
|
181
|
+
* @returns {Timeline} this(链式)
|
|
182
|
+
*/
|
|
183
|
+
stop() {
|
|
184
|
+
if (this._isDestroyed) return this
|
|
185
|
+
const wasPlaying = this._isPlaying
|
|
186
|
+
|
|
187
|
+
this._isPlaying = false
|
|
188
|
+
this._isPaused = false
|
|
189
|
+
this._currentTime = 0
|
|
190
|
+
this._currentLoop = 0
|
|
191
|
+
this._direction = 1
|
|
192
|
+
this._state = TimelineState.IDLE
|
|
193
|
+
|
|
194
|
+
// 仅在实际播放中触发 stop 事件
|
|
195
|
+
if (wasPlaying) {
|
|
196
|
+
this._emit(TimelineEventType.STOP, { timeline: this })
|
|
197
|
+
}
|
|
198
|
+
return this
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 跳转到指定时间
|
|
203
|
+
* @param {number} time - 目标时间(秒)
|
|
204
|
+
* @returns {Timeline} this(链式)
|
|
205
|
+
*/
|
|
206
|
+
seek(time) {
|
|
207
|
+
if (this._isDestroyed) return this
|
|
208
|
+
// 校验 time 是否为有效数字(防御 NaN/Infinity)
|
|
209
|
+
if (typeof time !== 'number' || Number.isNaN(time)) {
|
|
210
|
+
return this
|
|
211
|
+
}
|
|
212
|
+
// 范围约束 [0, duration]
|
|
213
|
+
const clampedTime = time < 0 ? 0 : time > this._duration ? this._duration : time
|
|
214
|
+
this._currentTime = clampedTime
|
|
215
|
+
this._evaluate()
|
|
216
|
+
this._emit(TimelineEventType.SEEK, { timeline: this, time: clampedTime })
|
|
217
|
+
return this
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// --------------------------------------------------------------------------
|
|
221
|
+
// 状态查询
|
|
222
|
+
// --------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
/** @type {boolean} 是否播放中(不含暂停) */
|
|
225
|
+
get isPlaying() { return this._isPlaying && !this._isPaused }
|
|
226
|
+
|
|
227
|
+
/** @type {boolean} 是否暂停 */
|
|
228
|
+
get isPaused() { return this._isPaused }
|
|
229
|
+
|
|
230
|
+
/** @type {number} 总时长(秒) */
|
|
231
|
+
get duration() { return this._duration }
|
|
232
|
+
|
|
233
|
+
/** @type {number} 当前时间(秒) */
|
|
234
|
+
get currentTime() { return this._currentTime }
|
|
235
|
+
|
|
236
|
+
/** @type {number} 当前进度 [0,1] */
|
|
237
|
+
get progress() { return this._duration > 0 ? this._currentTime / this._duration : 0 }
|
|
238
|
+
|
|
239
|
+
/** @type {string} 当前状态(TimelineState 枚举值) */
|
|
240
|
+
get state() { return this._state }
|
|
241
|
+
|
|
242
|
+
/** @type {boolean} 是否已销毁 */
|
|
243
|
+
get isDestroyed() { return this._isDestroyed }
|
|
244
|
+
|
|
245
|
+
/** @type {number} 当前已完成循环次数 */
|
|
246
|
+
get currentLoop() { return this._currentLoop }
|
|
247
|
+
|
|
248
|
+
// --------------------------------------------------------------------------
|
|
249
|
+
// 帧更新
|
|
250
|
+
// --------------------------------------------------------------------------
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* 每帧更新(由外部主循环调用,如 Application.update 或 Scene.update)
|
|
254
|
+
* @param {number} delta - 帧间隔(秒)
|
|
255
|
+
*/
|
|
256
|
+
update(delta) {
|
|
257
|
+
// 已销毁、未播放、已暂停:不推进
|
|
258
|
+
if (this._isDestroyed || !this._isPlaying || this._isPaused) return
|
|
259
|
+
// 校验 delta 是否为有效数字(防御 NaN/非数字输入导致 _currentTime 污染)
|
|
260
|
+
if (typeof delta !== 'number' || Number.isNaN(delta)) return
|
|
261
|
+
|
|
262
|
+
// 推进当前时间
|
|
263
|
+
this._currentTime += delta * this._direction
|
|
264
|
+
|
|
265
|
+
// === 边界处理 ===
|
|
266
|
+
if (this._direction > 0 && this._currentTime >= this._duration) {
|
|
267
|
+
// 正向到达终点
|
|
268
|
+
if (!this._handleForwardEnd()) return
|
|
269
|
+
} else if (this._direction < 0 && this._currentTime <= 0) {
|
|
270
|
+
// 反向到达起点
|
|
271
|
+
if (!this._handleBackwardEnd()) return
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 评估所有轨道
|
|
275
|
+
this._evaluate()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* 处理正向到达终点
|
|
280
|
+
* @returns {boolean} true=继续播放,false=已结束(不再 evaluate)
|
|
281
|
+
* @private
|
|
282
|
+
*/
|
|
283
|
+
_handleForwardEnd() {
|
|
284
|
+
this._currentTime = this._duration
|
|
285
|
+
|
|
286
|
+
// autoReverse 模式:切换为反向继续播放
|
|
287
|
+
if (this._autoReverse) {
|
|
288
|
+
this._direction = -1
|
|
289
|
+
// autoReverse 模式下:正向到终点不算一次完整循环
|
|
290
|
+
// 反向到起点后才算一次完整循环(在 _handleBackwardEnd 中计数)
|
|
291
|
+
// 若不循环:反向到起点后完成
|
|
292
|
+
// 若循环:反向到起点后计数 + 检查 loopCount
|
|
293
|
+
return true
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 非 autoReverse:循环检查
|
|
297
|
+
if (this._loop) {
|
|
298
|
+
this._currentLoop++
|
|
299
|
+
if (this._currentLoop >= this._loopCount) {
|
|
300
|
+
this._complete()
|
|
301
|
+
return false
|
|
302
|
+
}
|
|
303
|
+
// 重置时间到起点,继续下一轮循环
|
|
304
|
+
this._currentTime = 0
|
|
305
|
+
this._emit(TimelineEventType.LOOP, { timeline: this, loopCount: this._currentLoop })
|
|
306
|
+
return true
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 不循环:完成
|
|
310
|
+
this._complete()
|
|
311
|
+
return false
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 处理反向到达起点(仅在 autoReverse 模式下触发)
|
|
316
|
+
* @returns {boolean} true=继续播放,false=已结束
|
|
317
|
+
* @private
|
|
318
|
+
*/
|
|
319
|
+
_handleBackwardEnd() {
|
|
320
|
+
this._currentTime = 0
|
|
321
|
+
|
|
322
|
+
// autoReverse 模式:一次完整循环(正向+反向)完成
|
|
323
|
+
if (this._autoReverse) {
|
|
324
|
+
if (this._loop) {
|
|
325
|
+
// 计数 +1(正向+反向算一次循环)
|
|
326
|
+
this._currentLoop++
|
|
327
|
+
if (this._currentLoop >= this._loopCount) {
|
|
328
|
+
this._complete()
|
|
329
|
+
return false
|
|
330
|
+
}
|
|
331
|
+
// 切换为正向,继续下一轮循环
|
|
332
|
+
this._direction = 1
|
|
333
|
+
this._emit(TimelineEventType.LOOP, { timeline: this, loopCount: this._currentLoop })
|
|
334
|
+
return true
|
|
335
|
+
}
|
|
336
|
+
// autoReverse 但不循环:反向到起点后完成
|
|
337
|
+
this._complete()
|
|
338
|
+
return false
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// 非 autoReverse 不会反向到达起点(_direction 应该是 1)
|
|
342
|
+
// 防御性处理:直接完成
|
|
343
|
+
this._complete()
|
|
344
|
+
return false
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* 评估所有轨道(带异常隔离)
|
|
349
|
+
* @private
|
|
350
|
+
*/
|
|
351
|
+
_evaluate() {
|
|
352
|
+
for (const track of this._tracks) {
|
|
353
|
+
try {
|
|
354
|
+
track.evaluate(this._currentTime)
|
|
355
|
+
} catch (e) {
|
|
356
|
+
// 单个 Track 异常不影响其他 Track
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* 完成播放
|
|
363
|
+
* @private
|
|
364
|
+
*/
|
|
365
|
+
_complete() {
|
|
366
|
+
// 设置到终点并应用最后一帧(确保最终状态正确)
|
|
367
|
+
this._currentTime = this._direction > 0 ? this._duration : 0
|
|
368
|
+
this._evaluate()
|
|
369
|
+
this._isPlaying = false
|
|
370
|
+
this._isPaused = false
|
|
371
|
+
this._direction = 1
|
|
372
|
+
this._state = TimelineState.COMPLETED
|
|
373
|
+
this._emit(TimelineEventType.COMPLETE, { timeline: this })
|
|
374
|
+
// 自动销毁
|
|
375
|
+
if (this._autoDestroy) {
|
|
376
|
+
this.destroy()
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* 重新计算总时长(所有 Track 中最晚的 endTime)
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
384
|
+
_recalculateDuration() {
|
|
385
|
+
let max = 0
|
|
386
|
+
for (const track of this._tracks) {
|
|
387
|
+
const end = track.endTime
|
|
388
|
+
if (end > max) max = end
|
|
389
|
+
}
|
|
390
|
+
this._duration = max
|
|
391
|
+
// 若 currentTime 超出新 duration,约束到新 duration
|
|
392
|
+
if (this._currentTime > this._duration) {
|
|
393
|
+
this._currentTime = this._duration
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// --------------------------------------------------------------------------
|
|
398
|
+
// EventBus
|
|
399
|
+
// --------------------------------------------------------------------------
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* 注入 EventBus(公开方法,由 Scene 或用户调用)
|
|
403
|
+
* @param {EventBus|null} eventBus - 事件总线,传 null 可断开
|
|
404
|
+
* @returns {Timeline} this(链式)
|
|
405
|
+
*/
|
|
406
|
+
setEventBus(eventBus) {
|
|
407
|
+
if (this._isDestroyed) return this
|
|
408
|
+
this._eventBus = eventBus
|
|
409
|
+
return this
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 发射事件(带异常捕获)
|
|
414
|
+
* @param {string} type - 事件类型
|
|
415
|
+
* @param {Object} data - 事件数据
|
|
416
|
+
* @private
|
|
417
|
+
*/
|
|
418
|
+
_emit(type, data) {
|
|
419
|
+
if (!this._eventBus) return
|
|
420
|
+
try {
|
|
421
|
+
this._eventBus.emit(type, data)
|
|
422
|
+
} catch (e) {
|
|
423
|
+
// 事件总线异常忽略,避免影响主流程
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// --------------------------------------------------------------------------
|
|
428
|
+
// 销毁
|
|
429
|
+
// --------------------------------------------------------------------------
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* 销毁时间线(幂等,可重复调用)
|
|
433
|
+
* 停止播放 + 销毁所有 Track + 清空引用 + 发射 destroy 事件
|
|
434
|
+
*/
|
|
435
|
+
destroy() {
|
|
436
|
+
if (this._isDestroyed) return
|
|
437
|
+
this._isDestroyed = true
|
|
438
|
+
this._isPlaying = false
|
|
439
|
+
this._isPaused = false
|
|
440
|
+
this._state = TimelineState.DESTROYED
|
|
441
|
+
|
|
442
|
+
// 销毁所有 Track(带异常隔离)
|
|
443
|
+
for (const track of this._tracks) {
|
|
444
|
+
try {
|
|
445
|
+
track.destroy()
|
|
446
|
+
} catch (e) {
|
|
447
|
+
// 单个 Track 销毁异常忽略
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
this._tracks.length = 0
|
|
451
|
+
|
|
452
|
+
// 发射销毁事件(在 eventBus 被置 null 之前)
|
|
453
|
+
this._emit(TimelineEventType.DESTROY, { timeline: this })
|
|
454
|
+
this._eventBus = null
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
export { Timeline }
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Nimbus2D - Timeline 类型定义
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// 职责:
|
|
5
|
+
// 1. 定义 Timeline 相关的事件类型枚举(TimelineEventType)
|
|
6
|
+
// 2. 定义 Timeline 状态枚举(TimelineState)
|
|
7
|
+
// 3. 定义 Timeline 错误类(TimelineError)
|
|
8
|
+
//
|
|
9
|
+
// 设计决策:
|
|
10
|
+
// - 事件类型使用 Object.freeze 冻结,防止运行时被篡改
|
|
11
|
+
// - 状态枚举使用 Object.freeze 冻结
|
|
12
|
+
// - 错误类继承 NimbusError,附加 module='Timeline' 字段
|
|
13
|
+
// - 与 Camera/SceneManager 的类型定义风格保持一致
|
|
14
|
+
//
|
|
15
|
+
// 四期新增(任务 1.6):Timeline 动画引擎的类型定义
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
import { NimbusError } from '../../errors/NimbusError.js'
|
|
19
|
+
|
|
20
|
+
// ----------------------------------------------------------------------------
|
|
21
|
+
// 事件类型枚举
|
|
22
|
+
// ----------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Timeline 事件类型
|
|
26
|
+
* @enum {string}
|
|
27
|
+
* @readonly
|
|
28
|
+
*/
|
|
29
|
+
const TimelineEventType = Object.freeze({
|
|
30
|
+
/** 播放开始(play 调用) */
|
|
31
|
+
PLAY: 'timeline:play',
|
|
32
|
+
/** 暂停(pause 调用) */
|
|
33
|
+
PAUSE: 'timeline:pause',
|
|
34
|
+
/** 停止(stop 调用,currentTime 重置为 0) */
|
|
35
|
+
STOP: 'timeline:stop',
|
|
36
|
+
/** 跳转(seek 调用) */
|
|
37
|
+
SEEK: 'timeline:seek',
|
|
38
|
+
/** 循环(每次循环开始) */
|
|
39
|
+
LOOP: 'timeline:loop',
|
|
40
|
+
/** 完成(非循环模式到达终点,或循环次数到达) */
|
|
41
|
+
COMPLETE: 'timeline:complete',
|
|
42
|
+
/** 销毁(destroy 调用) */
|
|
43
|
+
DESTROY: 'timeline:destroy'
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
// ----------------------------------------------------------------------------
|
|
47
|
+
// 状态枚举
|
|
48
|
+
// ----------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Timeline 状态
|
|
52
|
+
* @enum {string}
|
|
53
|
+
* @readonly
|
|
54
|
+
*/
|
|
55
|
+
const TimelineState = Object.freeze({
|
|
56
|
+
/** 已创建(未播放) */
|
|
57
|
+
IDLE: 'idle',
|
|
58
|
+
/** 播放中 */
|
|
59
|
+
PLAYING: 'playing',
|
|
60
|
+
/** 暂停中 */
|
|
61
|
+
PAUSED: 'paused',
|
|
62
|
+
/** 已完成 */
|
|
63
|
+
COMPLETED: 'completed',
|
|
64
|
+
/** 已销毁 */
|
|
65
|
+
DESTROYED: 'destroyed'
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// ----------------------------------------------------------------------------
|
|
69
|
+
// 错误类
|
|
70
|
+
// ----------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Timeline 错误基类
|
|
74
|
+
* 所有 Timeline 错误构造函数设置 module='Timeline'
|
|
75
|
+
* @extends NimbusError
|
|
76
|
+
*/
|
|
77
|
+
class TimelineError extends NimbusError {
|
|
78
|
+
/**
|
|
79
|
+
* 构造函数
|
|
80
|
+
* @param {string} message - 错误消息
|
|
81
|
+
* @param {string} [code='TIMELINE_ERROR'] - 错误码
|
|
82
|
+
* @param {Error|null} [cause=null] - 原始错误
|
|
83
|
+
*/
|
|
84
|
+
constructor(message, code = 'TIMELINE_ERROR', cause = null) {
|
|
85
|
+
super(message, code, 'Timeline', cause)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { TimelineEventType, TimelineState, TimelineError }
|