@melonjs/spine-plugin 1.2.1 → 1.4.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/CHANGELOG.md +16 -4
- package/README.md +27 -14
- package/dist/@melonjs/spine-plugin.d.ts +240 -70
- package/dist/@melonjs/spine-plugin.js +499 -90
- package/package.json +11 -10
- package/src/AssetManager.js +38 -12
- package/src/SkeletonRenderer.js +132 -41
- package/src/index.js +189 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@melonjs/spine-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "melonJS Spine plugin",
|
|
5
5
|
"homepage": "https://github.com/melonjs/spine-plugin#readme",
|
|
6
6
|
"type": "module",
|
|
@@ -46,25 +46,26 @@
|
|
|
46
46
|
"CHANGELOG.md"
|
|
47
47
|
],
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"melonjs": "^15.
|
|
49
|
+
"melonjs": "^15.10.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@esotericsoftware/spine-canvas": "^4.2.
|
|
53
|
-
"@esotericsoftware/spine-core": "^4.2.
|
|
54
|
-
"@esotericsoftware/spine-webgl": "^4.2.
|
|
52
|
+
"@esotericsoftware/spine-canvas": "^4.2.19",
|
|
53
|
+
"@esotericsoftware/spine-core": "^4.2.19",
|
|
54
|
+
"@esotericsoftware/spine-webgl": "^4.2.19"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@babel/eslint-parser": "^7.22.
|
|
57
|
+
"@babel/eslint-parser": "^7.22.15",
|
|
58
58
|
"@babel/plugin-syntax-import-assertions": "^7.22.5",
|
|
59
59
|
"@rollup/plugin-commonjs": "^25.0.4",
|
|
60
|
+
"@rollup/plugin-json": "^6.0.0",
|
|
60
61
|
"@rollup/plugin-node-resolve": "^15.2.1",
|
|
61
62
|
"@rollup/plugin-replace": "^5.0.2",
|
|
62
|
-
"del-cli": "^5.
|
|
63
|
-
"eslint": "^8.
|
|
64
|
-
"eslint-plugin-jsdoc": "^46.5.
|
|
63
|
+
"del-cli": "^5.1.0",
|
|
64
|
+
"eslint": "^8.48.0",
|
|
65
|
+
"eslint-plugin-jsdoc": "^46.5.1",
|
|
65
66
|
"rollup": "^3.28.1",
|
|
66
67
|
"rollup-plugin-bundle-size": "^1.0.3",
|
|
67
|
-
"typescript": "^5.
|
|
68
|
+
"typescript": "^5.2.2"
|
|
68
69
|
},
|
|
69
70
|
"scripts": {
|
|
70
71
|
"build": "npm run clean && npm run lint && rollup -c --silent && npm run types",
|
package/src/AssetManager.js
CHANGED
|
@@ -2,28 +2,50 @@ import { event, video } from "melonjs";
|
|
|
2
2
|
import * as spineWebGL from "@esotericsoftware/spine-webgl";
|
|
3
3
|
import * as spineCanvas from "@esotericsoftware/spine-canvas";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @classdesc
|
|
7
|
+
* An Asset Manager class to load spine assets
|
|
8
|
+
*/
|
|
5
9
|
export default class AssetManager {
|
|
6
10
|
asset_manager;
|
|
7
11
|
pathPrefix;
|
|
8
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} [pathPrefix=""] - a default path prefix for assets location
|
|
15
|
+
*/
|
|
9
16
|
constructor(pathPrefix = "") {
|
|
10
|
-
event.once(event.VIDEO_INIT,
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
this.asset_manager = new spineCanvas.AssetManager(this.pathPrefix);
|
|
20
|
-
}
|
|
17
|
+
event.once(event.VIDEO_INIT, () => {
|
|
18
|
+
this.pathPrefix = pathPrefix;
|
|
19
|
+
if (video.renderer.WebGLVersion >= 1) {
|
|
20
|
+
this.asset_manager = new spineWebGL.AssetManager(video.renderer.getContext(), this.pathPrefix);
|
|
21
|
+
} else {
|
|
22
|
+
// canvas renderer
|
|
23
|
+
this.asset_manager = new spineCanvas.AssetManager(this.pathPrefix);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
/**
|
|
29
|
+
* set a default path prefix for assets location
|
|
30
|
+
* @see loadAsset
|
|
31
|
+
* @param {string} pathPrefix
|
|
32
|
+
*/
|
|
23
33
|
setPrefix(pathPrefix) {
|
|
24
|
-
this.asset_manager.pathPrefix = pathPrefix;
|
|
34
|
+
this.asset_manager.pathPrefix = this.pathPrefix = pathPrefix;
|
|
25
35
|
}
|
|
26
36
|
|
|
37
|
+
/**
|
|
38
|
+
* define all spine assets to be loaded
|
|
39
|
+
* @see setPrefix
|
|
40
|
+
* @see loadAll
|
|
41
|
+
* @param {string} atlas
|
|
42
|
+
* @param {string} skel
|
|
43
|
+
* @example
|
|
44
|
+
* // load spine assets
|
|
45
|
+
* Spine.assetManager.setPrefix("data/spine/");
|
|
46
|
+
* Spine.assetManager.loadAsset("alien.atlas", "alien-ess.json");
|
|
47
|
+
* await Spine.assetManager.loadAll();
|
|
48
|
+
*/
|
|
27
49
|
loadAsset(atlas, skel) {
|
|
28
50
|
if (atlas) {
|
|
29
51
|
this.asset_manager.loadTextureAtlas(atlas);
|
|
@@ -36,6 +58,10 @@ export default class AssetManager {
|
|
|
36
58
|
}
|
|
37
59
|
}
|
|
38
60
|
|
|
61
|
+
/**
|
|
62
|
+
* load all defined spine assets
|
|
63
|
+
* @see loadAsset
|
|
64
|
+
*/
|
|
39
65
|
loadAll() {
|
|
40
66
|
return this.asset_manager.loadAll();
|
|
41
67
|
}
|
package/src/SkeletonRenderer.js
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { Color as MColor, Math as MMath, Polygon } from "melonjs";
|
|
2
2
|
import { SkeletonClipping, ClippingAttachment, MeshAttachment, RegionAttachment } from "@esotericsoftware/spine-core";
|
|
3
3
|
|
|
4
|
-
const
|
|
4
|
+
const vertexSize = 2 + 2 + 4;
|
|
5
5
|
const blendModeLUT = ["normal", "additive", "multiply", "screen"];
|
|
6
|
-
|
|
7
6
|
const regionDebugColor = "green";
|
|
7
|
+
const meshDebugColor = "yellow";
|
|
8
8
|
const clipDebugColor = "blue";
|
|
9
9
|
|
|
10
|
+
let worldVertices = new Float32Array(vertexSize * 1024);
|
|
11
|
+
|
|
10
12
|
export default class SkeletonRenderer {
|
|
11
13
|
skeletonRenderer;
|
|
12
14
|
runtime;
|
|
13
15
|
tintColor = new MColor();
|
|
14
|
-
|
|
16
|
+
tempColor = new MColor();
|
|
15
17
|
debugRendering = false;
|
|
16
18
|
clipper = new SkeletonClipping();
|
|
17
19
|
clippingVertices = [];
|
|
@@ -30,11 +32,12 @@ export default class SkeletonRenderer {
|
|
|
30
32
|
let debugRendering = this.debugRendering;
|
|
31
33
|
|
|
32
34
|
for (var i = 0, n = drawOrder.length; i < n; i++) {
|
|
33
|
-
let clippedVertexSize = clipper.isClipping() ? 2 :
|
|
35
|
+
let clippedVertexSize = clipper.isClipping() ? 2 : vertexSize;
|
|
34
36
|
let slot = drawOrder[i];
|
|
35
37
|
let bone = slot.bone;
|
|
36
38
|
let image;
|
|
37
39
|
let region;
|
|
40
|
+
let triangles;
|
|
38
41
|
|
|
39
42
|
if (!bone.active) {
|
|
40
43
|
clipper.clipEndWithSlot(slot);
|
|
@@ -49,14 +52,10 @@ export default class SkeletonRenderer {
|
|
|
49
52
|
region = attachment.region;
|
|
50
53
|
image = region.texture.getImage();
|
|
51
54
|
} else if (attachment instanceof MeshAttachment) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
region = mesh.region;
|
|
57
|
-
image = mesh.region.texture.getImage();
|
|
58
|
-
*/
|
|
59
|
-
console.warn("spine-plugin: MeshAttachment is not supported yet");
|
|
55
|
+
this.computeMeshVertices(slot, attachment, false, clippedVertexSize);
|
|
56
|
+
triangles = attachment.triangles;
|
|
57
|
+
region = attachment.region;
|
|
58
|
+
image = region.texture.getImage();
|
|
60
59
|
} else if (attachment instanceof ClippingAttachment) {
|
|
61
60
|
let clip = attachment;
|
|
62
61
|
let vertices = this.clippingVertices;
|
|
@@ -80,43 +79,55 @@ export default class SkeletonRenderer {
|
|
|
80
79
|
let blendMode = slot.data.blendMode;
|
|
81
80
|
let color = this.tintColor;
|
|
82
81
|
|
|
82
|
+
renderer.save();
|
|
83
|
+
|
|
83
84
|
color.setFloat(skeletonColor.r * slotColor.r * regionColor.r,
|
|
84
85
|
skeletonColor.g * slotColor.g * regionColor.g,
|
|
85
86
|
skeletonColor.b * slotColor.b * regionColor.b,
|
|
86
87
|
skeletonColor.a * slotColor.a * regionColor.a);
|
|
87
88
|
|
|
88
|
-
renderer.
|
|
89
|
-
renderer.transform(bone.a, bone.c, bone.b, bone.d, bone.worldX, bone.worldY);
|
|
90
|
-
renderer.translate(attachment.offset[0], attachment.offset[1]);
|
|
91
|
-
renderer.rotate(MMath.degToRad(attachment.rotation));
|
|
92
|
-
|
|
93
|
-
let atlasScale = attachment.width / region.originalWidth;
|
|
94
|
-
renderer.scale(atlasScale * attachment.scaleX, atlasScale * attachment.scaleY);
|
|
95
|
-
|
|
96
|
-
let w = region.width, h = region.height;
|
|
97
|
-
let hW = w / 2, hH = h / 2;
|
|
98
|
-
renderer.translate(hW, hH);
|
|
99
|
-
if (region.degrees === 90) {
|
|
100
|
-
let t = w;
|
|
101
|
-
w = h;
|
|
102
|
-
h = t;
|
|
103
|
-
renderer.rotate(-MMath.ETA);
|
|
104
|
-
}
|
|
105
|
-
renderer.scale(1, -1);
|
|
106
|
-
renderer.translate(-hW, -hH);
|
|
89
|
+
renderer.setGlobalAlpha(color.a);
|
|
107
90
|
renderer.setTint(color);
|
|
108
91
|
renderer.setBlendMode(blendModeLUT[blendMode]);
|
|
109
|
-
renderer.setGlobalAlpha(color.a);
|
|
110
92
|
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
93
|
+
if (typeof triangles !== "undefined") {
|
|
94
|
+
let vertices = worldVertices;
|
|
95
|
+
for (var j = 0; j < triangles.length; j += 3) {
|
|
96
|
+
let t1 = triangles[j] * 8, t2 = triangles[j + 1] * 8, t3 = triangles[j + 2] * 8;
|
|
97
|
+
let x0 = vertices[t1], y0 = vertices[t1 + 1], u0 = vertices[t1 + 6], v0 = vertices[t1 + 7];
|
|
98
|
+
let x1 = vertices[t2], y1 = vertices[t2 + 1], u1 = vertices[t2 + 6], v1 = vertices[t2 + 7];
|
|
99
|
+
let x2 = vertices[t3], y2 = vertices[t3 + 1], u2 = vertices[t3 + 6], v2 = vertices[t3 + 7];
|
|
100
|
+
|
|
101
|
+
this.drawTriangle(renderer, image, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2);
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
let atlasScale = attachment.width / region.originalWidth;
|
|
105
|
+
let w = region.width, h = region.height;
|
|
106
|
+
let hW = w / 2, hH = h / 2;
|
|
107
|
+
|
|
108
|
+
renderer.transform(bone.a, bone.c, bone.b, bone.d, bone.worldX, bone.worldY);
|
|
109
|
+
renderer.translate(attachment.offset[0], attachment.offset[1]);
|
|
110
|
+
renderer.rotate(MMath.degToRad(attachment.rotation));
|
|
111
|
+
renderer.scale(atlasScale * attachment.scaleX, atlasScale * attachment.scaleY);
|
|
112
|
+
renderer.translate(hW, hH);
|
|
113
|
+
if (region.degrees === 90) {
|
|
114
|
+
let t = w;
|
|
115
|
+
w = h;
|
|
116
|
+
h = t;
|
|
117
|
+
renderer.rotate(-MMath.ETA);
|
|
118
|
+
}
|
|
119
|
+
renderer.scale(1, -1);
|
|
120
|
+
renderer.translate(-hW, -hH);
|
|
121
|
+
|
|
122
|
+
if (clipper.isClipping()) {
|
|
123
|
+
renderer.setMask(clippingMask);
|
|
124
|
+
}
|
|
125
|
+
renderer.drawImage(image, image.width * region.u, image.height * region.v, w, h, 0, 0, w, h);
|
|
126
|
+
|
|
127
|
+
if (debugRendering === true) {
|
|
128
|
+
renderer.setColor(regionDebugColor);
|
|
129
|
+
renderer.strokeRect(0, 0, w, h);
|
|
130
|
+
}
|
|
120
131
|
}
|
|
121
132
|
|
|
122
133
|
renderer.restore();
|
|
@@ -126,4 +137,84 @@ export default class SkeletonRenderer {
|
|
|
126
137
|
}
|
|
127
138
|
clipper.clipEnd();
|
|
128
139
|
}
|
|
140
|
+
|
|
141
|
+
drawTriangle(renderer, img, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2) {
|
|
142
|
+
u0 *= img.width;
|
|
143
|
+
v0 *= img.height;
|
|
144
|
+
u1 *= img.width;
|
|
145
|
+
v1 *= img.height;
|
|
146
|
+
u2 *= img.width;
|
|
147
|
+
v2 *= img.height;
|
|
148
|
+
|
|
149
|
+
renderer.save();
|
|
150
|
+
renderer.beginPath();
|
|
151
|
+
renderer.moveTo(x0, y0);
|
|
152
|
+
renderer.lineTo(x1, y1);
|
|
153
|
+
renderer.lineTo(x2, y2);
|
|
154
|
+
renderer.closePath();
|
|
155
|
+
renderer.setMask();
|
|
156
|
+
|
|
157
|
+
x1 -= x0;
|
|
158
|
+
y1 -= y0;
|
|
159
|
+
x2 -= x0;
|
|
160
|
+
y2 -= y0;
|
|
161
|
+
|
|
162
|
+
u1 -= u0;
|
|
163
|
+
v1 -= v0;
|
|
164
|
+
u2 -= u0;
|
|
165
|
+
v2 -= v0;
|
|
166
|
+
|
|
167
|
+
var det = 1 / (u1 * v2 - u2 * v1),
|
|
168
|
+
|
|
169
|
+
// linear transformation
|
|
170
|
+
a = (v2 * x1 - v1 * x2) * det,
|
|
171
|
+
b = (v2 * y1 - v1 * y2) * det,
|
|
172
|
+
c = (u1 * x2 - u2 * x1) * det,
|
|
173
|
+
d = (u1 * y2 - u2 * y1) * det,
|
|
174
|
+
|
|
175
|
+
// translation
|
|
176
|
+
e = x0 - a * u0 - c * v0,
|
|
177
|
+
f = y0 - b * u0 - d * v0;
|
|
178
|
+
|
|
179
|
+
renderer.transform(a, b, c, d, e, f);
|
|
180
|
+
renderer.drawImage(img, 0, 0);
|
|
181
|
+
renderer.clearMask();
|
|
182
|
+
renderer.restore();
|
|
183
|
+
|
|
184
|
+
if (this.debugRendering === true) {
|
|
185
|
+
renderer.setColor(meshDebugColor);
|
|
186
|
+
renderer.stroke();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
computeMeshVertices(slot, mesh, pma = false, vertexSize) {
|
|
192
|
+
let skeletonColor = slot.bone.skeleton.color;
|
|
193
|
+
let slotColor = slot.color;
|
|
194
|
+
let regionColor = mesh.color;
|
|
195
|
+
let alpha = skeletonColor.a * slotColor.a * regionColor.a;
|
|
196
|
+
let multiplier = pma ? alpha : 1;
|
|
197
|
+
|
|
198
|
+
this.tempColor.setFloat(skeletonColor.r * slotColor.r * regionColor.r * multiplier,
|
|
199
|
+
skeletonColor.g * slotColor.g * regionColor.g * multiplier,
|
|
200
|
+
skeletonColor.b * slotColor.b * regionColor.b * multiplier,
|
|
201
|
+
alpha);
|
|
202
|
+
|
|
203
|
+
if (worldVertices.length < mesh.worldVerticesLength) worldVertices = new Float32Array(mesh.worldVerticesLength);
|
|
204
|
+
mesh.computeWorldVertices(slot, 0, mesh.worldVerticesLength, worldVertices, 0, vertexSize);
|
|
205
|
+
|
|
206
|
+
let uvs = mesh.uvs;
|
|
207
|
+
let color = this.tempColor.toArray();
|
|
208
|
+
let vertices = worldVertices;
|
|
209
|
+
let vertexCount = mesh.worldVerticesLength / 2;
|
|
210
|
+
for (let i = 0, u = 0, v = 2; i < vertexCount; i++) {
|
|
211
|
+
vertices[v++] = color[0];
|
|
212
|
+
vertices[v++] = color[1];
|
|
213
|
+
vertices[v++] = color[2];
|
|
214
|
+
vertices[v++] = color[3];
|
|
215
|
+
vertices[v++] = uvs[u++];
|
|
216
|
+
vertices[v++] = uvs[u++];
|
|
217
|
+
v += 2;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
129
220
|
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Math, Renderable, Vector2d, video } from "melonjs";
|
|
2
|
-
|
|
1
|
+
import { Math, Renderable, Vector2d, video, loader, utils, event } from "melonjs";
|
|
3
2
|
import * as spineWebGL from "@esotericsoftware/spine-webgl";
|
|
4
3
|
import * as spineCanvas from "@esotericsoftware/spine-canvas";
|
|
5
4
|
import { Vector2 } from "@esotericsoftware/spine-core";
|
|
@@ -7,8 +6,53 @@ import { Vector2 } from "@esotericsoftware/spine-core";
|
|
|
7
6
|
import AssetManager from "./AssetManager.js";
|
|
8
7
|
import SkeletonRenderer from "./SkeletonRenderer.js";
|
|
9
8
|
|
|
9
|
+
import { name, version, dependencies, homepage } from "../package.json";
|
|
10
|
+
|
|
10
11
|
export let assetManager = new AssetManager();
|
|
11
12
|
|
|
13
|
+
// a custom Spine parser for melonJS preloader
|
|
14
|
+
function spineParser(data, onload, onerror) {
|
|
15
|
+
|
|
16
|
+
// decompose data.src for the spine loader
|
|
17
|
+
const ext = utils.file.getExtension(data.src);
|
|
18
|
+
const basename = utils.file.getBasename(data.src);
|
|
19
|
+
const path = utils.file.getPath(data.src);
|
|
20
|
+
const filename = basename + "." + ext;
|
|
21
|
+
|
|
22
|
+
// set url prefix
|
|
23
|
+
assetManager.setPrefix(path);
|
|
24
|
+
|
|
25
|
+
// load asset
|
|
26
|
+
switch (ext) {
|
|
27
|
+
case "atlas":
|
|
28
|
+
assetManager.asset_manager.loadTextureAtlas(filename, onload, onerror);
|
|
29
|
+
break;
|
|
30
|
+
case "json":
|
|
31
|
+
assetManager.asset_manager.loadText(filename, onload, onerror);
|
|
32
|
+
break;
|
|
33
|
+
case "skel":
|
|
34
|
+
assetManager.asset_manager.loadBinary(filename, onload, onerror);
|
|
35
|
+
break;
|
|
36
|
+
default:
|
|
37
|
+
throw "Spine plugin: unknown extension when preloading spine assets";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// set the spine custom parser
|
|
44
|
+
loader.setParser("spine", spineParser);
|
|
45
|
+
|
|
46
|
+
// hello world
|
|
47
|
+
event.once(event.VIDEO_INIT, () => {
|
|
48
|
+
console.log(`${name} ${version} - spine runtime ${dependencies["@esotericsoftware/spine-core"]} | ${homepage}`);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* @classdesc
|
|
53
|
+
* An object to display a Spine animated skeleton on screen.
|
|
54
|
+
* @augments Renderable
|
|
55
|
+
*/
|
|
12
56
|
export default class Spine extends Renderable {
|
|
13
57
|
runtime;
|
|
14
58
|
skeleton;
|
|
@@ -19,6 +63,40 @@ export default class Spine extends Renderable {
|
|
|
19
63
|
boneOffset;
|
|
20
64
|
boneSize;
|
|
21
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @param {number} x - the x coordinates of the Spine object
|
|
68
|
+
* @param {number} y - the y coordinates of the Spine object
|
|
69
|
+
* @param {object} settings - Configuration parameters for the Spine object
|
|
70
|
+
* @param {number} [settings.atlasFile] - the name of the atlasFile to be used to create this spine animation
|
|
71
|
+
* @param {number} [settings.jsonFile] - the name of the atlasFile to be used to create this spine animation
|
|
72
|
+
* @param {number} [settings.mixTime = 0.2] - the default mix duration to use when no mix duration has been defined between two animations.
|
|
73
|
+
* @example
|
|
74
|
+
* import * as Spine from '@melonjs/spine-plugin';
|
|
75
|
+
* import * as me from 'melonjs';
|
|
76
|
+
*
|
|
77
|
+
* // prepare/declare assets for the preloader
|
|
78
|
+
* const DataManifest = [
|
|
79
|
+
* {
|
|
80
|
+
* "name": "alien-ess.json",
|
|
81
|
+
* "type": "spine",
|
|
82
|
+
* "src": "data/spine/alien-ess.json"
|
|
83
|
+
* },
|
|
84
|
+
* {
|
|
85
|
+
* "name": "alien.atlas",
|
|
86
|
+
* "type": "spine",
|
|
87
|
+
* "src": "data/spine/alien.atlas"
|
|
88
|
+
* },
|
|
89
|
+
* ]
|
|
90
|
+
*
|
|
91
|
+
* // create a new Spine Renderable
|
|
92
|
+
* let spineAlien = new Spine(100, 100, {atlasFile: "alien.atlas", jsonFile: "alien-ess.json"});
|
|
93
|
+
*
|
|
94
|
+
* // set default animation
|
|
95
|
+
* spineAlien.setAnimation(0, "death", true);
|
|
96
|
+
*
|
|
97
|
+
* // add it to the game world
|
|
98
|
+
* me.game.world.addChild(spineAlien);
|
|
99
|
+
*/
|
|
22
100
|
constructor(x, y, settings) {
|
|
23
101
|
super(x, y, settings.width, settings.height);
|
|
24
102
|
|
|
@@ -39,20 +117,26 @@ export default class Spine extends Renderable {
|
|
|
39
117
|
this.pos.z = settings.z;
|
|
40
118
|
}
|
|
41
119
|
|
|
42
|
-
|
|
120
|
+
// use internally when calulcating bounds
|
|
43
121
|
this.boneOffset = new Vector2();
|
|
44
122
|
this.boneSize = new Vector2();
|
|
45
123
|
|
|
46
|
-
|
|
124
|
+
// default mixTime
|
|
125
|
+
this.mixTime = typeof settings.mixTime !== "undefined" ? settings.mixTime : 0.2;
|
|
126
|
+
|
|
47
127
|
|
|
48
128
|
if (settings.jsonFile) {
|
|
49
129
|
this.jsonFile = settings.jsonFile;
|
|
50
130
|
this.atlasFile = settings.atlasFile;
|
|
51
|
-
|
|
52
131
|
this.setSkeleton(this.atlasFile, this.jsonFile);
|
|
53
132
|
}
|
|
54
133
|
}
|
|
55
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Whether to enabler the debug mode when rendering the spine object
|
|
137
|
+
* @default false
|
|
138
|
+
* @type {boolean}
|
|
139
|
+
*/
|
|
56
140
|
get debugRendering() {
|
|
57
141
|
return this.skeletonRenderer.debugRendering;
|
|
58
142
|
}
|
|
@@ -61,14 +145,25 @@ export default class Spine extends Renderable {
|
|
|
61
145
|
this.skeletonRenderer.debugRendering = value;
|
|
62
146
|
}
|
|
63
147
|
|
|
148
|
+
/**
|
|
149
|
+
* set and load the given skeleton atlas and json definition files
|
|
150
|
+
* (use this if you did not specify any json or atlas through the constructor)
|
|
151
|
+
* @param {number} [atlasFile] - the name of the atlasFile to be used to create this spine animation
|
|
152
|
+
* @param {number} [jsonFile] - the name of the atlasFile to be used to create this spine animation
|
|
153
|
+
* @example
|
|
154
|
+
* // create a new Spine Renderable
|
|
155
|
+
* let spineAlien = new Spine(100, 100);
|
|
156
|
+
*
|
|
157
|
+
* // set the skeleton
|
|
158
|
+
* spineAlien.setSkeleton("alien.atlas", "alien-ess.json");
|
|
159
|
+
*
|
|
160
|
+
* // set default animation
|
|
161
|
+
* spineAlien.setAnimation(0, "death", true);
|
|
162
|
+
*
|
|
163
|
+
* // add it to the game world
|
|
164
|
+
* me.game.world.addChild(spineAlien);
|
|
165
|
+
*/
|
|
64
166
|
setSkeleton(atlasFile, jsonFile) {
|
|
65
|
-
this.loadSpineAssets(atlasFile, jsonFile);
|
|
66
|
-
this.root = this.skeleton.getRootBone();
|
|
67
|
-
// Spine uses Y-up, melonJS uses Y-down
|
|
68
|
-
this.root.scaleY *= -1;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
loadSpineAssets(atlasFile, jsonFile) {
|
|
72
167
|
// Create the texture atlas and skeleton data.
|
|
73
168
|
let atlas = this.assetManager.require(atlasFile);
|
|
74
169
|
let atlasLoader = new this.runtime.AtlasAttachmentLoader(atlas);
|
|
@@ -84,20 +179,50 @@ export default class Spine extends Renderable {
|
|
|
84
179
|
var animationStateData = new this.runtime.AnimationStateData(this.skeleton.data);
|
|
85
180
|
animationStateData.defaultMix = this.mixTime;
|
|
86
181
|
this.animationState = new this.runtime.AnimationState(animationStateData);
|
|
182
|
+
|
|
183
|
+
// get a reference to the root bone
|
|
184
|
+
this.root = this.skeleton.getRootBone();
|
|
185
|
+
// Spine uses Y-up, melonJS uses Y-down
|
|
186
|
+
this.root.scaleY *= -1;
|
|
187
|
+
|
|
188
|
+
// mark the object as dirty
|
|
189
|
+
this.isDirty = true;
|
|
87
190
|
}
|
|
88
191
|
|
|
192
|
+
/**
|
|
193
|
+
* Rotate this Spine object by the specified angle (in radians).
|
|
194
|
+
* @param {number} angle - The angle to rotate (in radians)
|
|
195
|
+
* @param {Vector2d|ObservableVector2d} [v] - an optional point to rotate around
|
|
196
|
+
* @returns {Spine} Reference to this object for method chaining
|
|
197
|
+
*/
|
|
89
198
|
rotate(angle, v) {
|
|
90
199
|
// rotation for rootBone is in degrees (anti-clockwise)
|
|
91
200
|
this.skeleton.getRootBone().rotation -= Math.radToDeg(angle) + 90;
|
|
92
201
|
// melonJS rotate method takes radians
|
|
93
|
-
super.rotate(angle, v);
|
|
202
|
+
return super.rotate(angle, v);
|
|
94
203
|
}
|
|
95
204
|
|
|
205
|
+
/**
|
|
206
|
+
* scale the Spine object around his anchor point. Scaling actually applies changes
|
|
207
|
+
* to the currentTransform member wich is used by the renderer to scale the object
|
|
208
|
+
* when rendering. It does not scale the object itself. For example if the renderable
|
|
209
|
+
* is an image, the image.width and image.height properties are unaltered but the currentTransform
|
|
210
|
+
* member will be changed.
|
|
211
|
+
* @param {number} x - a number representing the abscissa of the scaling vector.
|
|
212
|
+
* @param {number} [y=x] - a number representing the ordinate of the scaling vector.
|
|
213
|
+
* @returns {Spine} Reference to this object for method chaining
|
|
214
|
+
*/
|
|
96
215
|
scale(x, y = x) {
|
|
97
|
-
|
|
98
|
-
super.scale(x, y);
|
|
216
|
+
// untested
|
|
217
|
+
return super.scale(x, y);
|
|
99
218
|
}
|
|
100
219
|
|
|
220
|
+
/**
|
|
221
|
+
* update the bounding box for this spine object.
|
|
222
|
+
* (this will automatically update the bounds of the entire skeleton animation)
|
|
223
|
+
* @param {boolean} [absolute=true] - update the bounds size and position in (world) absolute coordinates
|
|
224
|
+
* @returns {Bounds} this shape bounding box Rectangle object
|
|
225
|
+
*/
|
|
101
226
|
updateBounds(absolute = true) {
|
|
102
227
|
if (this.isRenderable) {
|
|
103
228
|
let bounds = this.getBounds();
|
|
@@ -173,9 +298,6 @@ export default class Spine extends Renderable {
|
|
|
173
298
|
|
|
174
299
|
/**
|
|
175
300
|
* draw this spine object
|
|
176
|
-
* @name draw
|
|
177
|
-
* @memberof Spine
|
|
178
|
-
* @protected
|
|
179
301
|
* @param {CanvasRenderer|WebGLRenderer} renderer - a renderer instance
|
|
180
302
|
* @param {Camera2d} [viewport] - the viewport to (re)draw
|
|
181
303
|
*/
|
|
@@ -183,6 +305,13 @@ export default class Spine extends Renderable {
|
|
|
183
305
|
this.skeletonRenderer.draw(renderer, this.skeleton);
|
|
184
306
|
}
|
|
185
307
|
|
|
308
|
+
/**
|
|
309
|
+
* Sets the current animation for a track, discarding any queued animations.
|
|
310
|
+
* @param {number} [track_index] - If the formerly current track entry was never applied to a skeleton, it is replaced (not mixed from). In either case trackEnd determines when the track is cleared.
|
|
311
|
+
* @param {number} [index] - the animation index
|
|
312
|
+
* @param {boolean} [loop= false] - If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its duration.
|
|
313
|
+
* @returns A track entry to allow further customization of animation playback. References to the track entry must not be kept after the dispose event occurs.
|
|
314
|
+
*/
|
|
186
315
|
setAnimationByIndex(track_index, index, loop = false) {
|
|
187
316
|
if (index < 0 || index >= this.skeleton.data.animations.length) {
|
|
188
317
|
return (console.log("Animation Index not found"));
|
|
@@ -191,10 +320,25 @@ export default class Spine extends Renderable {
|
|
|
191
320
|
}
|
|
192
321
|
}
|
|
193
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Sets the current animation for a track, discarding any queued animations.
|
|
325
|
+
* @param {number} [track_index] - If the formerly current track entry was never applied to a skeleton, it is replaced (not mixed from). In either case trackEnd determines when the track is cleared.
|
|
326
|
+
* @param {string} [name] - the animation name
|
|
327
|
+
* @param {boolean} [loop= false] - If true, the animation will repeat. If false it will not, instead its last frame is applied if played beyond its duration.
|
|
328
|
+
* @returns A track entry to allow further customization of animation playback. References to the track entry must not be kept after the dispose event occurs.
|
|
329
|
+
* @example
|
|
330
|
+
* // set the current animation
|
|
331
|
+
* spineAlien.setAnimation(0, "death", true);
|
|
332
|
+
*/
|
|
194
333
|
setAnimation(track_index, name, loop = false) {
|
|
195
334
|
this.animationState.setAnimation(track_index, name, loop);
|
|
196
335
|
}
|
|
197
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Adds an animation to be played after the current or last queued animation for a track, and sets the track entry's mixDuration.
|
|
339
|
+
* @param {number} [delay=0] - If > 0, sets delay. If <= 0, the delay set is the duration of the previous track entry minus any mix duration plus the specified `delay` (ie the mix ends at (`delay` = 0) or before (`delay` < 0) the previous track entry duration). If the previous entry is looping, its next loop completion is used instead of its duration.
|
|
340
|
+
* @return A track entry to allow further customization of animation playback. References to the track entry must not be kept after the dispose} event occurs.
|
|
341
|
+
*/
|
|
198
342
|
addAnimationByIndex(track_index, index, loop = false, delay = 0) {
|
|
199
343
|
if (index < 0 || index >= this.skeleton.data.animations.length) {
|
|
200
344
|
return (console.log("Animation Index not found"));
|
|
@@ -223,18 +367,44 @@ export default class Spine extends Renderable {
|
|
|
223
367
|
};
|
|
224
368
|
}
|
|
225
369
|
|
|
370
|
+
/**
|
|
371
|
+
* Set the default mix duration to use when no mix duration has been defined between two animations.
|
|
372
|
+
* @param {number} mixTime
|
|
373
|
+
*/
|
|
226
374
|
setDefaultMixTime(mixTime) {
|
|
227
|
-
this.animationState.data.defaultMix = mixTime;
|
|
375
|
+
this.animationState.data.defaultMix = this.mixTime = mixTime;
|
|
228
376
|
}
|
|
229
377
|
|
|
378
|
+
/**
|
|
379
|
+
* Sets a mix duration by animation name.
|
|
380
|
+
*/
|
|
230
381
|
setTransitionMixTime(firstAnimation, secondAnimation, mixTime) {
|
|
231
382
|
this.animationState.setMix(firstAnimation, secondAnimation, mixTime);
|
|
232
383
|
}
|
|
233
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Sets a skin by name.
|
|
387
|
+
* @param {string} skinName
|
|
388
|
+
* @example
|
|
389
|
+
* // create a new Spine Renderable
|
|
390
|
+
* let spineAlien = new Spine(100, 100, {atlasFile: "mix-and-match-pma.atlas", jsonFile: "mix-and-match-pro.json"});
|
|
391
|
+
*
|
|
392
|
+
* // set default animation
|
|
393
|
+
* spineAlien.setAnimation(0, "dance", true);
|
|
394
|
+
*
|
|
395
|
+
* // set default skin
|
|
396
|
+
* spineAlien.setSkinByName("full-skins/girl");
|
|
397
|
+
*
|
|
398
|
+
* // add it to the game world
|
|
399
|
+
* me.game.world.addChild(spineAlien);
|
|
400
|
+
*/
|
|
234
401
|
setSkinByName(skinName) {
|
|
235
402
|
this.skeleton.setSkinByName(skinName);
|
|
236
403
|
}
|
|
237
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Sets this slot to the setup pose.
|
|
407
|
+
*/
|
|
238
408
|
setToSetupPose() {
|
|
239
409
|
this.skeleton.setToSetupPose();
|
|
240
410
|
}
|