@eva/spine-base 2.0.1-beta.3 → 2.0.1-beta.31

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.
@@ -1,7 +1,7 @@
1
1
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
2
2
  globalThis.EVA = globalThis.EVA || {};
3
3
  globalThis.EVA.plugin = globalThis.EVA.plugin || {};
4
- var _EVA_IIFE_spineBase = function (exports, eva_js, pluginRenderer, pixi_js) {
4
+ var _EVA_IIFE_spineBase = function (exports, eva_js, pluginRenderer) {
5
5
  'use strict';
6
6
  function __decorate(decorators, target, key, desc) {
7
7
  var c = arguments.length,
@@ -61,6 +61,7 @@ var _EVA_IIFE_spineBase = function (exports, eva_js, pluginRenderer, pixi_js) {
61
61
  this.scale = 1;
62
62
  this.animationName = '';
63
63
  this.autoPlay = true;
64
+ this.keepResource = false;
64
65
  this.waitExecuteInfos = [];
65
66
  }
66
67
  set armature(val) {
@@ -176,6 +177,7 @@ var _EVA_IIFE_spineBase = function (exports, eva_js, pluginRenderer, pixi_js) {
176
177
  __decorate([type('number')], Spine.prototype, "scale", void 0);
177
178
  __decorate([type('string')], Spine.prototype, "animationName", void 0);
178
179
  __decorate([type('boolean')], Spine.prototype, "autoPlay", void 0);
180
+ __decorate([type('boolean')], Spine.prototype, "keepResource", void 0);
179
181
  let dataMap = {};
180
182
  function createSpineData(name, data, scale, pixiSpine) {
181
183
  const skeletonAsset = data.ske;
@@ -215,14 +217,6 @@ var _EVA_IIFE_spineBase = function (exports, eva_js, pluginRenderer, pixi_js) {
215
217
  data.ref--;
216
218
  setTimeout(() => __awaiter(this, void 0, void 0, function* () {
217
219
  if (data.ref <= 0) {
218
- yield pixi_js.Assets.unload([res.src.image.url, res.src.atlas.url, res.src.ske.url]);
219
- const resolver = pixi_js.Assets.resolver;
220
- delete resolver._assetMap[res.src.image.url];
221
- delete resolver._assetMap[res.src.atlas.url];
222
- delete resolver._assetMap[res.src.ske.url];
223
- delete resolver._resolverHash[res.src.image.url];
224
- delete resolver._resolverHash[res.src.atlas.url];
225
- delete resolver._resolverHash[res.src.ske.url];
226
220
  eva_js.resource.destroy(resourceName);
227
221
  delete dataMap[resourceName];
228
222
  }
@@ -396,9 +390,11 @@ var _EVA_IIFE_spineBase = function (exports, eva_js, pluginRenderer, pixi_js) {
396
390
  component.armature.destroy({
397
391
  children: true
398
392
  });
399
- const res = yield eva_js.resource.getResource(component.lastResource);
400
- ((_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);
401
- releaseSpineData(res);
393
+ if (!component.keepResource) {
394
+ const res = yield eva_js.resource.getResource(component.lastResource);
395
+ ((_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);
396
+ releaseSpineData(res);
397
+ }
402
398
  }
403
399
  component.armature = null;
404
400
  delete this.armatures[changed.gameObject.id];
@@ -418,5 +414,5 @@ var _EVA_IIFE_spineBase = function (exports, eva_js, pluginRenderer, pixi_js) {
418
414
  value: true
419
415
  });
420
416
  return exports;
421
- }({}, EVA, EVA.plugin.renderer, PIXI);
417
+ }({}, EVA, EVA.plugin.renderer);
422
418
  globalThis.EVA.plugin.spineBase = globalThis.EVA.plugin.spineBase || _EVA_IIFE_spineBase;
@@ -1 +1 @@
1
- function _extends(){return _extends=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)({}).hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e},_extends.apply(null,arguments)}globalThis.EVA=globalThis.EVA||{},globalThis.EVA.plugin=globalThis.EVA.plugin||{};var _EVA_IIFE_spineBase=function(e,t,r,a){"use strict";function n(e,t,r,a){var n,i=arguments.length,s=i<3?t:null===a?a=Object.getOwnPropertyDescriptor(t,r):a;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)s=Reflect.decorate(e,t,r,a);else for(var o=e.length-1;o>=0;o--)(n=e[o])&&(s=(i<3?n(s):i>3?n(t,r,s):n(t,r))||s);return i>3&&s&&Object.defineProperty(t,r,s),s}function i(e,t,r,a){return new(r||(r=Promise))((function(n,i){function s(e){try{c(a.next(e))}catch(e){i(e)}}function o(e){try{c(a.throw(e))}catch(e){i(e)}}function c(e){var t;e.done?n(e.value):(t=e.value,t instanceof r?t:new r((function(e){e(t)}))).then(s,o)}c((a=a.apply(e,t||[])).next())}))}function s(e){return function(t,r){var a=function(e,t){return e.constructor.IDEProps||(e.constructor.IDEProps={}),e.constructor.IDEProps[t]||(e.constructor.IDEProps[t]={}),e.constructor.IDEProps[t]}(t,r);a.key=r,a.type=e}}class o extends t.Component{constructor(){super(...arguments),this.resource="",this.scale=1,this.animationName="",this.autoPlay=!0,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:r,track:a}=e;this.play(t,r,a)}else this.stop(e.track);this.waitExecuteInfos=[]}}get armature(){return this._armature}init(e){e&&_extends(this,e)}onDestroy(){this.destroied=!0}play(e,t,r){try{const a=null!=t?t:this.autoPlay;e&&(this.animationName=e),this.armature?(void 0===r&&(r=0),this.armature.state.setAnimation(r,this.animationName,a)):this.waitExecuteInfos.push({playType:!0,name:e,loop:a,track:r})}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,r,a){try{this.armature&&(void 0===a&&(a=0),this.armature.state.addAnimation(a,e,r,t))}catch(e){console.log(e)}}setMix(e,t,r){this.armature&&this.armature.state.data.setMix(e,t,r)}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)}}o.componentName="Spine",n([s("string")],o.prototype,"resource",void 0),n([s("number")],o.prototype,"scale",void 0),n([s("string")],o.prototype,"animationName",void 0),n([s("boolean")],o.prototype,"autoPlay",void 0);let c={};function u(e,t,r){return i(this,void 0,void 0,(function*(){let a=c[e.name];if(!a)if(e.complete)a=function(e,t,r,a){const n=t.ske,i=t.atlas,s=new a.AtlasAttachmentLoader(i),o=n instanceof Uint8Array?new a.SkeletonBinary(s):new a.SkeletonJson(s);o.scale=r||1;const u={spineData:o.readSkeletonData(n),ref:0,imageSrc:t.image.label};return c[e]=u,u}(e.name,e.data,t,r);else if(!a)return;return a.ref++,a.spineData}))}let l=class extends r.Renderer{constructor(){super(...arguments),this.armatures={}}init({pixiSpine:e}){this.renderSystem=this.game.getSystem(r.RendererSystem),this.renderSystem.rendererManager.register(this),this.pixiSpine=e,this.game.canvas.addEventListener("webglcontextrestored",(()=>{const e=this.game.gameObjects;let r=[];for(let a in this.armatures){const n=+a;for(let a=0;a<e.length;++a){let i=e[a];if(i.id===n){let e=i.getComponent(o);e&&(this.remove({type:t.OBSERVER_TYPE.REMOVE,gameObject:i,component:e,componentName:o.componentName}),r.push({type:t.OBSERVER_TYPE.ADD,gameObject:i,component:e,componentName:o.componentName}));break}}}setTimeout((()=>{r.forEach((e=>{this.add(e)}))}),1e3)}),!1)}update(e){for(let t in this.armatures)this.armatures[t].update(.001*e.deltaTime);super.update()}componentChanged(e){return i(this,void 0,void 0,(function*(){if("Spine"===e.componentName)if(e.type===t.OBSERVER_TYPE.ADD)this.add(e);else if(e.type===t.OBSERVER_TYPE.CHANGE){if("resource"===e.prop.prop[0])this.change(e)}else e.type===t.OBSERVER_TYPE.REMOVE&&this.remove(e)}))}add(e,r){var a,n;return i(this,void 0,void 0,(function*(){const i=e.component;clearTimeout(i.addHandler);const s=e.gameObject.id,o=this.increaseAsyncId(s),c=yield t.resource.getResource(i.resource);if(!this.validateAsyncId(s,o))return;const l=yield u(c,i.scale,this.pixiSpine);if(!this.validateAsyncId(s,o))return;if(!l)return void(i.addHandler=setTimeout((()=>{i.destroied||(void 0===r&&(r=20),--r>0?this.add(e,r):console.log("retry exceed max times",i.resource))}),1e3));this.remove(e);const m=null===(n=null===(a=this.renderSystem)||void 0===a?void 0:a.containerManager)||void 0===n?void 0:n.getContainer(e.gameObject.id);if(!m)return;i.lastResource=i.resource;const d=new this.pixiSpine.Spine({skeletonData:l,autoUpdate:!1});if(this.armatures[e.gameObject.id]=d,e.gameObject&&e.gameObject.transform){const t=e.gameObject.transform;d.x=t.size.width*t.origin.x,d.y=t.size.height*t.origin.y}m.addChildAt(d,0),d.update(),i.armature=d,i.emit("loaded",{resource:i.resource}),d.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(e){var r,n,s,o,u,l;return i(this,void 0,void 0,(function*(){this.increaseAsyncId(e.gameObject.id);const m=e.component;clearTimeout(m.addHandler);const d=this.armatures[e.gameObject.id],h=null===(n=null===(r=this.renderSystem)||void 0===r?void 0:r.containerManager)||void 0===n?void 0:n.getContainer(e.gameObject.id);if(h&&d&&h.removeChild(d),m.armature){m.armature.destroy({children:!0});const e=yield t.resource.getResource(m.lastResource);(null===(o=null===(s=e.data)||void 0===s?void 0:s.image)||void 0===o?void 0:o.src)||null===(l=null===(u=e.data)||void 0===u?void 0:u.image)||void 0===l||l.label,function(e){const r=e.name,n=c[r];n&&(n.ref--,setTimeout((()=>i(this,void 0,void 0,(function*(){if(n.ref<=0){yield a.Assets.unload([e.src.image.url,e.src.atlas.url,e.src.ske.url]);const n=a.Assets.resolver;delete n._assetMap[e.src.image.url],delete n._assetMap[e.src.atlas.url],delete n._assetMap[e.src.ske.url],delete n._resolverHash[e.src.image.url],delete n._resolverHash[e.src.atlas.url],delete n._resolverHash[e.src.ske.url],t.resource.destroy(r),delete c[r]}}))),100))}(e)}m.armature=null,delete this.armatures[e.gameObject.id],e.type,t.OBSERVER_TYPE.CHANGE}))}};l.systemName="SpineSystem",l=n([t.decorators.componentObserver({Spine:["resource"]})],l);var m=l;return t.resource.registerResourceType("SPINE"),e.Spine=o,e.SpineSystem=m,Object.defineProperty(e,"__esModule",{value:!0}),e}({},EVA,EVA.plugin.renderer,PIXI);globalThis.EVA.plugin.spineBase=globalThis.EVA.plugin.spineBase||_EVA_IIFE_spineBase;
1
+ function _extends(){return _extends=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var a in n)({}).hasOwnProperty.call(n,a)&&(e[a]=n[a])}return e},_extends.apply(null,arguments)}globalThis.EVA=globalThis.EVA||{},globalThis.EVA.plugin=globalThis.EVA.plugin||{};var _EVA_IIFE_spineBase=function(e,t,n){"use strict";function a(e,t,n,a){var i,r=arguments.length,o=r<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--)(i=e[s])&&(o=(r<3?i(o):r>3?i(t,n,o):i(t,n))||o);return r>3&&o&&Object.defineProperty(t,n,o),o}function i(e,t,n,a){return new(n||(n=Promise))((function(i,r){function o(e){try{c(a.next(e))}catch(e){r(e)}}function s(e){try{c(a.throw(e))}catch(e){r(e)}}function c(e){var t;e.done?i(e.value):(t=e.value,t instanceof n?t:new n((function(e){e(t)}))).then(o,s)}c((a=a.apply(e,t||[])).next())}))}function r(e){return function(t,n){var a=function(e,t){return e.constructor.IDEProps||(e.constructor.IDEProps={}),e.constructor.IDEProps[t]||(e.constructor.IDEProps[t]={}),e.constructor.IDEProps[t]}(t,n);a.key=n,a.type=e}}class o extends t.Component{constructor(){super(...arguments),this.resource="",this.scale=1,this.animationName="",this.autoPlay=!0,this.keepResource=!1,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&&_extends(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)}}o.componentName="Spine",a([r("string")],o.prototype,"resource",void 0),a([r("number")],o.prototype,"scale",void 0),a([r("string")],o.prototype,"animationName",void 0),a([r("boolean")],o.prototype,"autoPlay",void 0),a([r("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 i=t.ske,r=t.atlas,o=new a.AtlasAttachmentLoader(r),c=i instanceof Uint8Array?new a.SkeletonBinary(o):new a.SkeletonJson(o);c.scale=n||1;const u={spineData:c.readSkeletonData(i),ref:0,imageSrc:t.image.label};return s[e]=u,u}(e.name,e.data,t,n);else if(!a)return;return a.ref++,a.spineData}))}let u=class extends n.Renderer{constructor(){super(...arguments),this.armatures={}}init({pixiSpine:e}){this.renderSystem=this.game.getSystem(n.RendererSystem),this.renderSystem.rendererManager.register(this),this.pixiSpine=e,this.game.canvas.addEventListener("webglcontextrestored",(()=>{const e=this.game.gameObjects;let n=[];for(let a in this.armatures){const i=+a;for(let a=0;a<e.length;++a){let r=e[a];if(r.id===i){let e=r.getComponent(o);e&&(this.remove({type:t.OBSERVER_TYPE.REMOVE,gameObject:r,component:e,componentName:o.componentName}),n.push({type:t.OBSERVER_TYPE.ADD,gameObject:r,component:e,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);super.update()}componentChanged(e){return i(this,void 0,void 0,(function*(){if("Spine"===e.componentName)if(e.type===t.OBSERVER_TYPE.ADD)this.add(e);else if(e.type===t.OBSERVER_TYPE.CHANGE){if("resource"===e.prop.prop[0])this.change(e)}else e.type===t.OBSERVER_TYPE.REMOVE&&this.remove(e)}))}add(e,n){var a,r;return i(this,void 0,void 0,(function*(){const i=e.component;clearTimeout(i.addHandler);const o=e.gameObject.id,s=this.increaseAsyncId(o),u=yield t.resource.getResource(i.resource);if(!this.validateAsyncId(o,s))return;const m=yield c(u,i.scale,this.pixiSpine);if(!this.validateAsyncId(o,s))return;if(!m)return void(i.addHandler=setTimeout((()=>{i.destroied||(void 0===n&&(n=20),--n>0?this.add(e,n):console.log("retry exceed max times",i.resource))}),1e3));this.remove(e);const d=null===(r=null===(a=this.renderSystem)||void 0===a?void 0:a.containerManager)||void 0===r?void 0:r.getContainer(e.gameObject.id);if(!d)return;i.lastResource=i.resource;const l=new this.pixiSpine.Spine({skeletonData:m,autoUpdate:!1});if(this.armatures[e.gameObject.id]=l,e.gameObject&&e.gameObject.transform){const t=e.gameObject.transform;l.x=t.size.width*t.origin.x,l.y=t.size.height*t.origin.y}d.addChildAt(l,0),l.update(),i.armature=l,i.emit("loaded",{resource:i.resource}),l.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(e){var n,a,r,o,c,u;return i(this,void 0,void 0,(function*(){this.increaseAsyncId(e.gameObject.id);const m=e.component;clearTimeout(m.addHandler);const d=this.armatures[e.gameObject.id],l=null===(a=null===(n=this.renderSystem)||void 0===n?void 0:n.containerManager)||void 0===a?void 0:a.getContainer(e.gameObject.id);if(l&&d&&l.removeChild(d),m.armature&&(m.armature.destroy({children:!0}),!m.keepResource)){const e=yield t.resource.getResource(m.lastResource);(null===(o=null===(r=e.data)||void 0===r?void 0:r.image)||void 0===o?void 0:o.src)||null===(u=null===(c=e.data)||void 0===c?void 0:c.image)||void 0===u||u.label,function(e){const n=e.name,a=s[n];a&&(a.ref--,setTimeout((()=>i(this,void 0,void 0,(function*(){a.ref<=0&&(t.resource.destroy(n),delete s[n])}))),100))}(e)}m.armature=null,delete this.armatures[e.gameObject.id],e.type,t.OBSERVER_TYPE.CHANGE}))}};u.systemName="SpineSystem",u=a([t.decorators.componentObserver({Spine:["resource"]})],u);var m=u;return t.resource.registerResourceType("SPINE"),e.Spine=o,e.SpineSystem=m,Object.defineProperty(e,"__esModule",{value:!0}),e}({},EVA,EVA.plugin.renderer);globalThis.EVA.plugin.spineBase=globalThis.EVA.plugin.spineBase||_EVA_IIFE_spineBase;
@@ -5,7 +5,6 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  var eva_js = require('@eva/eva.js');
6
6
  var pluginRenderer = require('@eva/plugin-renderer');
7
7
  var inspectorDecorator = require('@eva/inspector-decorator');
8
- var pixi_js = require('pixi.js');
9
8
 
10
9
  /*! *****************************************************************************
11
10
  Copyright (c) Microsoft Corporation. All rights reserved.
@@ -38,15 +37,80 @@ function __awaiter(thisArg, _arguments, P, generator) {
38
37
  });
39
38
  }
40
39
 
40
+ /**
41
+ * Spine 骨骼动画组件
42
+ *
43
+ * Spine 组件用于播放 Esoteric Software 的 Spine 骨骼动画。
44
+ * 支持骨骼动画播放控制、动画混合、附件替换等高级功能,
45
+ * 适用于角色动画、复杂特效等需要骨骼动画的场景。
46
+ *
47
+ * 主要功能:
48
+ * - 骨骼动画播放和控制
49
+ * - 动画轨道管理(多动画并行)
50
+ * - 动画混合过渡
51
+ * - 骨骼和附件访问
52
+ * - 支持 Spine 3.6 和 3.8 版本
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * // 创建 Spine 动画
57
+ * const character = new GameObject('character');
58
+ * const spine = new Spine({
59
+ * resource: 'heroSpine', // Spine 资源
60
+ * animationName: 'idle', // 默认动画
61
+ * autoPlay: true, // 自动播放
62
+ * scale: 0.5 // 缩放比例
63
+ * });
64
+ * character.addComponent(spine);
65
+ *
66
+ * // 播放动画
67
+ * spine.play('walk', true); // 循环播放 walk 动画
68
+ *
69
+ * // 停止动画
70
+ * spine.stop();
71
+ *
72
+ * // 动画混合
73
+ * spine.setMix('idle', 'walk', 0.3); // 设置过渡时间
74
+ * spine.play('walk');
75
+ *
76
+ * // 添加动画队列
77
+ * spine.play('attack', false); // 播放攻击动画
78
+ * spine.addAnimation('idle', 0, true); // 攻击完成后回到 idle
79
+ *
80
+ * // 替换附件(换装)
81
+ * spine.setAttachment('weapon', 'sword'); // 将武器槽替换为剑
82
+ *
83
+ * // 访问骨骼
84
+ * const headBone = spine.getBone('head');
85
+ * if (headBone) {
86
+ * headBone.rotation = 15; // 旋转头部
87
+ * }
88
+ *
89
+ * // 多轨道动画
90
+ * spine.play('walk', true, 0); // 轨道0:身体动画
91
+ * spine.play('shoot', false, 1); // 轨道1:上半身动画
92
+ * ```
93
+ */
41
94
  class Spine extends eva_js.Component {
42
95
  constructor() {
43
96
  super(...arguments);
97
+ /** Spine 资源名称 */
44
98
  this.resource = '';
99
+ /** 动画缩放比例 */
45
100
  this.scale = 1;
101
+ /** 当前播放的动画名称 */
46
102
  this.animationName = '';
103
+ /** 是否自动播放动画 */
47
104
  this.autoPlay = true;
105
+ /** 是否保留资源(销毁时不释放) */
106
+ this.keepResource = false;
107
+ /** 等待执行的动画操作队列 */
48
108
  this.waitExecuteInfos = [];
49
109
  }
110
+ /**
111
+ * 设置骨架实例
112
+ * 当骨架加载完成后自动执行等待队列中的动画操作
113
+ */
50
114
  set armature(val) {
51
115
  this._armature = val;
52
116
  if (!val)
@@ -65,17 +129,36 @@ class Spine extends eva_js.Component {
65
129
  }
66
130
  this.waitExecuteInfos = [];
67
131
  }
132
+ /** 获取骨架实例 */
68
133
  get armature() {
69
134
  return this._armature;
70
135
  }
136
+ /**
137
+ * 初始化组件
138
+ * @param obj - 初始化参数
139
+ * @param obj.resource - Spine 资源名称
140
+ * @param obj.animationName - 默认动画名称
141
+ * @param obj.scale - 缩放比例
142
+ * @param obj.autoPlay - 是否自动播放
143
+ */
71
144
  init(obj) {
72
145
  if (!obj)
73
146
  return;
74
147
  Object.assign(this, obj);
75
148
  }
149
+ /** 组件销毁时调用 */
76
150
  onDestroy() {
77
151
  this.destroied = true;
78
152
  }
153
+ /**
154
+ * 播放指定动画
155
+ *
156
+ * 如果骨架尚未加载完成,动画操作将被加入等待队列。
157
+ *
158
+ * @param name - 动画名称,不指定则使用 animationName 属性
159
+ * @param loopAnimation - 是否循环播放,默认跟随 autoPlay 属性
160
+ * @param track - 动画轨道编号,默认为 0
161
+ */
79
162
  play(name, loopAnimation, track) {
80
163
  try {
81
164
  const loop = loopAnimation !== null && loopAnimation !== void 0 ? loopAnimation : this.autoPlay;
@@ -85,6 +168,12 @@ class Spine extends eva_js.Component {
85
168
  this.waitExecuteInfos.push({
86
169
  playType: true,
87
170
  name,
171
+ /**
172
+ * 在 v1.2.2 之前,Spine 动画的 autoPlay 为 true,动画会循环播放 https://github.com/eva-engine/eva.js/pull/164/files#diff-46e9ae36c04e7a0abedc1e14fd9d1c4e81d8386e9bb851f85971ccdba8957804L131
173
+ * 在 v1.2.2 之前,Spine 动画在每加载完( armature 设置之前)调用 play 是不生效的, 在 v1.2.2 [#164](https://github.com/eva-engine/eva.js/pull/164) 解决了这个问题
174
+ * 解决了不生效的问题以后,加载完成之前调用 play 默认循环是false,导致 autoPlay 下本来循环动画不循环了,和之前表现不一致
175
+ * 为了解决这个问题,在 autoPlay 的情况下,未加载完之前调用 play ,默认循环播放,除非设置不循环参数
176
+ */
88
177
  loop,
89
178
  track,
90
179
  });
@@ -100,6 +189,13 @@ class Spine extends eva_js.Component {
100
189
  console.log(e);
101
190
  }
102
191
  }
192
+ /**
193
+ * 停止指定轨道的动画
194
+ *
195
+ * 如果骨架尚未加载完成,停止操作将被加入等待队列。
196
+ *
197
+ * @param track - 动画轨道编号,默认为 0
198
+ */
103
199
  stop(track) {
104
200
  if (!this.armature) {
105
201
  this.waitExecuteInfos.push({
@@ -113,6 +209,16 @@ class Spine extends eva_js.Component {
113
209
  }
114
210
  this.armature.state.setEmptyAnimation(track, 0);
115
211
  }
212
+ /**
213
+ * 在当前动画之后添加新动画到队列
214
+ *
215
+ * 用于创建动画序列,当前动画播放完毕后自动播放下一个动画。
216
+ *
217
+ * @param name - 动画名称
218
+ * @param delay - 延迟时间(秒)
219
+ * @param loop - 是否循环播放
220
+ * @param track - 动画轨道编号,默认为 0
221
+ */
116
222
  addAnimation(name, delay, loop, track) {
117
223
  try {
118
224
  if (!this.armature) {
@@ -128,12 +234,27 @@ class Spine extends eva_js.Component {
128
234
  console.log(e);
129
235
  }
130
236
  }
237
+ /**
238
+ * 设置两个动画之间的混合过渡时间
239
+ *
240
+ * 当从一个动画切换到另一个动画时,会在指定时间内进行平滑过渡。
241
+ *
242
+ * @param from - 起始动画名称
243
+ * @param to - 目标动画名称
244
+ * @param duration - 过渡时长(秒)
245
+ */
131
246
  setMix(from, to, duration) {
132
247
  if (!this.armature) ;
133
248
  else {
134
249
  this.armature.state.data.setMix(from, to, duration);
135
250
  }
136
251
  }
252
+ /**
253
+ * 获取指定轨道当前播放的动画名称
254
+ *
255
+ * @param track - 动画轨道编号,默认为 0
256
+ * @returns 动画名称,如果未找到则返回 undefined
257
+ */
137
258
  getAnim(track = 0) {
138
259
  try {
139
260
  if (!this.armature) {
@@ -146,18 +267,41 @@ class Spine extends eva_js.Component {
146
267
  console.log(e);
147
268
  }
148
269
  }
270
+ /**
271
+ * 设置默认的动画混合时间
272
+ *
273
+ * 当没有为特定动画对指定混合时间时,将使用此默认值。
274
+ *
275
+ * @param duration - 默认混合时长(秒)
276
+ */
149
277
  setDefaultMix(duration) {
150
278
  if (!this.armature) ;
151
279
  else {
152
280
  this.armature.state.data.defaultMix = duration;
153
281
  }
154
282
  }
283
+ /**
284
+ * 替换指定插槽的附件
285
+ *
286
+ * 用于换装、武器切换等场景。
287
+ *
288
+ * @param slotName - 插槽名称
289
+ * @param attachmentName - 附件名称
290
+ */
155
291
  setAttachment(slotName, attachmentName) {
156
292
  if (!this.armature) {
157
293
  return;
158
294
  }
159
295
  this.armature.skeleton.setAttachment(slotName, attachmentName);
160
296
  }
297
+ /**
298
+ * 获取指定名称的骨骼
299
+ *
300
+ * 可用于直接操作骨骼的位置、旋转、缩放等属性。
301
+ *
302
+ * @param boneName - 骨骼名称
303
+ * @returns 骨骼对象,如果未找到则返回 undefined
304
+ */
161
305
  getBone(boneName) {
162
306
  if (!this.armature) {
163
307
  return;
@@ -165,6 +309,7 @@ class Spine extends eva_js.Component {
165
309
  return this.armature.skeleton.findBone(boneName);
166
310
  }
167
311
  }
312
+ /** 组件名称 */
168
313
  Spine.componentName = 'Spine';
169
314
  __decorate([
170
315
  inspectorDecorator.type('string')
@@ -177,7 +322,10 @@ __decorate([
177
322
  ], Spine.prototype, "animationName", void 0);
178
323
  __decorate([
179
324
  inspectorDecorator.type('boolean')
180
- ], Spine.prototype, "autoPlay", void 0);
325
+ ], Spine.prototype, "autoPlay", void 0);
326
+ __decorate([
327
+ inspectorDecorator.type('boolean')
328
+ ], Spine.prototype, "keepResource", void 0);
181
329
 
182
330
  let dataMap = {};
183
331
  function createSpineData(name, data, scale, pixiSpine) {
@@ -217,14 +365,6 @@ function releaseSpineData(res, _imageSrc) {
217
365
  data.ref--;
218
366
  setTimeout(() => __awaiter(this, void 0, void 0, function* () {
219
367
  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
368
  eva_js.resource.destroy(resourceName);
229
369
  delete dataMap[resourceName];
230
370
  }
@@ -232,17 +372,39 @@ function releaseSpineData(res, _imageSrc) {
232
372
  }
233
373
 
234
374
  const MaxRetryCount = 20;
375
+ /**
376
+ * Spine 骨骼动画系统
377
+ *
378
+ * SpineSystem 负责管理所有 Spine 组件的骨架创建、动画更新和资源管理。
379
+ * 系统会监听 Spine 组件的变化,自动加载骨骼数据并创建动画实例,
380
+ * 并在每帧更新所有活跃的 Spine 动画。
381
+ *
382
+ * 主要功能:
383
+ * - 骨骼数据加载和缓存
384
+ * - 动画实例创建和销毁
385
+ * - 每帧动画状态更新
386
+ * - WebGL 上下文恢复处理
387
+ * - 资源重试机制
388
+ */
235
389
  let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
236
390
  constructor() {
237
391
  super(...arguments);
392
+ /** 骨架实例映射表(游戏对象 ID -> 骨架容器) */
238
393
  this.armatures = {};
239
394
  }
395
+ /**
396
+ * 初始化系统
397
+ * @param obj - 初始化参数
398
+ * @param obj.pixiSpine - PixiJS Spine 插件实例
399
+ */
240
400
  init({ pixiSpine }) {
241
401
  this.renderSystem = this.game.getSystem(pluginRenderer.RendererSystem);
242
402
  this.renderSystem.rendererManager.register(this);
243
403
  this.pixiSpine = pixiSpine;
244
404
  this.game.canvas.addEventListener('webglcontextrestored', () => {
405
+ // 重建所有spine
245
406
  const objs = this.game.gameObjects;
407
+ // clearCache();
246
408
  let toAdd = [];
247
409
  for (let k in this.armatures) {
248
410
  const id = +k;
@@ -275,8 +437,14 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
275
437
  }, 1000);
276
438
  }, false);
277
439
  }
440
+ /**
441
+ * 每帧更新所有 Spine 动画
442
+ * @param e - 更新参数,包含帧间隔时间
443
+ */
278
444
  update(e) {
279
445
  for (let key in this.armatures) {
446
+ // TODO: 类型
447
+ // @ts-ignore
280
448
  this.armatures[key].update(e.deltaTime * 0.001);
281
449
  }
282
450
  super.update();
@@ -317,6 +485,7 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
317
485
  component.addHandler = setTimeout(() => {
318
486
  if (!component.destroied) {
319
487
  if (count === undefined) {
488
+ // 最大重试次数
320
489
  count = MaxRetryCount;
321
490
  }
322
491
  count--;
@@ -333,9 +502,11 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
333
502
  this.remove(changed);
334
503
  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
504
  if (!container) {
505
+ // console.warn('添加spine的container不存在');
336
506
  return;
337
507
  }
338
508
  component.lastResource = component.resource;
509
+ // @ts-ignore
339
510
  const armature = new this.pixiSpine.Spine({
340
511
  skeletonData: spineData,
341
512
  autoUpdate: false,
@@ -347,23 +518,30 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
347
518
  armature.y = tran.size.height * tran.origin.y;
348
519
  }
349
520
  container.addChildAt(armature, 0);
521
+ /** 保证第一帧显示正常 */
350
522
  armature.update();
351
523
  component.armature = armature;
524
+ // @ts-ignore
352
525
  component.emit('loaded', { resource: component.resource });
353
526
  armature.state.addListener({
527
+ // @ts-ignore
354
528
  start: (track, event) => {
355
529
  component.emit('start', { track, name: track.animation.name });
356
530
  },
531
+ // @ts-ignore
357
532
  complete: (track, event) => {
358
533
  component.emit('complete', { track, name: track.animation.name });
359
534
  },
535
+ // @ts-ignore
360
536
  interrupt: (track, event) => {
361
537
  component.emit('interrupt', { track, name: track.animation.name });
362
538
  },
363
- end: (track, event) => {
539
+ end: (track, // @ts-ignore
540
+ event) => {
364
541
  component.emit('end', { track, name: track.animation.name });
365
542
  },
366
543
  event: (track, event) => {
544
+ // @ts-ignore
367
545
  component.emit('event', track, event);
368
546
  },
369
547
  });
@@ -386,9 +564,11 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
386
564
  }
387
565
  if (component.armature) {
388
566
  component.armature.destroy({ children: true });
389
- const res = yield eva_js.resource.getResource(component.lastResource);
390
- ((_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);
391
- releaseSpineData(res);
567
+ if (!component.keepResource) {
568
+ const res = yield eva_js.resource.getResource(component.lastResource);
569
+ ((_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);
570
+ releaseSpineData(res);
571
+ }
392
572
  }
393
573
  component.armature = null;
394
574
  delete this.armatures[changed.gameObject.id];
@@ -396,6 +576,7 @@ let SpineSystem = class SpineSystem extends pluginRenderer.Renderer {
396
576
  });
397
577
  }
398
578
  };
579
+ /** 系统名称 */
399
580
  SpineSystem.systemName = 'SpineSystem';
400
581
  SpineSystem = __decorate([
401
582
  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"),r=require("pixi.js");
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");
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 i(e,t,a,r){var i,s=arguments.length,n=s<3?t:null===r?r=Object.getOwnPropertyDescriptor(t,a):r;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,a,r);else for(var o=e.length-1;o>=0;o--)(i=e[o])&&(n=(s<3?i(n):s>3?i(t,a,n):i(t,a))||n);return s>3&&n&&Object.defineProperty(t,a,n),n}function s(e,t,a,r){return new(a||(a=Promise))((function(i,s){function n(e){try{c(r.next(e))}catch(e){s(e)}}function o(e){try{c(r.throw(e))}catch(e){s(e)}}function c(e){e.done?i(e.value):new a((function(t){t(e.value)})).then(n,o)}c((r=r.apply(e,t||[])).next())}))}class n extends e.Component{constructor(){super(...arguments),this.resource="",this.scale=1,this.animationName="",this.autoPlay=!0,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:a,track:r}=e;this.play(t,a,r)}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,a){try{const r=null!=t?t:this.autoPlay;e&&(this.animationName=e),this.armature?(void 0===a&&(a=0),this.armature.state.setAnimation(a,this.animationName,r)):this.waitExecuteInfos.push({playType:!0,name:e,loop:r,track:a})}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,a,r){try{this.armature&&(void 0===r&&(r=0),this.armature.state.addAnimation(r,e,a,t))}catch(e){console.log(e)}}setMix(e,t,a){this.armature&&this.armature.state.data.setMix(e,t,a)}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)}}n.componentName="Spine",i([a.type("string")],n.prototype,"resource",void 0),i([a.type("number")],n.prototype,"scale",void 0),i([a.type("string")],n.prototype,"animationName",void 0),i([a.type("boolean")],n.prototype,"autoPlay",void 0);let o={};function c(e,t,a){return s(this,void 0,void 0,(function*(){let r=o[e.name];if(!r)if(e.complete)r=function(e,t,a,r){const i=t.ske,s=t.atlas,n=new r.AtlasAttachmentLoader(s),c=i instanceof Uint8Array?new r.SkeletonBinary(n):new r.SkeletonJson(n);c.scale=a||1;const m={spineData:c.readSkeletonData(i),ref:0,imageSrc:t.image.label};return o[e]=m,m}(e.name,e.data,t,a);else if(!r)return;return r.ref++,r.spineData}))}let m=class extends t.Renderer{constructor(){super(...arguments),this.armatures={}}init({pixiSpine:a}){this.renderSystem=this.game.getSystem(t.RendererSystem),this.renderSystem.rendererManager.register(this),this.pixiSpine=a,this.game.canvas.addEventListener("webglcontextrestored",(()=>{const t=this.game.gameObjects;let a=[];for(let r in this.armatures){const i=+r;for(let r=0;r<t.length;++r){let s=t[r];if(s.id===i){let t=s.getComponent(n);t&&(this.remove({type:e.OBSERVER_TYPE.REMOVE,gameObject:s,component:t,componentName:n.componentName}),a.push({type:e.OBSERVER_TYPE.ADD,gameObject:s,component:t,componentName:n.componentName}));break}}}setTimeout((()=>{a.forEach((e=>{this.add(e)}))}),1e3)}),!1)}update(e){for(let t in this.armatures)this.armatures[t].update(.001*e.deltaTime);super.update()}componentChanged(t){return s(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,a){var r,i;return s(this,void 0,void 0,(function*(){const s=t.component;clearTimeout(s.addHandler);const n=t.gameObject.id,o=this.increaseAsyncId(n),m=yield e.resource.getResource(s.resource);if(!this.validateAsyncId(n,o))return;const u=yield c(m,s.scale,this.pixiSpine);if(!this.validateAsyncId(n,o))return;if(!u)return void(s.addHandler=setTimeout((()=>{s.destroied||(void 0===a&&(a=20),--a>0?this.add(t,a):console.log("retry exceed max times",s.resource))}),1e3));this.remove(t);const d=null===(i=null===(r=this.renderSystem)||void 0===r?void 0:r.containerManager)||void 0===i?void 0:i.getContainer(t.gameObject.id);if(!d)return;s.lastResource=s.resource;const l=new this.pixiSpine.Spine({skeletonData:u,autoUpdate:!1});if(this.armatures[t.gameObject.id]=l,t.gameObject&&t.gameObject.transform){const e=t.gameObject.transform;l.x=e.size.width*e.origin.x,l.y=e.size.height*e.origin.y}d.addChildAt(l,0),l.update(),s.armature=l,s.emit("loaded",{resource:s.resource}),l.state.addListener({start:(e,t)=>{s.emit("start",{track:e,name:e.animation.name})},complete:(e,t)=>{s.emit("complete",{track:e,name:e.animation.name})},interrupt:(e,t)=>{s.emit("interrupt",{track:e,name:e.animation.name})},end:(e,t)=>{s.emit("end",{track:e,name:e.animation.name})},event:(e,t)=>{s.emit("event",e,t)}})}))}change(e){this.remove(e),this.add(e)}remove(t){var a,i,n,c,m,u;return s(this,void 0,void 0,(function*(){this.increaseAsyncId(t.gameObject.id);const d=t.component;clearTimeout(d.addHandler);const l=this.armatures[t.gameObject.id],h=null===(i=null===(a=this.renderSystem)||void 0===a?void 0:a.containerManager)||void 0===i?void 0:i.getContainer(t.gameObject.id);if(h&&l&&h.removeChild(l),d.armature){d.armature.destroy({children:!0});const t=yield e.resource.getResource(d.lastResource);(null===(c=null===(n=t.data)||void 0===n?void 0:n.image)||void 0===c?void 0:c.src)||null===(u=null===(m=t.data)||void 0===m?void 0:m.image)||void 0===u||u.label,function(t){const a=t.name,i=o[a];i&&(i.ref--,setTimeout((()=>s(this,void 0,void 0,(function*(){if(i.ref<=0){yield r.Assets.unload([t.src.image.url,t.src.atlas.url,t.src.ske.url]);const i=r.Assets.resolver;delete i._assetMap[t.src.image.url],delete i._assetMap[t.src.atlas.url],delete i._assetMap[t.src.ske.url],delete i._resolverHash[t.src.image.url],delete i._resolverHash[t.src.atlas.url],delete i._resolverHash[t.src.ske.url],e.resource.destroy(a),delete o[a]}}))),100))}(t)}d.armature=null,delete this.armatures[t.gameObject.id],t.type,e.OBSERVER_TYPE.CHANGE}))}};m.systemName="SpineSystem",m=i([e.decorators.componentObserver({Spine:["resource"]})],m);var u=m;e.resource.registerResourceType("SPINE"),exports.Spine=n,exports.SpineSystem=u;
16
+ function i(e,t,a,i){var r,n=arguments.length,o=n<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,a):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,a,i);else for(var s=e.length-1;s>=0;s--)(r=e[s])&&(o=(n<3?r(o):n>3?r(t,a,o):r(t,a))||o);return n>3&&o&&Object.defineProperty(t,a,o),o}function r(e,t,a,i){return new(a||(a=Promise))((function(r,n){function o(e){try{c(i.next(e))}catch(e){n(e)}}function s(e){try{c(i.throw(e))}catch(e){n(e)}}function c(e){e.done?r(e.value):new a((function(t){t(e.value)})).then(o,s)}c((i=i.apply(e,t||[])).next())}))}class n extends e.Component{constructor(){super(...arguments),this.resource="",this.scale=1,this.animationName="",this.autoPlay=!0,this.keepResource=!1,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:a,track:i}=e;this.play(t,a,i)}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,a){try{const i=null!=t?t:this.autoPlay;e&&(this.animationName=e),this.armature?(void 0===a&&(a=0),this.armature.state.setAnimation(a,this.animationName,i)):this.waitExecuteInfos.push({playType:!0,name:e,loop:i,track:a})}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,a,i){try{this.armature&&(void 0===i&&(i=0),this.armature.state.addAnimation(i,e,a,t))}catch(e){console.log(e)}}setMix(e,t,a){this.armature&&this.armature.state.data.setMix(e,t,a)}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)}}n.componentName="Spine",i([a.type("string")],n.prototype,"resource",void 0),i([a.type("number")],n.prototype,"scale",void 0),i([a.type("string")],n.prototype,"animationName",void 0),i([a.type("boolean")],n.prototype,"autoPlay",void 0),i([a.type("boolean")],n.prototype,"keepResource",void 0);let o={};function s(e,t,a){return r(this,void 0,void 0,(function*(){let i=o[e.name];if(!i)if(e.complete)i=function(e,t,a,i){const r=t.ske,n=t.atlas,s=new i.AtlasAttachmentLoader(n),c=r instanceof Uint8Array?new i.SkeletonBinary(s):new i.SkeletonJson(s);c.scale=a||1;const m={spineData:c.readSkeletonData(r),ref:0,imageSrc:t.image.label};return o[e]=m,m}(e.name,e.data,t,a);else if(!i)return;return i.ref++,i.spineData}))}let c=class extends t.Renderer{constructor(){super(...arguments),this.armatures={}}init({pixiSpine:a}){this.renderSystem=this.game.getSystem(t.RendererSystem),this.renderSystem.rendererManager.register(this),this.pixiSpine=a,this.game.canvas.addEventListener("webglcontextrestored",(()=>{const t=this.game.gameObjects;let a=[];for(let i in this.armatures){const r=+i;for(let i=0;i<t.length;++i){let o=t[i];if(o.id===r){let t=o.getComponent(n);t&&(this.remove({type:e.OBSERVER_TYPE.REMOVE,gameObject:o,component:t,componentName:n.componentName}),a.push({type:e.OBSERVER_TYPE.ADD,gameObject:o,component:t,componentName:n.componentName}));break}}}setTimeout((()=>{a.forEach((e=>{this.add(e)}))}),1e3)}),!1)}update(e){for(let t in this.armatures)this.armatures[t].update(.001*e.deltaTime);super.update()}componentChanged(t){return r(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,a){var i,n;return r(this,void 0,void 0,(function*(){const r=t.component;clearTimeout(r.addHandler);const o=t.gameObject.id,c=this.increaseAsyncId(o),m=yield e.resource.getResource(r.resource);if(!this.validateAsyncId(o,c))return;const u=yield s(m,r.scale,this.pixiSpine);if(!this.validateAsyncId(o,c))return;if(!u)return void(r.addHandler=setTimeout((()=>{r.destroied||(void 0===a&&(a=20),--a>0?this.add(t,a):console.log("retry exceed max times",r.resource))}),1e3));this.remove(t);const d=null===(n=null===(i=this.renderSystem)||void 0===i?void 0:i.containerManager)||void 0===n?void 0:n.getContainer(t.gameObject.id);if(!d)return;r.lastResource=r.resource;const l=new this.pixiSpine.Spine({skeletonData:u,autoUpdate:!1});if(this.armatures[t.gameObject.id]=l,t.gameObject&&t.gameObject.transform){const e=t.gameObject.transform;l.x=e.size.width*e.origin.x,l.y=e.size.height*e.origin.y}d.addChildAt(l,0),l.update(),r.armature=l,r.emit("loaded",{resource:r.resource}),l.state.addListener({start:(e,t)=>{r.emit("start",{track:e,name:e.animation.name})},complete:(e,t)=>{r.emit("complete",{track:e,name:e.animation.name})},interrupt:(e,t)=>{r.emit("interrupt",{track:e,name:e.animation.name})},end:(e,t)=>{r.emit("end",{track:e,name:e.animation.name})},event:(e,t)=>{r.emit("event",e,t)}})}))}change(e){this.remove(e),this.add(e)}remove(t){var a,i,n,s,c,m;return r(this,void 0,void 0,(function*(){this.increaseAsyncId(t.gameObject.id);const u=t.component;clearTimeout(u.addHandler);const d=this.armatures[t.gameObject.id],l=null===(i=null===(a=this.renderSystem)||void 0===a?void 0:a.containerManager)||void 0===i?void 0:i.getContainer(t.gameObject.id);if(l&&d&&l.removeChild(d),u.armature&&(u.armature.destroy({children:!0}),!u.keepResource)){const t=yield e.resource.getResource(u.lastResource);(null===(s=null===(n=t.data)||void 0===n?void 0:n.image)||void 0===s?void 0:s.src)||null===(m=null===(c=t.data)||void 0===c?void 0:c.image)||void 0===m||m.label,function(t){const a=t.name,i=o[a];i&&(i.ref--,setTimeout((()=>r(this,void 0,void 0,(function*(){i.ref<=0&&(e.resource.destroy(a),delete o[a])}))),100))}(t)}u.armature=null,delete this.armatures[t.gameObject.id],t.type,e.OBSERVER_TYPE.CHANGE}))}};c.systemName="SpineSystem",c=i([e.decorators.componentObserver({Spine:["resource"]})],c);var m=c;e.resource.registerResourceType("SPINE"),exports.Spine=n,exports.SpineSystem=m;
@@ -8,28 +8,172 @@ import { RendererManager } from '@eva/plugin-renderer';
8
8
  import { RendererSystem } from '@eva/plugin-renderer';
9
9
  import { UpdateParams } from '@eva/eva.js';
10
10
 
11
+ /**
12
+ * Spine 骨骼动画组件
13
+ *
14
+ * Spine 组件用于播放 Esoteric Software 的 Spine 骨骼动画。
15
+ * 支持骨骼动画播放控制、动画混合、附件替换等高级功能,
16
+ * 适用于角色动画、复杂特效等需要骨骼动画的场景。
17
+ *
18
+ * 主要功能:
19
+ * - 骨骼动画播放和控制
20
+ * - 动画轨道管理(多动画并行)
21
+ * - 动画混合过渡
22
+ * - 骨骼和附件访问
23
+ * - 支持 Spine 3.6 和 3.8 版本
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * // 创建 Spine 动画
28
+ * const character = new GameObject('character');
29
+ * const spine = new Spine({
30
+ * resource: 'heroSpine', // Spine 资源
31
+ * animationName: 'idle', // 默认动画
32
+ * autoPlay: true, // 自动播放
33
+ * scale: 0.5 // 缩放比例
34
+ * });
35
+ * character.addComponent(spine);
36
+ *
37
+ * // 播放动画
38
+ * spine.play('walk', true); // 循环播放 walk 动画
39
+ *
40
+ * // 停止动画
41
+ * spine.stop();
42
+ *
43
+ * // 动画混合
44
+ * spine.setMix('idle', 'walk', 0.3); // 设置过渡时间
45
+ * spine.play('walk');
46
+ *
47
+ * // 添加动画队列
48
+ * spine.play('attack', false); // 播放攻击动画
49
+ * spine.addAnimation('idle', 0, true); // 攻击完成后回到 idle
50
+ *
51
+ * // 替换附件(换装)
52
+ * spine.setAttachment('weapon', 'sword'); // 将武器槽替换为剑
53
+ *
54
+ * // 访问骨骼
55
+ * const headBone = spine.getBone('head');
56
+ * if (headBone) {
57
+ * headBone.rotation = 15; // 旋转头部
58
+ * }
59
+ *
60
+ * // 多轨道动画
61
+ * spine.play('walk', true, 0); // 轨道0:身体动画
62
+ * spine.play('shoot', false, 1); // 轨道1:上半身动画
63
+ * ```
64
+ */
11
65
  export declare class Spine extends Component<SpineParams> {
66
+ /** 组件名称 */
12
67
  static componentName: string;
68
+ /** Spine 资源名称 */
13
69
  resource: string;
70
+ /** 动画缩放比例 */
14
71
  scale: number;
72
+ /** 当前播放的动画名称 */
15
73
  animationName: string;
74
+ /** 是否自动播放动画 */
16
75
  autoPlay: boolean;
76
+ /** 是否保留资源(销毁时不释放) */
77
+ keepResource: boolean;
78
+ /** Spine 骨架实例(内部使用) */
17
79
  private _armature;
80
+ /** 等待执行的动画操作队列 */
18
81
  private waitExecuteInfos;
82
+ /**
83
+ * 设置骨架实例
84
+ * 当骨架加载完成后自动执行等待队列中的动画操作
85
+ */
19
86
  set armature(val: any);
87
+ /** 获取骨架实例 */
20
88
  get armature(): any;
89
+ /** 组件是否已销毁 */
21
90
  destroied: boolean;
91
+ /** 动画事件处理器 */
22
92
  addHandler: any;
93
+ /** 上一次使用的资源名称 */
23
94
  lastResource: string;
95
+ /**
96
+ * 初始化组件
97
+ * @param obj - 初始化参数
98
+ * @param obj.resource - Spine 资源名称
99
+ * @param obj.animationName - 默认动画名称
100
+ * @param obj.scale - 缩放比例
101
+ * @param obj.autoPlay - 是否自动播放
102
+ */
24
103
  init(obj?: SpineParams): void;
104
+ /** 组件销毁时调用 */
25
105
  onDestroy(): void;
106
+ /**
107
+ * 播放指定动画
108
+ *
109
+ * 如果骨架尚未加载完成,动画操作将被加入等待队列。
110
+ *
111
+ * @param name - 动画名称,不指定则使用 animationName 属性
112
+ * @param loopAnimation - 是否循环播放,默认跟随 autoPlay 属性
113
+ * @param track - 动画轨道编号,默认为 0
114
+ */
26
115
  play(name?: string, loopAnimation?: boolean, track?: number): void;
116
+ /**
117
+ * 停止指定轨道的动画
118
+ *
119
+ * 如果骨架尚未加载完成,停止操作将被加入等待队列。
120
+ *
121
+ * @param track - 动画轨道编号,默认为 0
122
+ */
27
123
  stop(track?: number): void;
124
+ /**
125
+ * 在当前动画之后添加新动画到队列
126
+ *
127
+ * 用于创建动画序列,当前动画播放完毕后自动播放下一个动画。
128
+ *
129
+ * @param name - 动画名称
130
+ * @param delay - 延迟时间(秒)
131
+ * @param loop - 是否循环播放
132
+ * @param track - 动画轨道编号,默认为 0
133
+ */
28
134
  addAnimation(name?: string, delay?: number, loop?: boolean, track?: number): void;
135
+ /**
136
+ * 设置两个动画之间的混合过渡时间
137
+ *
138
+ * 当从一个动画切换到另一个动画时,会在指定时间内进行平滑过渡。
139
+ *
140
+ * @param from - 起始动画名称
141
+ * @param to - 目标动画名称
142
+ * @param duration - 过渡时长(秒)
143
+ */
29
144
  setMix(from: string, to: string, duration: number): void;
145
+ /**
146
+ * 获取指定轨道当前播放的动画名称
147
+ *
148
+ * @param track - 动画轨道编号,默认为 0
149
+ * @returns 动画名称,如果未找到则返回 undefined
150
+ */
30
151
  getAnim(track?: number): any;
152
+ /**
153
+ * 设置默认的动画混合时间
154
+ *
155
+ * 当没有为特定动画对指定混合时间时,将使用此默认值。
156
+ *
157
+ * @param duration - 默认混合时长(秒)
158
+ */
31
159
  setDefaultMix(duration: number): void;
160
+ /**
161
+ * 替换指定插槽的附件
162
+ *
163
+ * 用于换装、武器切换等场景。
164
+ *
165
+ * @param slotName - 插槽名称
166
+ * @param attachmentName - 附件名称
167
+ */
32
168
  setAttachment(slotName: string, attachmentName: string): void;
169
+ /**
170
+ * 获取指定名称的骨骼
171
+ *
172
+ * 可用于直接操作骨骼的位置、旋转、缩放等属性。
173
+ *
174
+ * @param boneName - 骨骼名称
175
+ * @returns 骨骼对象,如果未找到则返回 undefined
176
+ */
33
177
  getBone(boneName: string): any;
34
178
  }
35
179
 
@@ -40,16 +184,45 @@ export declare interface SpineParams {
40
184
  autoPlay?: boolean;
41
185
  }
42
186
 
187
+ /**
188
+ * Spine 骨骼动画系统
189
+ *
190
+ * SpineSystem 负责管理所有 Spine 组件的骨架创建、动画更新和资源管理。
191
+ * 系统会监听 Spine 组件的变化,自动加载骨骼数据并创建动画实例,
192
+ * 并在每帧更新所有活跃的 Spine 动画。
193
+ *
194
+ * 主要功能:
195
+ * - 骨骼数据加载和缓存
196
+ * - 动画实例创建和销毁
197
+ * - 每帧动画状态更新
198
+ * - WebGL 上下文恢复处理
199
+ * - 资源重试机制
200
+ */
43
201
  export declare class SpineSystem extends Renderer {
202
+ /** 系统名称 */
44
203
  static systemName: string;
204
+ /** 骨架实例映射表(游戏对象 ID -> 骨架容器) */
45
205
  armatures: Record<number, Container>;
206
+ /** 渲染系统引用 */
46
207
  renderSystem: RendererSystem;
208
+ /** 渲染器管理器 */
47
209
  rendererManager: RendererManager;
210
+ /** 容器管理器 */
48
211
  containerManager: ContainerManager;
212
+ /** PixiJS Spine 插件实例 */
49
213
  pixiSpine: any;
214
+ /**
215
+ * 初始化系统
216
+ * @param obj - 初始化参数
217
+ * @param obj.pixiSpine - PixiJS Spine 插件实例
218
+ */
50
219
  init({ pixiSpine }: {
51
220
  pixiSpine: any;
52
221
  }): void;
222
+ /**
223
+ * 每帧更新所有 Spine 动画
224
+ * @param e - 更新参数,包含帧间隔时间
225
+ */
53
226
  update(e: UpdateParams): void;
54
227
  componentChanged(changed: ComponentChanged): Promise<void>;
55
228
  add(changed: ComponentChanged, count?: number): Promise<void>;
@@ -1,7 +1,6 @@
1
1
  import { Component, resource, OBSERVER_TYPE, decorators } from '@eva/eva.js';
2
2
  import { Renderer, RendererSystem } from '@eva/plugin-renderer';
3
3
  import { type } from '@eva/inspector-decorator';
4
- import { Assets } from 'pixi.js';
5
4
 
6
5
  /*! *****************************************************************************
7
6
  Copyright (c) Microsoft Corporation. All rights reserved.
@@ -34,15 +33,80 @@ function __awaiter(thisArg, _arguments, P, generator) {
34
33
  });
35
34
  }
36
35
 
36
+ /**
37
+ * Spine 骨骼动画组件
38
+ *
39
+ * Spine 组件用于播放 Esoteric Software 的 Spine 骨骼动画。
40
+ * 支持骨骼动画播放控制、动画混合、附件替换等高级功能,
41
+ * 适用于角色动画、复杂特效等需要骨骼动画的场景。
42
+ *
43
+ * 主要功能:
44
+ * - 骨骼动画播放和控制
45
+ * - 动画轨道管理(多动画并行)
46
+ * - 动画混合过渡
47
+ * - 骨骼和附件访问
48
+ * - 支持 Spine 3.6 和 3.8 版本
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * // 创建 Spine 动画
53
+ * const character = new GameObject('character');
54
+ * const spine = new Spine({
55
+ * resource: 'heroSpine', // Spine 资源
56
+ * animationName: 'idle', // 默认动画
57
+ * autoPlay: true, // 自动播放
58
+ * scale: 0.5 // 缩放比例
59
+ * });
60
+ * character.addComponent(spine);
61
+ *
62
+ * // 播放动画
63
+ * spine.play('walk', true); // 循环播放 walk 动画
64
+ *
65
+ * // 停止动画
66
+ * spine.stop();
67
+ *
68
+ * // 动画混合
69
+ * spine.setMix('idle', 'walk', 0.3); // 设置过渡时间
70
+ * spine.play('walk');
71
+ *
72
+ * // 添加动画队列
73
+ * spine.play('attack', false); // 播放攻击动画
74
+ * spine.addAnimation('idle', 0, true); // 攻击完成后回到 idle
75
+ *
76
+ * // 替换附件(换装)
77
+ * spine.setAttachment('weapon', 'sword'); // 将武器槽替换为剑
78
+ *
79
+ * // 访问骨骼
80
+ * const headBone = spine.getBone('head');
81
+ * if (headBone) {
82
+ * headBone.rotation = 15; // 旋转头部
83
+ * }
84
+ *
85
+ * // 多轨道动画
86
+ * spine.play('walk', true, 0); // 轨道0:身体动画
87
+ * spine.play('shoot', false, 1); // 轨道1:上半身动画
88
+ * ```
89
+ */
37
90
  class Spine extends Component {
38
91
  constructor() {
39
92
  super(...arguments);
93
+ /** Spine 资源名称 */
40
94
  this.resource = '';
95
+ /** 动画缩放比例 */
41
96
  this.scale = 1;
97
+ /** 当前播放的动画名称 */
42
98
  this.animationName = '';
99
+ /** 是否自动播放动画 */
43
100
  this.autoPlay = true;
101
+ /** 是否保留资源(销毁时不释放) */
102
+ this.keepResource = false;
103
+ /** 等待执行的动画操作队列 */
44
104
  this.waitExecuteInfos = [];
45
105
  }
106
+ /**
107
+ * 设置骨架实例
108
+ * 当骨架加载完成后自动执行等待队列中的动画操作
109
+ */
46
110
  set armature(val) {
47
111
  this._armature = val;
48
112
  if (!val)
@@ -61,17 +125,36 @@ class Spine extends Component {
61
125
  }
62
126
  this.waitExecuteInfos = [];
63
127
  }
128
+ /** 获取骨架实例 */
64
129
  get armature() {
65
130
  return this._armature;
66
131
  }
132
+ /**
133
+ * 初始化组件
134
+ * @param obj - 初始化参数
135
+ * @param obj.resource - Spine 资源名称
136
+ * @param obj.animationName - 默认动画名称
137
+ * @param obj.scale - 缩放比例
138
+ * @param obj.autoPlay - 是否自动播放
139
+ */
67
140
  init(obj) {
68
141
  if (!obj)
69
142
  return;
70
143
  Object.assign(this, obj);
71
144
  }
145
+ /** 组件销毁时调用 */
72
146
  onDestroy() {
73
147
  this.destroied = true;
74
148
  }
149
+ /**
150
+ * 播放指定动画
151
+ *
152
+ * 如果骨架尚未加载完成,动画操作将被加入等待队列。
153
+ *
154
+ * @param name - 动画名称,不指定则使用 animationName 属性
155
+ * @param loopAnimation - 是否循环播放,默认跟随 autoPlay 属性
156
+ * @param track - 动画轨道编号,默认为 0
157
+ */
75
158
  play(name, loopAnimation, track) {
76
159
  try {
77
160
  const loop = loopAnimation !== null && loopAnimation !== void 0 ? loopAnimation : this.autoPlay;
@@ -81,6 +164,12 @@ class Spine extends Component {
81
164
  this.waitExecuteInfos.push({
82
165
  playType: true,
83
166
  name,
167
+ /**
168
+ * 在 v1.2.2 之前,Spine 动画的 autoPlay 为 true,动画会循环播放 https://github.com/eva-engine/eva.js/pull/164/files#diff-46e9ae36c04e7a0abedc1e14fd9d1c4e81d8386e9bb851f85971ccdba8957804L131
169
+ * 在 v1.2.2 之前,Spine 动画在每加载完( armature 设置之前)调用 play 是不生效的, 在 v1.2.2 [#164](https://github.com/eva-engine/eva.js/pull/164) 解决了这个问题
170
+ * 解决了不生效的问题以后,加载完成之前调用 play 默认循环是false,导致 autoPlay 下本来循环动画不循环了,和之前表现不一致
171
+ * 为了解决这个问题,在 autoPlay 的情况下,未加载完之前调用 play ,默认循环播放,除非设置不循环参数
172
+ */
84
173
  loop,
85
174
  track,
86
175
  });
@@ -96,6 +185,13 @@ class Spine extends Component {
96
185
  console.log(e);
97
186
  }
98
187
  }
188
+ /**
189
+ * 停止指定轨道的动画
190
+ *
191
+ * 如果骨架尚未加载完成,停止操作将被加入等待队列。
192
+ *
193
+ * @param track - 动画轨道编号,默认为 0
194
+ */
99
195
  stop(track) {
100
196
  if (!this.armature) {
101
197
  this.waitExecuteInfos.push({
@@ -109,6 +205,16 @@ class Spine extends Component {
109
205
  }
110
206
  this.armature.state.setEmptyAnimation(track, 0);
111
207
  }
208
+ /**
209
+ * 在当前动画之后添加新动画到队列
210
+ *
211
+ * 用于创建动画序列,当前动画播放完毕后自动播放下一个动画。
212
+ *
213
+ * @param name - 动画名称
214
+ * @param delay - 延迟时间(秒)
215
+ * @param loop - 是否循环播放
216
+ * @param track - 动画轨道编号,默认为 0
217
+ */
112
218
  addAnimation(name, delay, loop, track) {
113
219
  try {
114
220
  if (!this.armature) {
@@ -124,12 +230,27 @@ class Spine extends Component {
124
230
  console.log(e);
125
231
  }
126
232
  }
233
+ /**
234
+ * 设置两个动画之间的混合过渡时间
235
+ *
236
+ * 当从一个动画切换到另一个动画时,会在指定时间内进行平滑过渡。
237
+ *
238
+ * @param from - 起始动画名称
239
+ * @param to - 目标动画名称
240
+ * @param duration - 过渡时长(秒)
241
+ */
127
242
  setMix(from, to, duration) {
128
243
  if (!this.armature) ;
129
244
  else {
130
245
  this.armature.state.data.setMix(from, to, duration);
131
246
  }
132
247
  }
248
+ /**
249
+ * 获取指定轨道当前播放的动画名称
250
+ *
251
+ * @param track - 动画轨道编号,默认为 0
252
+ * @returns 动画名称,如果未找到则返回 undefined
253
+ */
133
254
  getAnim(track = 0) {
134
255
  try {
135
256
  if (!this.armature) {
@@ -142,18 +263,41 @@ class Spine extends Component {
142
263
  console.log(e);
143
264
  }
144
265
  }
266
+ /**
267
+ * 设置默认的动画混合时间
268
+ *
269
+ * 当没有为特定动画对指定混合时间时,将使用此默认值。
270
+ *
271
+ * @param duration - 默认混合时长(秒)
272
+ */
145
273
  setDefaultMix(duration) {
146
274
  if (!this.armature) ;
147
275
  else {
148
276
  this.armature.state.data.defaultMix = duration;
149
277
  }
150
278
  }
279
+ /**
280
+ * 替换指定插槽的附件
281
+ *
282
+ * 用于换装、武器切换等场景。
283
+ *
284
+ * @param slotName - 插槽名称
285
+ * @param attachmentName - 附件名称
286
+ */
151
287
  setAttachment(slotName, attachmentName) {
152
288
  if (!this.armature) {
153
289
  return;
154
290
  }
155
291
  this.armature.skeleton.setAttachment(slotName, attachmentName);
156
292
  }
293
+ /**
294
+ * 获取指定名称的骨骼
295
+ *
296
+ * 可用于直接操作骨骼的位置、旋转、缩放等属性。
297
+ *
298
+ * @param boneName - 骨骼名称
299
+ * @returns 骨骼对象,如果未找到则返回 undefined
300
+ */
157
301
  getBone(boneName) {
158
302
  if (!this.armature) {
159
303
  return;
@@ -161,6 +305,7 @@ class Spine extends Component {
161
305
  return this.armature.skeleton.findBone(boneName);
162
306
  }
163
307
  }
308
+ /** 组件名称 */
164
309
  Spine.componentName = 'Spine';
165
310
  __decorate([
166
311
  type('string')
@@ -173,7 +318,10 @@ __decorate([
173
318
  ], Spine.prototype, "animationName", void 0);
174
319
  __decorate([
175
320
  type('boolean')
176
- ], Spine.prototype, "autoPlay", void 0);
321
+ ], Spine.prototype, "autoPlay", void 0);
322
+ __decorate([
323
+ type('boolean')
324
+ ], Spine.prototype, "keepResource", void 0);
177
325
 
178
326
  let dataMap = {};
179
327
  function createSpineData(name, data, scale, pixiSpine) {
@@ -213,14 +361,6 @@ function releaseSpineData(res, _imageSrc) {
213
361
  data.ref--;
214
362
  setTimeout(() => __awaiter(this, void 0, void 0, function* () {
215
363
  if (data.ref <= 0) {
216
- yield Assets.unload([res.src.image.url, res.src.atlas.url, res.src.ske.url]);
217
- const resolver = Assets.resolver;
218
- delete resolver._assetMap[res.src.image.url];
219
- delete resolver._assetMap[res.src.atlas.url];
220
- delete resolver._assetMap[res.src.ske.url];
221
- delete resolver._resolverHash[res.src.image.url];
222
- delete resolver._resolverHash[res.src.atlas.url];
223
- delete resolver._resolverHash[res.src.ske.url];
224
364
  resource.destroy(resourceName);
225
365
  delete dataMap[resourceName];
226
366
  }
@@ -228,17 +368,39 @@ function releaseSpineData(res, _imageSrc) {
228
368
  }
229
369
 
230
370
  const MaxRetryCount = 20;
371
+ /**
372
+ * Spine 骨骼动画系统
373
+ *
374
+ * SpineSystem 负责管理所有 Spine 组件的骨架创建、动画更新和资源管理。
375
+ * 系统会监听 Spine 组件的变化,自动加载骨骼数据并创建动画实例,
376
+ * 并在每帧更新所有活跃的 Spine 动画。
377
+ *
378
+ * 主要功能:
379
+ * - 骨骼数据加载和缓存
380
+ * - 动画实例创建和销毁
381
+ * - 每帧动画状态更新
382
+ * - WebGL 上下文恢复处理
383
+ * - 资源重试机制
384
+ */
231
385
  let SpineSystem = class SpineSystem extends Renderer {
232
386
  constructor() {
233
387
  super(...arguments);
388
+ /** 骨架实例映射表(游戏对象 ID -> 骨架容器) */
234
389
  this.armatures = {};
235
390
  }
391
+ /**
392
+ * 初始化系统
393
+ * @param obj - 初始化参数
394
+ * @param obj.pixiSpine - PixiJS Spine 插件实例
395
+ */
236
396
  init({ pixiSpine }) {
237
397
  this.renderSystem = this.game.getSystem(RendererSystem);
238
398
  this.renderSystem.rendererManager.register(this);
239
399
  this.pixiSpine = pixiSpine;
240
400
  this.game.canvas.addEventListener('webglcontextrestored', () => {
401
+ // 重建所有spine
241
402
  const objs = this.game.gameObjects;
403
+ // clearCache();
242
404
  let toAdd = [];
243
405
  for (let k in this.armatures) {
244
406
  const id = +k;
@@ -271,8 +433,14 @@ let SpineSystem = class SpineSystem extends Renderer {
271
433
  }, 1000);
272
434
  }, false);
273
435
  }
436
+ /**
437
+ * 每帧更新所有 Spine 动画
438
+ * @param e - 更新参数,包含帧间隔时间
439
+ */
274
440
  update(e) {
275
441
  for (let key in this.armatures) {
442
+ // TODO: 类型
443
+ // @ts-ignore
276
444
  this.armatures[key].update(e.deltaTime * 0.001);
277
445
  }
278
446
  super.update();
@@ -313,6 +481,7 @@ let SpineSystem = class SpineSystem extends Renderer {
313
481
  component.addHandler = setTimeout(() => {
314
482
  if (!component.destroied) {
315
483
  if (count === undefined) {
484
+ // 最大重试次数
316
485
  count = MaxRetryCount;
317
486
  }
318
487
  count--;
@@ -329,9 +498,11 @@ let SpineSystem = class SpineSystem extends Renderer {
329
498
  this.remove(changed);
330
499
  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);
331
500
  if (!container) {
501
+ // console.warn('添加spine的container不存在');
332
502
  return;
333
503
  }
334
504
  component.lastResource = component.resource;
505
+ // @ts-ignore
335
506
  const armature = new this.pixiSpine.Spine({
336
507
  skeletonData: spineData,
337
508
  autoUpdate: false,
@@ -343,23 +514,30 @@ let SpineSystem = class SpineSystem extends Renderer {
343
514
  armature.y = tran.size.height * tran.origin.y;
344
515
  }
345
516
  container.addChildAt(armature, 0);
517
+ /** 保证第一帧显示正常 */
346
518
  armature.update();
347
519
  component.armature = armature;
520
+ // @ts-ignore
348
521
  component.emit('loaded', { resource: component.resource });
349
522
  armature.state.addListener({
523
+ // @ts-ignore
350
524
  start: (track, event) => {
351
525
  component.emit('start', { track, name: track.animation.name });
352
526
  },
527
+ // @ts-ignore
353
528
  complete: (track, event) => {
354
529
  component.emit('complete', { track, name: track.animation.name });
355
530
  },
531
+ // @ts-ignore
356
532
  interrupt: (track, event) => {
357
533
  component.emit('interrupt', { track, name: track.animation.name });
358
534
  },
359
- end: (track, event) => {
535
+ end: (track, // @ts-ignore
536
+ event) => {
360
537
  component.emit('end', { track, name: track.animation.name });
361
538
  },
362
539
  event: (track, event) => {
540
+ // @ts-ignore
363
541
  component.emit('event', track, event);
364
542
  },
365
543
  });
@@ -382,9 +560,11 @@ let SpineSystem = class SpineSystem extends Renderer {
382
560
  }
383
561
  if (component.armature) {
384
562
  component.armature.destroy({ children: true });
385
- const res = yield resource.getResource(component.lastResource);
386
- ((_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);
387
- releaseSpineData(res);
563
+ if (!component.keepResource) {
564
+ const res = yield resource.getResource(component.lastResource);
565
+ ((_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);
566
+ releaseSpineData(res);
567
+ }
388
568
  }
389
569
  component.armature = null;
390
570
  delete this.armatures[changed.gameObject.id];
@@ -392,6 +572,7 @@ let SpineSystem = class SpineSystem extends Renderer {
392
572
  });
393
573
  }
394
574
  };
575
+ /** 系统名称 */
395
576
  SpineSystem.systemName = 'SpineSystem';
396
577
  SpineSystem = __decorate([
397
578
  decorators.componentObserver({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eva/spine-base",
3
- "version": "2.0.1-beta.3",
3
+ "version": "2.0.1-beta.31",
4
4
  "description": "@eva/spine-base",
5
5
  "main": "index.js",
6
6
  "module": "dist/spine-base.esm.js",
@@ -18,8 +18,8 @@
18
18
  "license": "MIT",
19
19
  "homepage": "https://eva.js.org",
20
20
  "dependencies": {
21
- "@eva/eva.js": "2.0.1-beta.3",
22
- "@eva/plugin-renderer": "2.0.1-beta.3",
21
+ "@eva/eva.js": "2.0.1-beta.31",
22
+ "@eva/plugin-renderer": "2.0.1-beta.31",
23
23
  "@eva/inspector-decorator": "^0.0.5",
24
24
  "pixi.js": "^8.8.1"
25
25
  }