@melonjs/spine-plugin 1.0.0

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/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "@melonjs/spine-plugin",
3
+ "version": "1.0.0",
4
+ "description": "melonJS Spine plugin",
5
+ "type": "module",
6
+ "keywords": [
7
+ "2D",
8
+ "HTML5",
9
+ "javascript",
10
+ "TypeScript",
11
+ "es6",
12
+ "Canvas",
13
+ "WebGL",
14
+ "WebGL2",
15
+ "melonjs",
16
+ "Spine",
17
+ "esotericsoftware"
18
+ ],
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/melonjs/debug-spine.git"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/melonjs/debug-spine/issues"
25
+ },
26
+ "license": "MIT",
27
+ "author": "AltByte Pte Ltd",
28
+ "funding": "https://github.com/sponsors/melonjs",
29
+ "engines": {
30
+ "node": ">= 19"
31
+ },
32
+ "main": "dist/@melonjs/spine-plugin.js",
33
+ "module": "dist/@melonjs/spine-plugin.js",
34
+ "types": "dist/@melonjs/spine-plugin.d.ts",
35
+ "sideEffects": false,
36
+ "files": [
37
+ "dist/",
38
+ "src/",
39
+ "package.json",
40
+ "README.md",
41
+ "LICENSE"
42
+ ],
43
+ "peerDependencies": {
44
+ "melonjs": "^15.9.0"
45
+ },
46
+ "dependencies": {
47
+ "@esotericsoftware/spine-canvas": "^4.2.18",
48
+ "@esotericsoftware/spine-core": "^4.2.18",
49
+ "@esotericsoftware/spine-webgl": "^4.2.18"
50
+ },
51
+ "devDependencies": {
52
+ "@babel/eslint-parser": "^7.22.0",
53
+ "@babel/plugin-syntax-import-assertions": "^7.22.5",
54
+ "@rollup/plugin-commonjs": "^25.0.4",
55
+ "@rollup/plugin-node-resolve": "^15.1.0",
56
+ "@rollup/plugin-replace": "^5.0.2",
57
+ "del-cli": "^5.0.0",
58
+ "eslint": "^8.47.0",
59
+ "eslint-plugin-jsdoc": "^46.4.6",
60
+ "rollup": "^3.28.0",
61
+ "rollup-plugin-bundle-size": "^1.0.3",
62
+ "typescript": "^5.1.6"
63
+ },
64
+ "scripts": {
65
+ "build": "npm run clean && npm run lint && rollup -c --silent && npm run types",
66
+ "lint": "eslint src/**.js rollup.config.mjs",
67
+ "serve": "python3 -m http.server",
68
+ "test": "npm run serve",
69
+ "prepublishOnly": "npm run build",
70
+ "clean": "del-cli --force dist/*",
71
+ "types": "tsc"
72
+ },
73
+ "homepage": "https://github.com/melonjs/debug-spine#readme",
74
+ "directories": {
75
+ "test": "test"
76
+ }
77
+ }
@@ -0,0 +1,42 @@
1
+ import { event, video } from "melonjs";
2
+ import * as spineWebGL from "@esotericsoftware/spine-webgl";
3
+ import * as spineCanvas from "@esotericsoftware/spine-canvas";
4
+
5
+ export default class AssetManager {
6
+ asset_manager;
7
+ pathPrefix;
8
+
9
+ constructor(pathPrefix = "") {
10
+ event.once(event.VIDEO_INIT, this.initAssetManager.bind(this));
11
+ this.pathPrefix = pathPrefix;
12
+ }
13
+
14
+ initAssetManager() {
15
+ if (video.renderer.WebGLVersion >= 1) {
16
+ this.asset_manager = new spineWebGL.AssetManager(video.renderer.getContext(), this.pathPrefix);
17
+ } else {
18
+ // canvas renderer
19
+ this.asset_manager = new spineCanvas.AssetManager(this.pathPrefix);
20
+ }
21
+ }
22
+
23
+ setPrefix(pathPrefix) {
24
+ this.asset_manager.pathPrefix = pathPrefix;
25
+ }
26
+
27
+ loadAsset(atlas, skel) {
28
+ if (atlas) {
29
+ this.asset_manager.loadTextureAtlas(atlas);
30
+ }
31
+
32
+ if (skel.endsWith(".skel")) {
33
+ this.asset_manager.loadBinary(skel);
34
+ } else {
35
+ this.asset_manager.loadText(skel);
36
+ }
37
+ }
38
+
39
+ loadAll() {
40
+ return this.asset_manager.loadAll();
41
+ }
42
+ }
@@ -0,0 +1,82 @@
1
+ import { Color as MColor, Math as MMath } from "melonjs";
2
+ import { RegionAttachment, Color } from "@esotericsoftware/spine-core";
3
+
4
+ const worldVertices = new Float32Array(8);
5
+ const blendModeLUT = ["normal", "additive", "multiply", "screen"];
6
+
7
+ export default class SkeletonRenderer {
8
+ isWebGLRenderer;
9
+ skeletonRenderer;
10
+ runtime;
11
+ tempColor = new Color();
12
+ tintColor = new MColor();
13
+
14
+ constructor(runtime) {
15
+ this.runtime = runtime;
16
+ this.skeletonRenderer = new runtime.SkeletonRenderer();
17
+ this.tempColor = new Color();
18
+ }
19
+
20
+ draw(renderer, skeleton) {
21
+ // based on https://github.com/EsotericSoftware/spine-runtimes/blob/4.1/spine-ts/spine-canvas/src/SkeletonRenderer.ts
22
+ let drawOrder = skeleton.drawOrder;
23
+ let skeletonColor = skeleton.color;
24
+
25
+ for (var i = 0, n = drawOrder.length; i < n; i++) {
26
+ let slot = drawOrder[i];
27
+ let bone = slot.bone;
28
+
29
+ if (!bone.active) continue;
30
+
31
+ let attachment = slot.getAttachment();
32
+
33
+ if (attachment instanceof RegionAttachment) {
34
+ let region = attachment.region;
35
+ let image = region.texture.getImage();
36
+ let slotColor = slot.color;
37
+ let regionColor = attachment.color;
38
+ let blendMode = slot.data.blendMode;
39
+ let color = this.tintColor;
40
+
41
+ color.setFloat(skeletonColor.r * slotColor.r * regionColor.r,
42
+ skeletonColor.g * slotColor.g * regionColor.g,
43
+ skeletonColor.b * slotColor.b * regionColor.b,
44
+ skeletonColor.a * slotColor.a * regionColor.a);
45
+
46
+ attachment.computeWorldVertices(slot, worldVertices, 0, 2);
47
+
48
+ renderer.save();
49
+ renderer.transform(bone.a, bone.c, bone.b, bone.d, bone.worldX, bone.worldY);
50
+ renderer.translate(attachment.offset[0], attachment.offset[1]);
51
+ renderer.rotate(MMath.degToRad(attachment.rotation));
52
+
53
+ let atlasScale = attachment.width / region.originalWidth;
54
+ renderer.scale(atlasScale * attachment.scaleX, atlasScale * attachment.scaleY);
55
+
56
+ let w = region.width, h = region.height;
57
+ let hW = w / 2, hH = h / 2;
58
+ renderer.translate(hW, hH);
59
+ if (region.degrees === 90) {
60
+ let t = w;
61
+ w = h;
62
+ h = t;
63
+ renderer.rotate(-MMath.ETA);
64
+ }
65
+ renderer.scale(1, -1);
66
+ renderer.translate(-hW, -hH);
67
+ renderer.setTint(color);
68
+ renderer.setBlendMode(blendModeLUT[blendMode]);
69
+ renderer.setGlobalAlpha(color.a);
70
+ renderer.drawImage(image, image.width * region.u, image.height * region.v, w, h, 0, 0, w, h);
71
+ renderer.restore();
72
+ }
73
+ }
74
+ }
75
+
76
+ updateSkeleton(skeleton) {
77
+ // for update skeleton
78
+ //if (this.isWebGLRenderer === false) {
79
+ skeleton.scaleY = -1;
80
+ //}
81
+ }
82
+ }
package/src/index.js ADDED
@@ -0,0 +1,231 @@
1
+ import { Math, Renderable, Vector2d, video } from "melonjs";
2
+
3
+ import * as spineWebGL from "@esotericsoftware/spine-webgl";
4
+ import * as spineCanvas from "@esotericsoftware/spine-canvas";
5
+ import { Vector2 } from "@esotericsoftware/spine-core";
6
+
7
+ import AssetManager from "./AssetManager.js";
8
+ import SkeletonRenderer from "./SkeletonRenderer.js";
9
+
10
+ export let assetManager = new AssetManager();
11
+
12
+ export default class Spine extends Renderable {
13
+ runtime;
14
+ skeleton;
15
+ animationState;
16
+ skeletonRenderer;
17
+ assetManager;
18
+ root;
19
+ boneOffset;
20
+ boneSize;
21
+
22
+ constructor(x, y, settings) {
23
+ super(x, y, settings.width, settings.height);
24
+
25
+ if (video.renderer.WebGLVersion >= 1) {
26
+ this.runtime = spineWebGL;
27
+ } else {
28
+ this.runtime = spineCanvas;
29
+ }
30
+
31
+ this.assetManager = assetManager.asset_manager;
32
+ this.skeletonRenderer = new SkeletonRenderer(this.runtime);
33
+
34
+ // force anchorPoint to 0,0
35
+ this.anchorPoint.set(0, 0);
36
+
37
+ // displaying order
38
+ if (typeof settings.z !== "undefined") {
39
+ this.pos.z = settings.z;
40
+ }
41
+
42
+ this.scaleValue = {x: 1, y: 1};
43
+ this.boneOffset = new Vector2();
44
+ this.boneSize = new Vector2();
45
+
46
+ this.mixTime = settings.mixTime || 0.2;
47
+
48
+ if (settings.jsonFile) {
49
+ this.jsonFile = settings.jsonFile;
50
+ this.atlasFile = settings.atlasFile;
51
+
52
+ this.setSkeleton(this.atlasFile, this.jsonFile);
53
+ }
54
+ }
55
+
56
+ setSkeleton(atlasFile, jsonFile) {
57
+ this.loadSpineAssets(atlasFile, jsonFile);
58
+ this.root = this.skeleton.getRootBone();
59
+ }
60
+
61
+ loadSpineAssets(atlasFile, jsonFile) {
62
+ // Create the texture atlas and skeleton data.
63
+ let atlas = this.assetManager.require(atlasFile);
64
+ let atlasLoader = new this.runtime.AtlasAttachmentLoader(atlas);
65
+ let skeletonJson = new this.runtime.SkeletonJson(atlasLoader);
66
+ let skeletonData = skeletonJson.readSkeletonData(this.assetManager.require(jsonFile));
67
+
68
+ // Instantiate a new skeleton based on the atlas and skeleton data.
69
+ this.skeleton = new this.runtime.Skeleton(skeletonData);
70
+ this.skeleton.setToSetupPose();
71
+ this.skeleton.updateWorldTransform();
72
+
73
+ // Setup an animation state with a default mix of 0.2 seconds.
74
+ var animationStateData = new this.runtime.AnimationStateData(this.skeleton.data);
75
+ animationStateData.defaultMix = this.mixTime;
76
+ this.animationState = new this.runtime.AnimationState(animationStateData);
77
+ }
78
+
79
+ rotate(angle, v) {
80
+ // rotation for rootBone is in degrees (anti-clockwise)
81
+ this.skeleton.getRootBone().rotation -= Math.radToDeg(angle) + 90;
82
+ // melonJS rotate method takes radians
83
+ super.rotate(angle, v);
84
+ }
85
+
86
+ scale(x, y = x) {
87
+ this.scaleValue = {x, y};
88
+ super.scale(x, y);
89
+ }
90
+
91
+ updateBounds(absolute = true) {
92
+ if (this.isRenderable) {
93
+ let bounds = this.getBounds();
94
+ let isIdentity = this.autoTransform === true && this.currentTransform.isIdentity();
95
+
96
+ bounds.clear();
97
+
98
+ if (typeof this.skeleton !== "undefined") {
99
+ let rootBone = this.skeleton.getRootBone();
100
+ let boneOffset = this.boneOffset;
101
+ let boneSize = this.boneSize;
102
+
103
+ this.skeleton.getBounds(boneOffset, boneSize);
104
+
105
+ bounds.addFrame(
106
+ boneOffset.x - rootBone.x,
107
+ boneOffset.y - rootBone.y,
108
+ boneSize.x + boneOffset.x - rootBone.x,
109
+ boneSize.y + boneOffset.y - rootBone.y,
110
+ !isIdentity ? this.currentTransform : undefined
111
+ );
112
+ } else {
113
+ bounds.addFrame(
114
+ 0,
115
+ 0,
116
+ this.width,
117
+ this.height,
118
+ !isIdentity ? this.currentTransform : undefined
119
+ );
120
+ }
121
+
122
+ if (absolute === true) {
123
+ var absPos = this.getAbsolutePosition();
124
+ bounds.centerOn(absPos.x + bounds.centerX, absPos.y + bounds.centerY);
125
+ }
126
+ return bounds;
127
+
128
+ } else {
129
+ // manage the case where updateBounds is called
130
+ // before the object being yet properly initialized
131
+ return super.updateBounds(absolute);
132
+ }
133
+ }
134
+
135
+ /**
136
+ * update function (automatically called by melonJS).
137
+ * @param {number} dt - time since the last update in milliseconds.
138
+ * @returns {boolean} true if the renderable is dirty
139
+ */
140
+ update(dt) { // eslint-disable-line no-unused-vars
141
+ if (typeof this.skeleton !== "undefined") {
142
+ let rootBone = this.skeleton.getRootBone();
143
+
144
+ // update the root bone position
145
+ if (rootBone.x !== this.pos.x) {
146
+ rootBone.x = this.pos.x;
147
+ }
148
+ if (rootBone.y !== this.pos.y) {
149
+ rootBone.y = this.pos.y;
150
+ }
151
+
152
+ this.skeletonRenderer.updateSkeleton(rootBone);
153
+ // Update and apply the animation state, update the skeleton's
154
+ // world transforms and render the skeleton.
155
+ this.animationState.update(dt / 1000);
156
+ this.animationState.apply(this.skeleton);
157
+ this.skeleton.updateWorldTransform();
158
+
159
+ this.updateBounds();
160
+ }
161
+ return true;
162
+ }
163
+
164
+
165
+ /**
166
+ * draw this spine object
167
+ * @name draw
168
+ * @memberof Spine
169
+ * @protected
170
+ * @param {CanvasRenderer|WebGLRenderer} renderer - a renderer instance
171
+ * @param {Camera2d} [viewport] - the viewport to (re)draw
172
+ */
173
+ draw(renderer) {
174
+ this.skeletonRenderer.draw(renderer, this.skeleton);
175
+ }
176
+
177
+
178
+ setAnimationByIndex(track_index, index, loop = false) {
179
+ if (index < 0 || index >= this.skeleton.data.animations.length)
180
+ { return (console.log("Animation Index not found")); }
181
+ else
182
+ { this.animationState.setAnimation(track_index, this.skeleton.data.animations[index].name, loop); }
183
+ }
184
+
185
+ setAnimation(track_index, name, loop = false) {
186
+ this.animationState.setAnimation(track_index, name, loop);
187
+ }
188
+
189
+ addAnimationByIndex(track_index, index, loop = false, delay = 0) {
190
+ if (index < 0 || index >= this.skeleton.data.animations.length)
191
+ { return (console.log("Animation Index not found")); }
192
+ else
193
+ { this.animationState.addAnimation(track_index, this.skeleton.data.animations[index].name, loop, delay); }
194
+ }
195
+
196
+ addAnimationByName(track_index, animationName, loop = false, delay = 0) {
197
+ this.animationState.addAnimation(track_index, animationName, loop, delay);
198
+ }
199
+
200
+ getSpinePosition() {
201
+ return new Vector2d(this.pos.x, this.pos.y);
202
+ }
203
+
204
+ setSpineSize(width, height) {
205
+ this.width = width;
206
+ this.height = height;
207
+ }
208
+
209
+ getSpineSize() {
210
+ return {
211
+ width: this.width,
212
+ height: this.height
213
+ };
214
+ }
215
+
216
+ setDefaultMixTime(mixTime) {
217
+ this.animationState.data.defaultMix = mixTime;
218
+ }
219
+
220
+ setTransitionMixTime(firstAnimation, secondAnimation, mixTime) {
221
+ this.animationState.setMix(firstAnimation, secondAnimation, mixTime);
222
+ }
223
+
224
+ setSkinByName(skinName) {
225
+ this.skeleton.setSkinByName(skinName);
226
+ }
227
+
228
+ setToSetupPose() {
229
+ this.skeleton.setToSetupPose();
230
+ }
231
+ }