@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/LICENSE +21 -0
- package/README.md +46 -0
- package/dist/@melonjs/spine-plugin.d.ts +4130 -0
- package/dist/@melonjs/spine-plugin.js +15235 -0
- package/package.json +77 -0
- package/src/AssetManager.js +42 -0
- package/src/SkeletonRenderer.js +82 -0
- package/src/index.js +231 -0
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
|
+
}
|