@eva/spine-base 2.0.1-beta.9 → 2.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/EVA.plugin.spineBase.js +103 -12
- package/dist/EVA.plugin.spineBase.min.js +1 -1
- package/dist/spine-base.cjs.js +322 -15
- package/dist/spine-base.cjs.prod.js +2 -2
- package/dist/spine-base.d.ts +221 -0
- package/dist/spine-base.esm.js +322 -15
- package/package.json +4 -4
package/dist/spine-base.cjs.js
CHANGED
|
@@ -4,8 +4,8 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
4
4
|
|
|
5
5
|
var eva_js = require('@eva/eva.js');
|
|
6
6
|
var pluginRenderer = require('@eva/plugin-renderer');
|
|
7
|
-
var inspectorDecorator = require('@eva/inspector-decorator');
|
|
8
7
|
var pixi_js = require('pixi.js');
|
|
8
|
+
var inspectorDecorator = require('@eva/inspector-decorator');
|
|
9
9
|
|
|
10
10
|
/*! *****************************************************************************
|
|
11
11
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
@@ -38,15 +38,84 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
38
38
|
});
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Spine 骨骼动画组件
|
|
43
|
+
*
|
|
44
|
+
* Spine 组件用于播放 Esoteric Software 的 Spine 骨骼动画。
|
|
45
|
+
* 支持骨骼动画播放控制、动画混合、附件替换等高级功能,
|
|
46
|
+
* 适用于角色动画、复杂特效等需要骨骼动画的场景。
|
|
47
|
+
*
|
|
48
|
+
* 主要功能:
|
|
49
|
+
* - 骨骼动画播放和控制
|
|
50
|
+
* - 动画轨道管理(多动画并行)
|
|
51
|
+
* - 动画混合过渡
|
|
52
|
+
* - 骨骼和附件访问
|
|
53
|
+
* - 支持 Spine 3.6 和 3.8 版本
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* // 创建 Spine 动画
|
|
58
|
+
* const character = new GameObject('character');
|
|
59
|
+
* const spine = new Spine({
|
|
60
|
+
* resource: 'heroSpine', // Spine 资源
|
|
61
|
+
* animationName: 'idle', // 默认动画
|
|
62
|
+
* autoPlay: true, // 自动播放
|
|
63
|
+
* scale: 0.5 // 缩放比例
|
|
64
|
+
* });
|
|
65
|
+
* character.addComponent(spine);
|
|
66
|
+
*
|
|
67
|
+
* // 播放动画
|
|
68
|
+
* spine.play('walk', true); // 循环播放 walk 动画
|
|
69
|
+
*
|
|
70
|
+
* // 停止动画
|
|
71
|
+
* spine.stop();
|
|
72
|
+
*
|
|
73
|
+
* // 动画混合
|
|
74
|
+
* spine.setMix('idle', 'walk', 0.3); // 设置过渡时间
|
|
75
|
+
* spine.play('walk');
|
|
76
|
+
*
|
|
77
|
+
* // 添加动画队列
|
|
78
|
+
* spine.play('attack', false); // 播放攻击动画
|
|
79
|
+
* spine.addAnimation('idle', 0, true); // 攻击完成后回到 idle
|
|
80
|
+
*
|
|
81
|
+
* // 替换附件(换装)
|
|
82
|
+
* spine.setAttachment('weapon', 'sword'); // 将武器槽替换为剑
|
|
83
|
+
*
|
|
84
|
+
* // 访问骨骼
|
|
85
|
+
* const headBone = spine.getBone('head');
|
|
86
|
+
* if (headBone) {
|
|
87
|
+
* headBone.rotation = 15; // 旋转头部
|
|
88
|
+
* }
|
|
89
|
+
*
|
|
90
|
+
* // 多轨道动画
|
|
91
|
+
* spine.play('walk', true, 0); // 轨道0:身体动画
|
|
92
|
+
* spine.play('shoot', false, 1); // 轨道1:上半身动画
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
41
95
|
class Spine extends eva_js.Component {
|
|
42
96
|
constructor() {
|
|
43
97
|
super(...arguments);
|
|
98
|
+
/** Spine 资源名称 */
|
|
44
99
|
this.resource = '';
|
|
100
|
+
/** 动画缩放比例 */
|
|
45
101
|
this.scale = 1;
|
|
102
|
+
/** 当前播放的动画名称 */
|
|
46
103
|
this.animationName = '';
|
|
104
|
+
/** 是否自动播放动画 */
|
|
47
105
|
this.autoPlay = true;
|
|
106
|
+
/** 是否保留资源(销毁时不释放) */
|
|
107
|
+
this.keepResource = false;
|
|
108
|
+
/** 挂载到插槽的 GameObject 映射(GameObject -> { slot, wrapper }) */
|
|
109
|
+
this._slotGameObjects = new Map();
|
|
110
|
+
/** 等待容器就绪的 slot 挂载请求 */
|
|
111
|
+
this._pendingSlotObjects = [];
|
|
112
|
+
/** 等待执行的动画操作队列 */
|
|
48
113
|
this.waitExecuteInfos = [];
|
|
49
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* 设置骨架实例
|
|
117
|
+
* 当骨架加载完成后自动执行等待队列中的动画操作
|
|
118
|
+
*/
|
|
50
119
|
set armature(val) {
|
|
51
120
|
this._armature = val;
|
|
52
121
|
if (!val)
|
|
@@ -65,17 +134,36 @@ class Spine extends eva_js.Component {
|
|
|
65
134
|
}
|
|
66
135
|
this.waitExecuteInfos = [];
|
|
67
136
|
}
|
|
137
|
+
/** 获取骨架实例 */
|
|
68
138
|
get armature() {
|
|
69
139
|
return this._armature;
|
|
70
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* 初始化组件
|
|
143
|
+
* @param obj - 初始化参数
|
|
144
|
+
* @param obj.resource - Spine 资源名称
|
|
145
|
+
* @param obj.animationName - 默认动画名称
|
|
146
|
+
* @param obj.scale - 缩放比例
|
|
147
|
+
* @param obj.autoPlay - 是否自动播放
|
|
148
|
+
*/
|
|
71
149
|
init(obj) {
|
|
72
150
|
if (!obj)
|
|
73
151
|
return;
|
|
74
152
|
Object.assign(this, obj);
|
|
75
153
|
}
|
|
154
|
+
/** 组件销毁时调用 */
|
|
76
155
|
onDestroy() {
|
|
77
156
|
this.destroied = true;
|
|
78
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* 播放指定动画
|
|
160
|
+
*
|
|
161
|
+
* 如果骨架尚未加载完成,动画操作将被加入等待队列。
|
|
162
|
+
*
|
|
163
|
+
* @param name - 动画名称,不指定则使用 animationName 属性
|
|
164
|
+
* @param loopAnimation - 是否循环播放,默认跟随 autoPlay 属性
|
|
165
|
+
* @param track - 动画轨道编号,默认为 0
|
|
166
|
+
*/
|
|
79
167
|
play(name, loopAnimation, track) {
|
|
80
168
|
try {
|
|
81
169
|
const loop = loopAnimation !== null && loopAnimation !== void 0 ? loopAnimation : this.autoPlay;
|
|
@@ -85,6 +173,12 @@ class Spine extends eva_js.Component {
|
|
|
85
173
|
this.waitExecuteInfos.push({
|
|
86
174
|
playType: true,
|
|
87
175
|
name,
|
|
176
|
+
/**
|
|
177
|
+
* 在 v1.2.2 之前,Spine 动画的 autoPlay 为 true,动画会循环播放 https://github.com/eva-engine/eva.js/pull/164/files#diff-46e9ae36c04e7a0abedc1e14fd9d1c4e81d8386e9bb851f85971ccdba8957804L131
|
|
178
|
+
* 在 v1.2.2 之前,Spine 动画在每加载完( armature 设置之前)调用 play 是不生效的, 在 v1.2.2 [#164](https://github.com/eva-engine/eva.js/pull/164) 解决了这个问题
|
|
179
|
+
* 解决了不生效的问题以后,加载完成之前调用 play 默认循环是false,导致 autoPlay 下本来循环动画不循环了,和之前表现不一致
|
|
180
|
+
* 为了解决这个问题,在 autoPlay 的情况下,未加载完之前调用 play ,默认循环播放,除非设置不循环参数
|
|
181
|
+
*/
|
|
88
182
|
loop,
|
|
89
183
|
track,
|
|
90
184
|
});
|
|
@@ -100,6 +194,13 @@ class Spine extends eva_js.Component {
|
|
|
100
194
|
console.log(e);
|
|
101
195
|
}
|
|
102
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* 停止指定轨道的动画
|
|
199
|
+
*
|
|
200
|
+
* 如果骨架尚未加载完成,停止操作将被加入等待队列。
|
|
201
|
+
*
|
|
202
|
+
* @param track - 动画轨道编号,默认为 0
|
|
203
|
+
*/
|
|
103
204
|
stop(track) {
|
|
104
205
|
if (!this.armature) {
|
|
105
206
|
this.waitExecuteInfos.push({
|
|
@@ -113,6 +214,16 @@ class Spine extends eva_js.Component {
|
|
|
113
214
|
}
|
|
114
215
|
this.armature.state.setEmptyAnimation(track, 0);
|
|
115
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* 在当前动画之后添加新动画到队列
|
|
219
|
+
*
|
|
220
|
+
* 用于创建动画序列,当前动画播放完毕后自动播放下一个动画。
|
|
221
|
+
*
|
|
222
|
+
* @param name - 动画名称
|
|
223
|
+
* @param delay - 延迟时间(秒)
|
|
224
|
+
* @param loop - 是否循环播放
|
|
225
|
+
* @param track - 动画轨道编号,默认为 0
|
|
226
|
+
*/
|
|
116
227
|
addAnimation(name, delay, loop, track) {
|
|
117
228
|
try {
|
|
118
229
|
if (!this.armature) {
|
|
@@ -128,12 +239,27 @@ class Spine extends eva_js.Component {
|
|
|
128
239
|
console.log(e);
|
|
129
240
|
}
|
|
130
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* 设置两个动画之间的混合过渡时间
|
|
244
|
+
*
|
|
245
|
+
* 当从一个动画切换到另一个动画时,会在指定时间内进行平滑过渡。
|
|
246
|
+
*
|
|
247
|
+
* @param from - 起始动画名称
|
|
248
|
+
* @param to - 目标动画名称
|
|
249
|
+
* @param duration - 过渡时长(秒)
|
|
250
|
+
*/
|
|
131
251
|
setMix(from, to, duration) {
|
|
132
252
|
if (!this.armature) ;
|
|
133
253
|
else {
|
|
134
254
|
this.armature.state.data.setMix(from, to, duration);
|
|
135
255
|
}
|
|
136
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* 获取指定轨道当前播放的动画名称
|
|
259
|
+
*
|
|
260
|
+
* @param track - 动画轨道编号,默认为 0
|
|
261
|
+
* @returns 动画名称,如果未找到则返回 undefined
|
|
262
|
+
*/
|
|
137
263
|
getAnim(track = 0) {
|
|
138
264
|
try {
|
|
139
265
|
if (!this.armature) {
|
|
@@ -146,25 +272,159 @@ class Spine extends eva_js.Component {
|
|
|
146
272
|
console.log(e);
|
|
147
273
|
}
|
|
148
274
|
}
|
|
275
|
+
/**
|
|
276
|
+
* 设置默认的动画混合时间
|
|
277
|
+
*
|
|
278
|
+
* 当没有为特定动画对指定混合时间时,将使用此默认值。
|
|
279
|
+
*
|
|
280
|
+
* @param duration - 默认混合时长(秒)
|
|
281
|
+
*/
|
|
149
282
|
setDefaultMix(duration) {
|
|
150
283
|
if (!this.armature) ;
|
|
151
284
|
else {
|
|
152
285
|
this.armature.state.data.defaultMix = duration;
|
|
153
286
|
}
|
|
154
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* 替换指定插槽的附件
|
|
290
|
+
*
|
|
291
|
+
* 用于换装、武器切换等场景。
|
|
292
|
+
*
|
|
293
|
+
* @param slotName - 插槽名称
|
|
294
|
+
* @param attachmentName - 附件名称
|
|
295
|
+
*/
|
|
155
296
|
setAttachment(slotName, attachmentName) {
|
|
156
297
|
if (!this.armature) {
|
|
157
298
|
return;
|
|
158
299
|
}
|
|
159
300
|
this.armature.skeleton.setAttachment(slotName, attachmentName);
|
|
160
301
|
}
|
|
302
|
+
/**
|
|
303
|
+
* 获取指定名称的骨骼
|
|
304
|
+
*
|
|
305
|
+
* 可用于直接操作骨骼的位置、旋转、缩放等属性。
|
|
306
|
+
*
|
|
307
|
+
* @param boneName - 骨骼名称
|
|
308
|
+
* @returns 骨骼对象,如果未找到则返回 undefined
|
|
309
|
+
*/
|
|
161
310
|
getBone(boneName) {
|
|
162
311
|
if (!this.armature) {
|
|
163
312
|
return;
|
|
164
313
|
}
|
|
165
314
|
return this.armature.skeleton.findBone(boneName);
|
|
166
315
|
}
|
|
316
|
+
/**
|
|
317
|
+
* 将一个 GameObject 挂载到 Spine 的指定插槽上
|
|
318
|
+
*
|
|
319
|
+
* 挂载后 GameObject 会跟随骨骼运动。当 Spine 组件销毁时,
|
|
320
|
+
* 挂载的 GameObject 也会被自动销毁。
|
|
321
|
+
*
|
|
322
|
+
* @param slot - 插槽名称或索引
|
|
323
|
+
* @param gameObject - 要挂载的 GameObject
|
|
324
|
+
* @param options - 可选配置
|
|
325
|
+
* @param options.followAttachmentTimeline - 是否跟随插槽的附件时间线
|
|
326
|
+
*/
|
|
327
|
+
addSlotObject(slot, gameObject, options) {
|
|
328
|
+
if (!this.armature) {
|
|
329
|
+
console.warn('Spine armature is not ready, cannot addSlotObject');
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
if (!this._containerManager) {
|
|
333
|
+
console.warn('ContainerManager is not available');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
const container = this._containerManager.getContainer(gameObject.id);
|
|
337
|
+
if (!container) {
|
|
338
|
+
// 容器尚未就绪,加入 pending 队列,等待下一帧自动处理
|
|
339
|
+
this._pendingSlotObjects.push({ slot, gameObject, options });
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
this._doAddSlotObject(slot, gameObject, container, options);
|
|
343
|
+
}
|
|
344
|
+
_doAddSlotObject(slot, gameObject, container, options) {
|
|
345
|
+
// 创建 wrapper 容器:Spine 骨骼矩阵作用在 wrapper 上,
|
|
346
|
+
// gameObject 的 container 作为子节点,其 transform 作为相对 slot 的局部偏移
|
|
347
|
+
const wrapper = new pixi_js.Container();
|
|
348
|
+
wrapper.addChild(container);
|
|
349
|
+
this.armature.addSlotObject(slot, wrapper, options);
|
|
350
|
+
this._slotGameObjects.set(gameObject, { slot, wrapper });
|
|
351
|
+
// slot object 可能不在 game.gameObjects 中,RendererSystem 不会自动同步 transform
|
|
352
|
+
// 手动同步 gameObject 及其子树的 transform 到 container
|
|
353
|
+
this._syncTransformTree(gameObject);
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* 递归同步 gameObject 及其子树的 transform 到对应的渲染容器
|
|
357
|
+
*/
|
|
358
|
+
_syncTransformTree(gameObject) {
|
|
359
|
+
var _a;
|
|
360
|
+
if (!this._containerManager)
|
|
361
|
+
return;
|
|
362
|
+
this._containerManager.updateTransform({
|
|
363
|
+
name: gameObject.id,
|
|
364
|
+
transform: gameObject.transform,
|
|
365
|
+
});
|
|
366
|
+
if ((_a = gameObject.transform) === null || _a === void 0 ? void 0 : _a.children) {
|
|
367
|
+
for (const childTransform of gameObject.transform.children) {
|
|
368
|
+
if (childTransform.gameObject) {
|
|
369
|
+
this._syncTransformTree(childTransform.gameObject);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* 处理等待容器就绪的 slot 挂载请求(由 SpineSystem 每帧调用)
|
|
376
|
+
*/
|
|
377
|
+
_flushPendingSlotObjects() {
|
|
378
|
+
if (this._pendingSlotObjects.length === 0)
|
|
379
|
+
return;
|
|
380
|
+
if (!this.armature || !this._containerManager)
|
|
381
|
+
return;
|
|
382
|
+
const still = [];
|
|
383
|
+
for (const pending of this._pendingSlotObjects) {
|
|
384
|
+
const container = this._containerManager.getContainer(pending.gameObject.id);
|
|
385
|
+
if (container) {
|
|
386
|
+
this._doAddSlotObject(pending.slot, pending.gameObject, container, pending.options);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
still.push(pending);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
this._pendingSlotObjects = still;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* 从插槽上移除挂载的 GameObject
|
|
396
|
+
*
|
|
397
|
+
* @param gameObject - 要移除的 GameObject
|
|
398
|
+
*/
|
|
399
|
+
removeSlotObject(gameObject) {
|
|
400
|
+
// 从 pending 队列中移除
|
|
401
|
+
this._pendingSlotObjects = this._pendingSlotObjects.filter(p => p.gameObject !== gameObject);
|
|
402
|
+
const entry = this._slotGameObjects.get(gameObject);
|
|
403
|
+
if (entry && this.armature) {
|
|
404
|
+
this.armature.removeSlotObject(entry.wrapper);
|
|
405
|
+
entry.wrapper.destroy({ children: false });
|
|
406
|
+
}
|
|
407
|
+
this._slotGameObjects.delete(gameObject);
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* 销毁所有挂载到插槽的 GameObject(内部使用)
|
|
411
|
+
*/
|
|
412
|
+
_destroySlotGameObjects() {
|
|
413
|
+
for (const [gameObject, entry] of this._slotGameObjects) {
|
|
414
|
+
if (!gameObject.destroyed) {
|
|
415
|
+
// 先从 spine 插槽移除 wrapper,避免 destroy 时重复操作
|
|
416
|
+
if (this.armature) {
|
|
417
|
+
this.armature.removeSlotObject(entry.wrapper);
|
|
418
|
+
}
|
|
419
|
+
entry.wrapper.destroy({ children: false });
|
|
420
|
+
gameObject.destroy();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
this._slotGameObjects.clear();
|
|
424
|
+
this._pendingSlotObjects = [];
|
|
425
|
+
}
|
|
167
426
|
}
|
|
427
|
+
/** 组件名称 */
|
|
168
428
|
Spine.componentName = 'Spine';
|
|
169
429
|
__decorate([
|
|
170
430
|
inspectorDecorator.type('string')
|
|
@@ -177,7 +437,10 @@ __decorate([
|
|
|
177
437
|
], Spine.prototype, "animationName", void 0);
|
|
178
438
|
__decorate([
|
|
179
439
|
inspectorDecorator.type('boolean')
|
|
180
|
-
], Spine.prototype, "autoPlay", void 0);
|
|
440
|
+
], Spine.prototype, "autoPlay", void 0);
|
|
441
|
+
__decorate([
|
|
442
|
+
inspectorDecorator.type('boolean')
|
|
443
|
+
], Spine.prototype, "keepResource", void 0);
|
|
181
444
|
|
|
182
445
|
let dataMap = {};
|
|
183
446
|
function createSpineData(name, data, scale, pixiSpine) {
|
|
@@ -217,14 +480,6 @@ function releaseSpineData(res, _imageSrc) {
|
|
|
217
480
|
data.ref--;
|
|
218
481
|
setTimeout(() => __awaiter(this, void 0, void 0, function* () {
|
|
219
482
|
if (data.ref <= 0) {
|
|
220
|
-
yield pixi_js.Assets.unload([res.src.image.url, res.src.atlas.url, res.src.ske.url]);
|
|
221
|
-
const resolver = pixi_js.Assets.resolver;
|
|
222
|
-
delete resolver._assetMap[res.src.image.url];
|
|
223
|
-
delete resolver._assetMap[res.src.atlas.url];
|
|
224
|
-
delete resolver._assetMap[res.src.ske.url];
|
|
225
|
-
delete resolver._resolverHash[res.src.image.url];
|
|
226
|
-
delete resolver._resolverHash[res.src.atlas.url];
|
|
227
|
-
delete resolver._resolverHash[res.src.ske.url];
|
|
228
483
|
eva_js.resource.destroy(resourceName);
|
|
229
484
|
delete dataMap[resourceName];
|
|
230
485
|
}
|
|
@@ -232,17 +487,41 @@ function releaseSpineData(res, _imageSrc) {
|
|
|
232
487
|
}
|
|
233
488
|
|
|
234
489
|
const MaxRetryCount = 20;
|
|
490
|
+
/**
|
|
491
|
+
* Spine 骨骼动画系统
|
|
492
|
+
*
|
|
493
|
+
* SpineSystem 负责管理所有 Spine 组件的骨架创建、动画更新和资源管理。
|
|
494
|
+
* 系统会监听 Spine 组件的变化,自动加载骨骼数据并创建动画实例,
|
|
495
|
+
* 并在每帧更新所有活跃的 Spine 动画。
|
|
496
|
+
*
|
|
497
|
+
* 主要功能:
|
|
498
|
+
* - 骨骼数据加载和缓存
|
|
499
|
+
* - 动画实例创建和销毁
|
|
500
|
+
* - 每帧动画状态更新
|
|
501
|
+
* - WebGL 上下文恢复处理
|
|
502
|
+
* - 资源重试机制
|
|
503
|
+
*/
|
|
235
504
|
let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
|
|
236
505
|
constructor() {
|
|
237
506
|
super(...arguments);
|
|
507
|
+
/** 骨架实例映射表(游戏对象 ID -> 骨架容器) */
|
|
238
508
|
this.armatures = {};
|
|
509
|
+
/** Spine 组件实例映射(游戏对象 ID -> Spine 组件) */
|
|
510
|
+
this._spineComponents = {};
|
|
239
511
|
}
|
|
512
|
+
/**
|
|
513
|
+
* 初始化系统
|
|
514
|
+
* @param obj - 初始化参数
|
|
515
|
+
* @param obj.pixiSpine - PixiJS Spine 插件实例
|
|
516
|
+
*/
|
|
240
517
|
init({ pixiSpine }) {
|
|
241
518
|
this.renderSystem = this.game.getSystem(pluginRenderer.RendererSystem);
|
|
242
519
|
this.renderSystem.rendererManager.register(this);
|
|
243
520
|
this.pixiSpine = pixiSpine;
|
|
244
521
|
this.game.canvas.addEventListener('webglcontextrestored', () => {
|
|
522
|
+
// 重建所有spine
|
|
245
523
|
const objs = this.game.gameObjects;
|
|
524
|
+
// clearCache();
|
|
246
525
|
let toAdd = [];
|
|
247
526
|
for (let k in this.armatures) {
|
|
248
527
|
const id = +k;
|
|
@@ -275,10 +554,20 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
|
|
|
275
554
|
}, 1000);
|
|
276
555
|
}, false);
|
|
277
556
|
}
|
|
557
|
+
/**
|
|
558
|
+
* 每帧更新所有 Spine 动画
|
|
559
|
+
* @param e - 更新参数,包含帧间隔时间
|
|
560
|
+
*/
|
|
278
561
|
update(e) {
|
|
279
562
|
for (let key in this.armatures) {
|
|
563
|
+
// TODO: 类型
|
|
564
|
+
// @ts-ignore
|
|
280
565
|
this.armatures[key].update(e.deltaTime * 0.001);
|
|
281
566
|
}
|
|
567
|
+
// 处理等待容器就绪的 slot 挂载请求
|
|
568
|
+
for (let key in this._spineComponents) {
|
|
569
|
+
this._spineComponents[key]._flushPendingSlotObjects();
|
|
570
|
+
}
|
|
282
571
|
super.update();
|
|
283
572
|
}
|
|
284
573
|
componentChanged(changed) {
|
|
@@ -301,7 +590,7 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
|
|
|
301
590
|
});
|
|
302
591
|
}
|
|
303
592
|
add(changed, count) {
|
|
304
|
-
var _a, _b;
|
|
593
|
+
var _a, _b, _c;
|
|
305
594
|
return __awaiter(this, void 0, void 0, function* () {
|
|
306
595
|
const component = changed.component;
|
|
307
596
|
clearTimeout(component.addHandler);
|
|
@@ -317,6 +606,7 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
|
|
|
317
606
|
component.addHandler = setTimeout(() => {
|
|
318
607
|
if (!component.destroied) {
|
|
319
608
|
if (count === undefined) {
|
|
609
|
+
// 最大重试次数
|
|
320
610
|
count = MaxRetryCount;
|
|
321
611
|
}
|
|
322
612
|
count--;
|
|
@@ -333,37 +623,48 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
|
|
|
333
623
|
this.remove(changed);
|
|
334
624
|
const container = (_b = (_a = this.renderSystem) === null || _a === void 0 ? void 0 : _a.containerManager) === null || _b === void 0 ? void 0 : _b.getContainer(changed.gameObject.id);
|
|
335
625
|
if (!container) {
|
|
626
|
+
// console.warn('添加spine的container不存在');
|
|
336
627
|
return;
|
|
337
628
|
}
|
|
338
629
|
component.lastResource = component.resource;
|
|
630
|
+
// @ts-ignore
|
|
339
631
|
const armature = new this.pixiSpine.Spine({
|
|
340
632
|
skeletonData: spineData,
|
|
341
633
|
autoUpdate: false,
|
|
342
634
|
});
|
|
343
635
|
this.armatures[changed.gameObject.id] = armature;
|
|
636
|
+
this._spineComponents[changed.gameObject.id] = component;
|
|
344
637
|
if (changed.gameObject && changed.gameObject.transform) {
|
|
345
638
|
const tran = changed.gameObject.transform;
|
|
346
639
|
armature.x = tran.size.width * tran.origin.x;
|
|
347
640
|
armature.y = tran.size.height * tran.origin.y;
|
|
348
641
|
}
|
|
349
642
|
container.addChildAt(armature, 0);
|
|
643
|
+
/** 保证第一帧显示正常 */
|
|
350
644
|
armature.update();
|
|
645
|
+
component._containerManager = (_c = this.renderSystem) === null || _c === void 0 ? void 0 : _c.containerManager;
|
|
351
646
|
component.armature = armature;
|
|
647
|
+
// @ts-ignore
|
|
352
648
|
component.emit('loaded', { resource: component.resource });
|
|
353
649
|
armature.state.addListener({
|
|
650
|
+
// @ts-ignore
|
|
354
651
|
start: (track, event) => {
|
|
355
652
|
component.emit('start', { track, name: track.animation.name });
|
|
356
653
|
},
|
|
654
|
+
// @ts-ignore
|
|
357
655
|
complete: (track, event) => {
|
|
358
656
|
component.emit('complete', { track, name: track.animation.name });
|
|
359
657
|
},
|
|
658
|
+
// @ts-ignore
|
|
360
659
|
interrupt: (track, event) => {
|
|
361
660
|
component.emit('interrupt', { track, name: track.animation.name });
|
|
362
661
|
},
|
|
363
|
-
end: (track,
|
|
662
|
+
end: (track, // @ts-ignore
|
|
663
|
+
event) => {
|
|
364
664
|
component.emit('end', { track, name: track.animation.name });
|
|
365
665
|
},
|
|
366
666
|
event: (track, event) => {
|
|
667
|
+
// @ts-ignore
|
|
367
668
|
component.emit('event', track, event);
|
|
368
669
|
},
|
|
369
670
|
});
|
|
@@ -385,17 +686,23 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
|
|
|
385
686
|
container.removeChild(armature);
|
|
386
687
|
}
|
|
387
688
|
if (component.armature) {
|
|
689
|
+
// 销毁所有挂载到插槽的 GameObject
|
|
690
|
+
component._destroySlotGameObjects();
|
|
388
691
|
component.armature.destroy({ children: true });
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
692
|
+
if (!component.keepResource) {
|
|
693
|
+
const res = yield eva_js.resource.getResource(component.lastResource);
|
|
694
|
+
((_d = (_c = res.data) === null || _c === void 0 ? void 0 : _c.image) === null || _d === void 0 ? void 0 : _d.src) || ((_f = (_e = res.data) === null || _e === void 0 ? void 0 : _e.image) === null || _f === void 0 ? void 0 : _f.label);
|
|
695
|
+
releaseSpineData(res);
|
|
696
|
+
}
|
|
392
697
|
}
|
|
393
698
|
component.armature = null;
|
|
394
699
|
delete this.armatures[changed.gameObject.id];
|
|
700
|
+
delete this._spineComponents[changed.gameObject.id];
|
|
395
701
|
if (changed.type === eva_js.OBSERVER_TYPE.CHANGE) ;
|
|
396
702
|
});
|
|
397
703
|
}
|
|
398
704
|
};
|
|
705
|
+
/** 系统名称 */
|
|
399
706
|
SpineSystem.systemName = 'SpineSystem';
|
|
400
707
|
SpineSystem = __decorate([
|
|
401
708
|
eva_js.decorators.componentObserver({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@eva/eva.js"),t=require("@eva/plugin-renderer"),a=require("@eva/inspector-decorator")
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("@eva/eva.js"),t=require("@eva/plugin-renderer"),n=require("pixi.js"),a=require("@eva/inspector-decorator");
|
|
2
2
|
/*! *****************************************************************************
|
|
3
3
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
4
4
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
@@ -13,4 +13,4 @@ MERCHANTABLITY OR NON-INFRINGEMENT.
|
|
|
13
13
|
See the Apache Version 2.0 License for specific language governing permissions
|
|
14
14
|
and limitations under the License.
|
|
15
15
|
***************************************************************************** */
|
|
16
|
-
function
|
|
16
|
+
function r(e,t,n,a){var r,i=arguments.length,o=i<3?t:null===a?a=Object.getOwnPropertyDescriptor(t,n):a;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,n,a);else for(var s=e.length-1;s>=0;s--)(r=e[s])&&(o=(i<3?r(o):i>3?r(t,n,o):r(t,n))||o);return i>3&&o&&Object.defineProperty(t,n,o),o}function i(e,t,n,a){return new(n||(n=Promise))((function(r,i){function o(e){try{c(a.next(e))}catch(e){i(e)}}function s(e){try{c(a.throw(e))}catch(e){i(e)}}function c(e){e.done?r(e.value):new n((function(t){t(e.value)})).then(o,s)}c((a=a.apply(e,t||[])).next())}))}class o extends e.Component{constructor(){super(...arguments),this.resource="",this.scale=1,this.animationName="",this.autoPlay=!0,this.keepResource=!1,this._slotGameObjects=new Map,this._pendingSlotObjects=[],this.waitExecuteInfos=[]}set armature(e){if(this._armature=e,e){this.autoPlay&&this.play(this.animationName);for(const e of this.waitExecuteInfos)if(e.playType){const{name:t,loop:n,track:a}=e;this.play(t,n,a)}else this.stop(e.track);this.waitExecuteInfos=[]}}get armature(){return this._armature}init(e){e&&Object.assign(this,e)}onDestroy(){this.destroied=!0}play(e,t,n){try{const a=null!=t?t:this.autoPlay;e&&(this.animationName=e),this.armature?(void 0===n&&(n=0),this.armature.state.setAnimation(n,this.animationName,a)):this.waitExecuteInfos.push({playType:!0,name:e,loop:a,track:n})}catch(e){console.log(e)}}stop(e){this.armature?(void 0===e&&(e=0),this.armature.state.setEmptyAnimation(e,0)):this.waitExecuteInfos.push({playType:!1,track:e})}addAnimation(e,t,n,a){try{this.armature&&(void 0===a&&(a=0),this.armature.state.addAnimation(a,e,n,t))}catch(e){console.log(e)}}setMix(e,t,n){this.armature&&this.armature.state.data.setMix(e,t,n)}getAnim(e=0){try{if(this.armature)return this.armature.state.tracks[e].animation.name}catch(e){console.log(e)}}setDefaultMix(e){this.armature&&(this.armature.state.data.defaultMix=e)}setAttachment(e,t){this.armature&&this.armature.skeleton.setAttachment(e,t)}getBone(e){if(this.armature)return this.armature.skeleton.findBone(e)}addSlotObject(e,t,n){if(!this.armature)return void console.warn("Spine armature is not ready, cannot addSlotObject");if(!this._containerManager)return void console.warn("ContainerManager is not available");const a=this._containerManager.getContainer(t.id);a?this._doAddSlotObject(e,t,a,n):this._pendingSlotObjects.push({slot:e,gameObject:t,options:n})}_doAddSlotObject(e,t,a,r){const i=new n.Container;i.addChild(a),this.armature.addSlotObject(e,i,r),this._slotGameObjects.set(t,{slot:e,wrapper:i}),this._syncTransformTree(t)}_syncTransformTree(e){var t;if(this._containerManager&&(this._containerManager.updateTransform({name:e.id,transform:e.transform}),null===(t=e.transform)||void 0===t?void 0:t.children))for(const t of e.transform.children)t.gameObject&&this._syncTransformTree(t.gameObject)}_flushPendingSlotObjects(){if(0===this._pendingSlotObjects.length)return;if(!this.armature||!this._containerManager)return;const e=[];for(const t of this._pendingSlotObjects){const n=this._containerManager.getContainer(t.gameObject.id);n?this._doAddSlotObject(t.slot,t.gameObject,n,t.options):e.push(t)}this._pendingSlotObjects=e}removeSlotObject(e){this._pendingSlotObjects=this._pendingSlotObjects.filter((t=>t.gameObject!==e));const t=this._slotGameObjects.get(e);t&&this.armature&&(this.armature.removeSlotObject(t.wrapper),t.wrapper.destroy({children:!1})),this._slotGameObjects.delete(e)}_destroySlotGameObjects(){for(const[e,t]of this._slotGameObjects)e.destroyed||(this.armature&&this.armature.removeSlotObject(t.wrapper),t.wrapper.destroy({children:!1}),e.destroy());this._slotGameObjects.clear(),this._pendingSlotObjects=[]}}o.componentName="Spine",r([a.type("string")],o.prototype,"resource",void 0),r([a.type("number")],o.prototype,"scale",void 0),r([a.type("string")],o.prototype,"animationName",void 0),r([a.type("boolean")],o.prototype,"autoPlay",void 0),r([a.type("boolean")],o.prototype,"keepResource",void 0);let s={};function c(e,t,n){return i(this,void 0,void 0,(function*(){let a=s[e.name];if(!a)if(e.complete)a=function(e,t,n,a){const r=t.ske,i=t.atlas,o=new a.AtlasAttachmentLoader(i),c=r instanceof Uint8Array?new a.SkeletonBinary(o):new a.SkeletonJson(o);c.scale=n||1;const d={spineData:c.readSkeletonData(r),ref:0,imageSrc:t.image.label};return s[e]=d,d}(e.name,e.data,t,n);else if(!a)return;return a.ref++,a.spineData}))}let d=class extends t.Renderer{constructor(){super(...arguments),this.armatures={},this._spineComponents={}}init({pixiSpine:n}){this.renderSystem=this.game.getSystem(t.RendererSystem),this.renderSystem.rendererManager.register(this),this.pixiSpine=n,this.game.canvas.addEventListener("webglcontextrestored",(()=>{const t=this.game.gameObjects;let n=[];for(let a in this.armatures){const r=+a;for(let a=0;a<t.length;++a){let i=t[a];if(i.id===r){let t=i.getComponent(o);t&&(this.remove({type:e.OBSERVER_TYPE.REMOVE,gameObject:i,component:t,componentName:o.componentName}),n.push({type:e.OBSERVER_TYPE.ADD,gameObject:i,component:t,componentName:o.componentName}));break}}}setTimeout((()=>{n.forEach((e=>{this.add(e)}))}),1e3)}),!1)}update(e){for(let t in this.armatures)this.armatures[t].update(.001*e.deltaTime);for(let e in this._spineComponents)this._spineComponents[e]._flushPendingSlotObjects();super.update()}componentChanged(t){return i(this,void 0,void 0,(function*(){if("Spine"===t.componentName)if(t.type===e.OBSERVER_TYPE.ADD)this.add(t);else if(t.type===e.OBSERVER_TYPE.CHANGE){if("resource"===t.prop.prop[0])this.change(t)}else t.type===e.OBSERVER_TYPE.REMOVE&&this.remove(t)}))}add(t,n){var a,r,o;return i(this,void 0,void 0,(function*(){const i=t.component;clearTimeout(i.addHandler);const s=t.gameObject.id,d=this.increaseAsyncId(s),m=yield e.resource.getResource(i.resource);if(!this.validateAsyncId(s,d))return;const l=yield c(m,i.scale,this.pixiSpine);if(!this.validateAsyncId(s,d))return;if(!l)return void(i.addHandler=setTimeout((()=>{i.destroied||(void 0===n&&(n=20),--n>0?this.add(t,n):console.log("retry exceed max times",i.resource))}),1e3));this.remove(t);const u=null===(r=null===(a=this.renderSystem)||void 0===a?void 0:a.containerManager)||void 0===r?void 0:r.getContainer(t.gameObject.id);if(!u)return;i.lastResource=i.resource;const h=new this.pixiSpine.Spine({skeletonData:l,autoUpdate:!1});if(this.armatures[t.gameObject.id]=h,this._spineComponents[t.gameObject.id]=i,t.gameObject&&t.gameObject.transform){const e=t.gameObject.transform;h.x=e.size.width*e.origin.x,h.y=e.size.height*e.origin.y}u.addChildAt(h,0),h.update(),i._containerManager=null===(o=this.renderSystem)||void 0===o?void 0:o.containerManager,i.armature=h,i.emit("loaded",{resource:i.resource}),h.state.addListener({start:(e,t)=>{i.emit("start",{track:e,name:e.animation.name})},complete:(e,t)=>{i.emit("complete",{track:e,name:e.animation.name})},interrupt:(e,t)=>{i.emit("interrupt",{track:e,name:e.animation.name})},end:(e,t)=>{i.emit("end",{track:e,name:e.animation.name})},event:(e,t)=>{i.emit("event",e,t)}})}))}change(e){this.remove(e),this.add(e)}remove(t){var n,a,r,o,c,d;return i(this,void 0,void 0,(function*(){this.increaseAsyncId(t.gameObject.id);const m=t.component;clearTimeout(m.addHandler);const l=this.armatures[t.gameObject.id],u=null===(a=null===(n=this.renderSystem)||void 0===n?void 0:n.containerManager)||void 0===a?void 0:a.getContainer(t.gameObject.id);if(u&&l&&u.removeChild(l),m.armature&&(m._destroySlotGameObjects(),m.armature.destroy({children:!0}),!m.keepResource)){const t=yield e.resource.getResource(m.lastResource);(null===(o=null===(r=t.data)||void 0===r?void 0:r.image)||void 0===o?void 0:o.src)||null===(d=null===(c=t.data)||void 0===c?void 0:c.image)||void 0===d||d.label,function(t){const n=t.name,a=s[n];a&&(a.ref--,setTimeout((()=>i(this,void 0,void 0,(function*(){a.ref<=0&&(e.resource.destroy(n),delete s[n])}))),100))}(t)}m.armature=null,delete this.armatures[t.gameObject.id],delete this._spineComponents[t.gameObject.id],t.type,e.OBSERVER_TYPE.CHANGE}))}};d.systemName="SpineSystem",d=r([e.decorators.componentObserver({Spine:["resource"]})],d);var m=d;e.resource.registerResourceType("SPINE"),exports.Spine=o,exports.SpineSystem=m;
|