├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── example ├── favicon.ico └── index.html ├── lib ├── egret-spine.d.ts ├── egret-spine.js └── egret-spine.js.map ├── src ├── EventEmitter.ts ├── SkeletonAnimation.ts ├── SkeletonRenderer.ts ├── Track.ts └── egret.d.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | built 17 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "spine-runtimes"] 2 | path = spine-runtimes 3 | url = https://github.com/EsotericSoftware/spine-runtimes.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Spine Runtimes License Agreement 2 | Last updated May 1, 2019. Replaces all prior versions. 3 | 4 | Copyright (c) 2013-2019, Esoteric Software LLC 5 | 6 | Integration of the Spine Runtimes into software or otherwise creating 7 | derivative works of the Spine Runtimes is permitted under the terms and 8 | conditions of Section 2 of the Spine Editor License Agreement: 9 | http://esotericsoftware.com/spine-editor-license 10 | 11 | Otherwise, it is permitted to integrate the Spine Runtimes into software 12 | or otherwise create derivative works of the Spine Runtimes (collectively, 13 | "Products"), provided that each user of the Products must obtain their own 14 | Spine Editor license and redistribution of the Products in any form must 15 | include this license and copyright notice. 16 | 17 | THIS SOFTWARE IS PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY EXPRESS 18 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 20 | NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY DIRECT, INDIRECT, 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 22 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, BUSINESS 23 | INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND ON ANY 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 26 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # egret-spine 2 | A spine runtime for Egret (Demo: https://fightingcat.github.io/egret-spine/example in progress...). 3 | 4 | ## Installing 5 | Considering that Egret doesn't use module, neither does official spine core runtime, this runtime doesn't either, for now. 6 | 7 | See [Egret document](http://developer.egret.com/cn/github/egret-docs/Engine2D/projectConfig/libraryProject/index.html) for integrating to Egret project. 8 | 9 | Or just include the .js file in HTML, if you only intend to build for web, like: 10 | ```HTML 11 | 12 | ``` 13 | 14 | ## Getting started 15 | This implementation doesn't involve resource downloading, your actual code may varies from this boilerplate. 16 | 17 | ```typescript 18 | function loadImage(url: string): Promise { 19 | return new Promise(resolve => { 20 | let loader = new egret.ImageLoader(); 21 | let texture = new egret.Texture(); 22 | 23 | loader.once(egret.Event.COMPLETE, () => { 24 | texture.bitmapData = loader.data; 25 | resolve(texture); 26 | }, null); 27 | 28 | loader.load(url); 29 | }); 30 | } 31 | 32 | function loadText(url: string): Promise { 33 | return new Promise(resolve => { 34 | let request = new egret.HttpRequest(); 35 | request.responseType = egret.HttpResponseType.TEXT; 36 | request.open(url, 'GET'); 37 | request.send(); 38 | request.once(egret.Event.COMPLETE, () => { 39 | resolve(request.response); 40 | }, null) 41 | }); 42 | } 43 | 44 | async function runDemo() { 45 | let json = await loadText('assets/demo.json'); 46 | let atlas = await loadText('assets/demo.atlas'); 47 | let texAtlas = spine.createTextureAtlas(atlas, { 48 | "demo.png": await loadImage('assets/demo.png') 49 | }); 50 | let skelData = spine.createSkeletonData(json, texAtlas); 51 | 52 | let animation = new spine.SkeletonAnimation(skelData); 53 | 54 | animation.play('animation'); 55 | egret.lifecycle.stage.addChild(animation); 56 | } 57 | 58 | runDemo(); 59 | ``` 60 | [More example code](https://github.com/fightingcat/egret-spine/blob/master/example/src/Main.ts). 61 | 62 | ## Learn more 63 | Several classes or structures have been added, all be declared within namespace spine to minimize impact. 64 | 65 | + **createSkeletonData** Helper for creating skeleton data. 66 | + **createTextureAtlas** Helper for creating texture atlas. 67 | + **SkeletonAnimation** A user-friendly animation manager. 68 | + **SkeletonRenderer** A mere skeleton renderer 69 | + **SlotRenderer** Slot renderer for `SkeletonRenderer`. 70 | + **EventEmitter** Embbeded implemation of event emitter. 71 | + **SpineEvent** Enums of animation events. 72 | + **Track** Track abstraction for `SkeletonAnimation`. 73 | -------------------------------------------------------------------------------- /example/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YowaiCoder/egret-spine/f9ef79d28e2f283ef2c9d7a9f4de9cef231ee700/example/favicon.ico -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | Egret Spine Demo 9 | 10 | 11 | 12 | in progress... 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/EventEmitter.ts: -------------------------------------------------------------------------------- 1 | namespace spine { 2 | type ListenerList = { fn: Function, ctx: unknown, once?: boolean }[]; 3 | 4 | export class EventEmitter { 5 | private _listeners = new Map(); 6 | 7 | public on(event: T, fn: Function, ctx?: unknown, once?: boolean): this { 8 | const listeners = this._listeners.get(event); 9 | 10 | if (listeners) { 11 | listeners.push({ fn, ctx, once }); 12 | } 13 | else { 14 | this._listeners.set(event, [{ fn, ctx, once }]); 15 | } 16 | return this; 17 | } 18 | 19 | public once(event: T, fn: Function, ctx?: unknown): this { 20 | const listeners = this._listeners.get(event); 21 | 22 | if (listeners) { 23 | listeners.push({ fn, ctx, once: true }); 24 | } 25 | else { 26 | this._listeners.set(event, [{ fn, ctx, once: true }]); 27 | } 28 | return this; 29 | } 30 | 31 | public off(event: T, fn?: Function, ctx?: unknown, once?: boolean): this { 32 | const listeners = this._listeners.get(event); 33 | 34 | if (arguments.length === 0) { 35 | this._listeners.clear(); 36 | } 37 | else if (arguments.length === 1) { 38 | this._listeners.delete(event); 39 | } 40 | else if (listeners) { 41 | if (arguments.length === 2) { 42 | for (let i = 0; i < listeners.length; i++) { 43 | if (fn === listeners[i].fn) { 44 | listeners.splice(i--, 1); 45 | } 46 | } 47 | } 48 | else if (arguments.length === 3) { 49 | for (let i = 0; i < listeners.length; i++) { 50 | const it = listeners[i]; 51 | 52 | if (fn === it.fn && ctx === it.ctx) { 53 | listeners.splice(i--, 1); 54 | } 55 | } 56 | } 57 | else { 58 | for (let i = 0; i < listeners.length; i++) { 59 | const it = listeners[i]; 60 | 61 | if (fn === it.fn && ctx === it.ctx && (!once || it.once)) { 62 | listeners.splice(i--, 1); 63 | } 64 | } 65 | } 66 | } 67 | return this; 68 | } 69 | 70 | public emit(event: T, a?: unknown, b?: unknown, c?: unknown): this { 71 | const listeners = this._listeners.get(event); 72 | 73 | if (listeners) { 74 | if (arguments.length === 1) { 75 | for (let i = 0; i < listeners.length; i++) { 76 | const it = listeners[i]; 77 | 78 | if (it.once) listeners.splice(i--, 1); 79 | it.fn.call(it.ctx); 80 | } 81 | } 82 | else if (arguments.length === 2) { 83 | for (let i = 0; i < listeners.length; i++) { 84 | const it = listeners[i]; 85 | 86 | if (it.once) listeners.splice(i--, 1); 87 | it.fn.call(it.ctx, a); 88 | } 89 | } 90 | else if (arguments.length === 3) { 91 | for (let i = 0; i < listeners.length; i++) { 92 | const it = listeners[i]; 93 | 94 | if (it.once) listeners.splice(i--, 1); 95 | it.fn.call(it.ctx, a, b); 96 | } 97 | } 98 | else if (arguments.length === 4) { 99 | for (let i = 0; i < listeners.length; i++) { 100 | const it = listeners[i]; 101 | 102 | if (it.once) listeners.splice(i--, 1); 103 | it.fn.call(it.ctx, a, b, c); 104 | } 105 | } 106 | else { 107 | const args = Array.prototype.slice.call(arguments, 1); 108 | 109 | for (let i = 0; i < listeners.length; i++) { 110 | const it = listeners[i]; 111 | 112 | if (it.once) listeners.splice(i--, 1); 113 | it.fn.apply(it.ctx, args); 114 | } 115 | } 116 | } 117 | return this; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/SkeletonAnimation.ts: -------------------------------------------------------------------------------- 1 | namespace spine { 2 | export class SkeletonAnimation extends egret.DisplayObjectContainer { 3 | public readonly renderer: SkeletonRenderer; 4 | public readonly state: AnimationState; 5 | public readonly stateData: AnimationStateData; 6 | public readonly skeleton: Skeleton; 7 | public readonly skeletonData: SkeletonData; 8 | private lastTime: number = 0; 9 | 10 | public constructor(skeletonData: SkeletonData) { 11 | super(); 12 | this.renderer = new SkeletonRenderer(skeletonData); 13 | this.state = this.renderer.state; 14 | this.stateData = this.renderer.stateData; 15 | this.skeleton = this.renderer.skeleton; 16 | this.skeletonData = this.renderer.skeletonData; 17 | this.addChild(this.renderer); 18 | this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddedToStage, this); 19 | } 20 | 21 | public get flipX(): boolean { 22 | return this.renderer.scaleX == -1; 23 | } 24 | 25 | public set flipX(flip: boolean) { 26 | this.renderer.scaleX = flip ? -1 : 1; 27 | } 28 | 29 | public get flipY(): boolean { 30 | return this.renderer.scaleY == 1; 31 | } 32 | 33 | public set flipY(flip: boolean) { 34 | this.renderer.scaleY = flip ? 1 : -1; 35 | } 36 | 37 | public setTimeScale(scale: number) { 38 | this.state.timeScale = scale; 39 | } 40 | 41 | public play(anim: string, loop = 0, trackID = 0): Track { 42 | return this.start(trackID).add(anim, loop); 43 | } 44 | 45 | public start(trackID = 0): Track { 46 | this.skeleton.setToSetupPose(); 47 | return new Track(this, trackID); 48 | } 49 | 50 | public stop(track: number) { 51 | this.state.clearTrack(track); 52 | } 53 | 54 | public stopAll(reset?: boolean) { 55 | this.state.clearTracks(); 56 | if (reset) this.skeleton.setToSetupPose(); 57 | } 58 | 59 | private onAddedToStage() { 60 | this.lastTime = Date.now() / 1000; 61 | this.addEventListener(egret.Event.ENTER_FRAME, this.onEnterFrame, this); 62 | this.addEventListener(egret.Event.REMOVED_FROM_STAGE, this.onRemovedFromStage, this); 63 | } 64 | 65 | private onRemovedFromStage() { 66 | this.removeEventListener(egret.Event.ENTER_FRAME, this.onEnterFrame, this); 67 | this.removeEventListener(egret.Event.REMOVED_FROM_STAGE, this.onRemovedFromStage, this); 68 | } 69 | 70 | private onEnterFrame() { 71 | const now = Date.now() / 1000; 72 | this.renderer.update(now - this.lastTime); 73 | this.lastTime = now; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/SkeletonRenderer.ts: -------------------------------------------------------------------------------- 1 | namespace spine { 2 | 3 | const QuadIndices = [0, 1, 2, 2, 3, 0]; 4 | 5 | class EgretTexture extends Texture { 6 | public smoothing: boolean = false; 7 | 8 | public constructor(readonly original: egret.Texture) { 9 | super(original.bitmapData.source); 10 | } 11 | 12 | public setFilters(minFilter: TextureFilter, magFilter: TextureFilter): void { 13 | const { Nearest, MipMapNearestNearest } = TextureFilter; 14 | const minSmoothing = minFilter !== Nearest && minFilter !== MipMapNearestNearest; 15 | const magSmoothing = magFilter !== Nearest && magFilter !== MipMapNearestNearest; 16 | 17 | this.smoothing = minSmoothing || magSmoothing; 18 | } 19 | 20 | public setWraps(uWrap: TextureWrap, vWrap: TextureWrap): void { } 21 | 22 | public dispose(): void { } 23 | } 24 | 25 | export function createSkeletonData(jsonData: string | {}, atlas: TextureAtlas) { 26 | const skelJson = new SkeletonJson(new AtlasAttachmentLoader(atlas)); 27 | return skelJson.readSkeletonData(jsonData); 28 | } 29 | 30 | export function createTextureAtlas(atlasData: string, textures: Record) { 31 | return new TextureAtlas(atlasData, (file: string) => { 32 | return new EgretTexture(textures[file]); 33 | }); 34 | } 35 | 36 | export class SkeletonRenderer extends egret.DisplayObjectContainer { 37 | public readonly skeleton: Skeleton; 38 | public readonly skeletonData: SkeletonData; 39 | public readonly state: AnimationState; 40 | public readonly stateData: AnimationStateData; 41 | public readonly slotRenderers: SlotRenderer[] = []; 42 | 43 | public constructor(skeletonData: SkeletonData) { 44 | super(); 45 | this.scaleY = -1; 46 | this.touchEnabled = true; 47 | this.skeletonData = skeletonData; 48 | this.stateData = new AnimationStateData(skeletonData); 49 | this.state = new AnimationState(this.stateData); 50 | this.skeleton = new Skeleton(skeletonData); 51 | this.skeleton.updateWorldTransform(); 52 | this.skeleton.setSlotsToSetupPose(); 53 | 54 | for (const slot of this.skeleton.slots) { 55 | const renderer = new SlotRenderer(slot); 56 | 57 | renderer.renderSlot(); 58 | this.addChild(renderer); 59 | this.slotRenderers.push(renderer); 60 | } 61 | } 62 | 63 | public findSlotRenderer(name: string): SlotRenderer | undefined { 64 | return this.slotRenderers.find(it => it.name === name); 65 | } 66 | 67 | public update(dt: number) { 68 | this.state.update(dt); 69 | this.state.apply(this.skeleton); 70 | this.skeleton.updateWorldTransform(); 71 | 72 | const drawOrder = this.skeleton.drawOrder; 73 | 74 | for (let i = 0; i < drawOrder.length; i++) { 75 | const index = drawOrder[i].data.index; 76 | const renderer = this.slotRenderers[index]; 77 | 78 | if (renderer.zIndex !== i) { 79 | renderer.zIndex = i; 80 | } 81 | renderer.renderSlot(); 82 | } 83 | } 84 | } 85 | 86 | export class SlotRenderer extends egret.Mesh { 87 | public premultipliedAlpha: boolean = false; 88 | private attachment?: Attachment; 89 | 90 | public constructor(readonly slot: Slot) { 91 | super(); 92 | this.name = slot.data.name; 93 | if (slot.data.blendMode === BlendMode.Additive) { 94 | this.blendMode = egret.BlendMode.ADD; 95 | } 96 | } 97 | 98 | public renderSlot() { 99 | const boneActive = this.slot.bone.active; 100 | const attachment = this.slot.attachment; 101 | 102 | if (boneActive && attachment) { 103 | if (attachment === this.attachment) return; 104 | 105 | if (attachment instanceof RegionAttachment) { 106 | this.updateColor(attachment.color); 107 | this.updateRegionAttachment(attachment); 108 | this.visible = true; 109 | return; 110 | } 111 | if (attachment instanceof MeshAttachment) { 112 | this.updateColor(attachment.color); 113 | this.updateMeshAttachment(attachment); 114 | this.visible = true; 115 | return; 116 | } 117 | // TODO: implement our own clipper 118 | // if (attachment instanceof ClippingAttachment) { } 119 | } 120 | // no supported attachment. 121 | this.visible = false; 122 | } 123 | 124 | // two color tinting is not supported (due to bad performance of CustomFilter) 125 | private updateColor(color: Color) { 126 | const premultipliedAlpha = this.premultipliedAlpha; 127 | const skelColor = this.slot.bone.skeleton.color; 128 | const slotColor = this.slot.color; 129 | 130 | const alpha = skelColor.a * slotColor.a * color.a; 131 | const scale = premultipliedAlpha ? 255 * alpha : 255; 132 | const r = skelColor.r * slotColor.r * color.r * scale; 133 | const g = skelColor.g * slotColor.g * color.g * scale; 134 | const b = skelColor.b * slotColor.b * color.b * scale; 135 | 136 | this.tint = (r << 16) + (g << 8) + (b | 0); 137 | this.alpha = premultipliedAlpha ? 1 : alpha; 138 | } 139 | 140 | private updateRegionAttachment(attachment: RegionAttachment) { 141 | const region = attachment.region as TextureAtlasRegion; 142 | const texture = region.texture as EgretTexture; 143 | const meshNode = this.$renderNode as egret.sys.MeshNode; 144 | 145 | meshNode.uvs = attachment.uvs as number[]; 146 | meshNode.indices = QuadIndices; 147 | meshNode.vertices.length = 6; 148 | attachment.computeWorldVertices(this.slot.bone, meshNode.vertices, 0, 2); 149 | this.$updateVertices(); 150 | this.texture = texture.original; 151 | this.smoothing = texture.smoothing; 152 | } 153 | 154 | private updateMeshAttachment(attachment: MeshAttachment) { 155 | const length = attachment.worldVerticesLength; 156 | const region = attachment.region as TextureAtlasRegion; 157 | const texture = region.texture as EgretTexture; 158 | const meshNode = this.$renderNode as egret.sys.MeshNode; 159 | 160 | meshNode.uvs = attachment.uvs as number[]; 161 | meshNode.indices = attachment.triangles; 162 | meshNode.vertices.length = length; 163 | attachment.computeWorldVertices(this.slot, 0, length, meshNode.vertices, 0, 2); 164 | this.$updateVertices(); 165 | this.texture = texture.original; 166 | this.smoothing = texture.smoothing; 167 | } 168 | } 169 | } -------------------------------------------------------------------------------- /src/Track.ts: -------------------------------------------------------------------------------- 1 | namespace spine { 2 | interface AnimationRecord { 3 | name: string; 4 | loop: number; 5 | listener?: AnimationListener; 6 | } 7 | 8 | export const enum SpineEvent { 9 | PlayStart, 10 | PlayEnd, 11 | LoopStart, 12 | LoopEnd, 13 | Interrupt, 14 | Custom, 15 | TrackEnd, 16 | } 17 | 18 | export interface AnimationListener { 19 | playStart?: () => void; 20 | playEnd?: () => void; 21 | loopStart?: () => void; 22 | loopEnd?: () => void; 23 | interrupt?: () => void; 24 | custom?: (event: Event) => void; 25 | } 26 | 27 | export class Track extends EventEmitter { 28 | public readonly trackID: number; 29 | public readonly skelAnimation: SkeletonAnimation; 30 | private stateListener: AnimationStateListener; 31 | private trackEntry: TrackEntry | undefined; 32 | private animations: AnimationRecord[] = []; 33 | private disposed: boolean = false; 34 | private loop: number = 0; 35 | 36 | public constructor(skelAnimation: SkeletonAnimation, trackID: number) { 37 | super(); 38 | this.trackID = trackID; 39 | this.skelAnimation = skelAnimation; 40 | this.stateListener = { 41 | complete: () => this.onComplete(), 42 | interrupt: () => this.onInterrupt(), 43 | event: (_, event) => this.onCustomEvent(event), 44 | start: undefined!, end: undefined!, dispose: undefined! 45 | }; 46 | } 47 | 48 | public waitPlayStart() { 49 | return new Promise(resolve => this.once(SpineEvent.PlayStart, resolve)); 50 | } 51 | 52 | public waitPlayEnd() { 53 | return new Promise(resolve => this.once(SpineEvent.PlayEnd, resolve)); 54 | } 55 | 56 | public waitLoopStart() { 57 | return new Promise(resolve => this.once(SpineEvent.LoopStart, resolve)); 58 | } 59 | 60 | public waitLoopEnd() { 61 | return new Promise(resolve => this.once(SpineEvent.LoopEnd, resolve)); 62 | } 63 | 64 | public waitInterrupt() { 65 | return new Promise(resolve => this.once(SpineEvent.Interrupt, resolve)); 66 | } 67 | 68 | public waitTrackEnd() { 69 | return new Promise(resolve => this.once(SpineEvent.TrackEnd, resolve)); 70 | } 71 | 72 | public waitEvent() { 73 | return new Promise(resolve => this.once(SpineEvent.Custom, resolve)); 74 | } 75 | 76 | public waitNamedEvent(name: string) { 77 | return new Promise(resolve => { 78 | const callback = (event: Event) => { 79 | if (event.data.name == name) { 80 | this.off(SpineEvent.Custom, callback); 81 | resolve(event); 82 | } 83 | } 84 | this.on(SpineEvent.Custom, callback); 85 | }); 86 | } 87 | 88 | public add(name: string, loop: number = 1, listener?: AnimationListener) { 89 | if (!this.disposed) { 90 | this.animations.push({ name, loop, listener }); 91 | if (this.animations.length == 1) { 92 | this.playNextAnimation(); 93 | } 94 | } 95 | return this; 96 | } 97 | 98 | private setAnimation(name: string, loop: boolean) { 99 | if (this.trackEntry) this.trackEntry.listener = undefined!; 100 | this.trackEntry = this.skelAnimation.state.setAnimation(this.trackID, name, loop)!; 101 | this.trackEntry.listener = this.stateListener; 102 | this.skelAnimation.renderer.update(0); 103 | } 104 | 105 | private playNextAnimation() { 106 | if (!this.disposed && this.animations.length > 0) { 107 | const { name, listener } = this.animations[0]; 108 | 109 | if (listener) { 110 | if (listener.playStart) this.on(SpineEvent.PlayStart, listener.playStart, listener); 111 | if (listener.playEnd) this.on(SpineEvent.PlayEnd, listener.playEnd, listener); 112 | if (listener.loopStart) this.on(SpineEvent.LoopStart, listener.loopStart, listener); 113 | if (listener.loopEnd) this.on(SpineEvent.LoopEnd, listener.loopEnd, listener); 114 | if (listener.interrupt) this.on(SpineEvent.Interrupt, listener.interrupt, listener); 115 | if (listener.custom) this.on(SpineEvent.Custom, listener.custom, listener); 116 | } 117 | this.loop = 0; 118 | this.setAnimation(name, false); 119 | this.emit(SpineEvent.PlayStart); 120 | this.emit(SpineEvent.LoopStart); 121 | } 122 | } 123 | 124 | private onComplete() { 125 | if (!this.disposed) { 126 | const animation = this.animations[0]; 127 | 128 | this.emit(SpineEvent.LoopEnd); 129 | 130 | if (++this.loop != animation.loop) { 131 | this.setAnimation(animation.name, false); 132 | this.emit(SpineEvent.LoopStart); 133 | } 134 | else { 135 | const listener = animation.listener; 136 | 137 | this.emit(SpineEvent.PlayEnd); 138 | this.animations.shift(); 139 | 140 | if (listener) { 141 | this.off(SpineEvent.PlayStart, listener.playStart); 142 | this.off(SpineEvent.PlayEnd, listener.playEnd); 143 | this.off(SpineEvent.LoopStart, listener.loopStart); 144 | this.off(SpineEvent.LoopEnd, listener.loopEnd); 145 | this.off(SpineEvent.Interrupt, listener.interrupt); 146 | this.off(SpineEvent.Custom, listener.custom); 147 | } 148 | if (this.animations.length > 0) { 149 | this.playNextAnimation(); 150 | } 151 | else { 152 | this.trackEntry!.listener = undefined!; 153 | this.trackEntry = undefined; 154 | this.disposed = true; 155 | this.emit(SpineEvent.TrackEnd); 156 | } 157 | } 158 | } 159 | } 160 | 161 | private onInterrupt() { 162 | if (!this.disposed) { 163 | this.disposed = true; 164 | this.animations.length = 0; 165 | this.emit(SpineEvent.Interrupt); 166 | } 167 | } 168 | 169 | private onCustomEvent(event: Event) { 170 | if (!this.disposed) { 171 | this.emit(SpineEvent.Custom, event); 172 | } 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "System", 4 | "target": "ES6", 5 | "strict": false, 6 | "declaration": true, 7 | "sourceMap": true, 8 | "inlineSources": true, 9 | "outFile": "lib/egret-spine.js", 10 | }, 11 | "include": [ 12 | "spine-runtimes/spine-ts/core", 13 | "src", 14 | ] 15 | } --------------------------------------------------------------------------------