@melonjs/spine-plugin 1.0.0 → 1.2.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  a [Spine](http://en.esotericsoftware.com/spine-in-depth) 4.1 plugin implementation for [melonJS 2](www.melonjs.org)
4
4
 
5
- ![melonjs-spine-gif](https://github.com/melonjs/spine-plugin/assets/4033090/e4f1db3e-e2c7-4d18-9d80-b42fc9897c59)
5
+ ![melonjs-spine-gif](https://github.com/melonjs/spine-plugin/assets/4033090/dc259c8e-def6-419e-83a9-cda374715686)
6
6
 
7
7
  >Note: although functional, this plugin is still a work in progress. Feedback and especially contributions are welcome!
8
8
 
@@ -13,10 +13,11 @@ a [Spine](http://en.esotericsoftware.com/spine-in-depth) 4.1 plugin implementati
13
13
 
14
14
  Installation
15
15
  -------------------------------------------------------------------------------
16
- this plugin is already bundled with the require Spine runtime, so there is no need to install it separately.
16
+ this plugin is already bundled with the required Spine runtime, so there is no need to install it separately.
17
17
  >Note: this plugin requires melonJS version 15.9 or higher.
18
18
 
19
19
  To install the plugin using npm :
20
+
20
21
  `$ [sudo] npm install @melonjs/spine-plugin`
21
22
 
22
23
  Then import and use the plugin in your project. For example:
@@ -29,8 +30,8 @@ Spine.assetManager.loadAsset("alien.atlas", "alien-ess.json");
29
30
 
30
31
  // create new Spine Renderable
31
32
  export default class AlienSpine extends Spine {
32
- constructor(x, y, settings ){
33
- super(x, y, settings);
33
+ constructor(x, y, settings ){
34
+ super(x, y, settings);
34
35
  ...
35
36
  }
36
37
  }
@@ -370,6 +370,8 @@ declare class Spine extends Renderable$1 {
370
370
  mixTime: any;
371
371
  jsonFile: any;
372
372
  atlasFile: any;
373
+ set debugRendering(arg: boolean);
374
+ get debugRendering(): boolean;
373
375
  setSkeleton(atlasFile: any, jsonFile: any): void;
374
376
  loadSpineAssets(atlasFile: any, jsonFile: any): void;
375
377
  rotate(angle: any, v: any): void;
@@ -4073,8 +4075,12 @@ declare class SkeletonRenderer {
4073
4075
  runtime: any;
4074
4076
  tempColor: Color;
4075
4077
  tintColor: Color$1;
4078
+ vertexSize: number;
4079
+ debugRendering: boolean;
4080
+ clipper: SkeletonClipping;
4081
+ clippingVertices: any[];
4082
+ clippingMask: Polygon;
4076
4083
  draw(renderer: any, skeleton: any): void;
4077
- updateSkeleton(skeleton: any): void;
4078
4084
  }
4079
4085
  import { Vector2d } from 'melonjs';
4080
4086
  /******************************************************************************
@@ -4127,4 +4133,5 @@ declare class Vertices {
4127
4133
  vertices: any;
4128
4134
  }
4129
4135
  import { Color as Color$1 } from 'melonjs';
4136
+ import { Polygon } from 'melonjs';
4130
4137
  export { Spine as default };
@@ -1,11 +1,11 @@
1
1
  /*!
2
- * melonJS Spine plugin - v1.0.0
2
+ * melonJS Spine plugin - v1.2.0
3
3
  * http://www.melonjs.org
4
4
  * @melonjs/spine-plugin is licensed under the MIT License.
5
5
  * http://www.opensource.org/licenses/mit-license
6
6
  * @copyright (C) 2011 - 2023 AltByte Pte Ltd
7
7
  */
8
- import { event, video, Color as Color$1, Math as Math$1, Renderable as Renderable$1, Vector2d } from 'melonjs';
8
+ import { event, video, Color as Color$1, Polygon, Math as Math$1, Renderable as Renderable$1, Vector2d } from 'melonjs';
9
9
 
10
10
  /******************************************************************************
11
11
  * Spine Runtimes License Agreement
@@ -14938,6 +14938,11 @@ class SkeletonRenderer {
14938
14938
  runtime;
14939
14939
  tempColor = new Color();
14940
14940
  tintColor = new Color$1();
14941
+ vertexSize = 2 + 2 + 4;
14942
+ debugRendering = false;
14943
+ clipper = new SkeletonClipping();
14944
+ clippingVertices = [];
14945
+ clippingMask = new Polygon(0, 0);
14941
14946
 
14942
14947
  constructor(runtime) {
14943
14948
  this.runtime = runtime;
@@ -14946,15 +14951,20 @@ class SkeletonRenderer {
14946
14951
  }
14947
14952
 
14948
14953
  draw(renderer, skeleton) {
14949
- // based on https://github.com/EsotericSoftware/spine-runtimes/blob/4.1/spine-ts/spine-canvas/src/SkeletonRenderer.ts
14954
+ let clipper = this.clipper;
14950
14955
  let drawOrder = skeleton.drawOrder;
14951
14956
  let skeletonColor = skeleton.color;
14952
14957
 
14953
14958
  for (var i = 0, n = drawOrder.length; i < n; i++) {
14959
+ let clippedVertexSize = clipper.isClipping() ? 2 : this.vertexSize;
14954
14960
  let slot = drawOrder[i];
14955
14961
  let bone = slot.bone;
14956
14962
 
14957
- if (!bone.active) continue;
14963
+ if (!bone.active) {
14964
+ clipper.clipEndWithSlot(slot);
14965
+ renderer.clearMask();
14966
+ continue;
14967
+ }
14958
14968
 
14959
14969
  let attachment = slot.getAttachment();
14960
14970
 
@@ -14971,7 +14981,7 @@ class SkeletonRenderer {
14971
14981
  skeletonColor.b * slotColor.b * regionColor.b,
14972
14982
  skeletonColor.a * slotColor.a * regionColor.a);
14973
14983
 
14974
- attachment.computeWorldVertices(slot, worldVertices, 0, 2);
14984
+ attachment.computeWorldVertices(slot, worldVertices, 0, clippedVertexSize);
14975
14985
 
14976
14986
  renderer.save();
14977
14987
  renderer.transform(bone.a, bone.c, bone.b, bone.d, bone.worldX, bone.worldY);
@@ -14995,17 +15005,41 @@ class SkeletonRenderer {
14995
15005
  renderer.setTint(color);
14996
15006
  renderer.setBlendMode(blendModeLUT[blendMode]);
14997
15007
  renderer.setGlobalAlpha(color.a);
15008
+
15009
+ if (clipper.isClipping()) {
15010
+ renderer.setMask(this.clippingMask);
15011
+ }
15012
+
14998
15013
  renderer.drawImage(image, image.width * region.u, image.height * region.v, w, h, 0, 0, w, h);
15014
+
15015
+ if (this.debugRendering === true) {
15016
+ renderer.setColor("green");
15017
+ renderer.strokeRect(0, 0, w, h);
15018
+ }
15019
+
14999
15020
  renderer.restore();
15021
+ } else if (attachment instanceof MeshAttachment) {
15022
+ // do nothing for now;
15023
+ } else if (attachment instanceof ClippingAttachment) {
15024
+ let clip = attachment;
15025
+ let vertices = this.clippingVertices;
15026
+ clipper.clipStart(slot, clip);
15027
+ clip.computeWorldVertices(slot, 0, clip.worldVerticesLength, vertices, 0, 2);
15028
+ this.clippingMask.setVertices(vertices, clip.worldVerticesLength);
15029
+ if (this.debugRendering === true) {
15030
+ renderer.setColor("blue");
15031
+ renderer.stroke(this.clippingMask);
15032
+ }
15033
+ continue;
15034
+ } else {
15035
+ clipper.clipEndWithSlot(slot);
15036
+ renderer.clearMask();
15037
+ continue;
15000
15038
  }
15039
+ clipper.clipEndWithSlot(slot);
15040
+ renderer.clearMask();
15001
15041
  }
15002
- }
15003
-
15004
- updateSkeleton(skeleton) {
15005
- // for update skeleton
15006
- //if (this.isWebGLRenderer === false) {
15007
- skeleton.scaleY = -1;
15008
- //}
15042
+ clipper.clipEnd();
15009
15043
  }
15010
15044
  }
15011
15045
 
@@ -15055,9 +15089,19 @@ class Spine extends Renderable$1 {
15055
15089
  }
15056
15090
  }
15057
15091
 
15092
+ get debugRendering() {
15093
+ return this.skeletonRenderer.debugRendering;
15094
+ }
15095
+
15096
+ set debugRendering(value) {
15097
+ this.skeletonRenderer.debugRendering = value;
15098
+ }
15099
+
15058
15100
  setSkeleton(atlasFile, jsonFile) {
15059
15101
  this.loadSpineAssets(atlasFile, jsonFile);
15060
15102
  this.root = this.skeleton.getRootBone();
15103
+ // Spine uses Y-up, melonJS uses Y-down
15104
+ this.root.scaleY *= -1;
15061
15105
  }
15062
15106
 
15063
15107
  loadSpineAssets(atlasFile, jsonFile) {
@@ -15139,7 +15183,7 @@ class Spine extends Renderable$1 {
15139
15183
  * @param {number} dt - time since the last update in milliseconds.
15140
15184
  * @returns {boolean} true if the renderable is dirty
15141
15185
  */
15142
- update(dt) { // eslint-disable-line no-unused-vars
15186
+ update(dt) {
15143
15187
  if (typeof this.skeleton !== "undefined") {
15144
15188
  let rootBone = this.skeleton.getRootBone();
15145
15189
 
@@ -15151,7 +15195,6 @@ class Spine extends Renderable$1 {
15151
15195
  rootBone.y = this.pos.y;
15152
15196
  }
15153
15197
 
15154
- this.skeletonRenderer.updateSkeleton(rootBone);
15155
15198
  // Update and apply the animation state, update the skeleton's
15156
15199
  // world transforms and render the skeleton.
15157
15200
  this.animationState.update(dt / 1000);
@@ -15176,12 +15219,12 @@ class Spine extends Renderable$1 {
15176
15219
  this.skeletonRenderer.draw(renderer, this.skeleton);
15177
15220
  }
15178
15221
 
15179
-
15180
15222
  setAnimationByIndex(track_index, index, loop = false) {
15181
- if (index < 0 || index >= this.skeleton.data.animations.length)
15182
- { return (console.log("Animation Index not found")); }
15183
- else
15184
- { this.animationState.setAnimation(track_index, this.skeleton.data.animations[index].name, loop); }
15223
+ if (index < 0 || index >= this.skeleton.data.animations.length) {
15224
+ return (console.log("Animation Index not found"));
15225
+ } else {
15226
+ this.animationState.setAnimation(track_index, this.skeleton.data.animations[index].name, loop);
15227
+ }
15185
15228
  }
15186
15229
 
15187
15230
  setAnimation(track_index, name, loop = false) {
@@ -15189,10 +15232,11 @@ class Spine extends Renderable$1 {
15189
15232
  }
15190
15233
 
15191
15234
  addAnimationByIndex(track_index, index, loop = false, delay = 0) {
15192
- if (index < 0 || index >= this.skeleton.data.animations.length)
15193
- { return (console.log("Animation Index not found")); }
15194
- else
15195
- { this.animationState.addAnimation(track_index, this.skeleton.data.animations[index].name, loop, delay); }
15235
+ if (index < 0 || index >= this.skeleton.data.animations.length) {
15236
+ return (console.log("Animation Index not found"));
15237
+ } else {
15238
+ this.animationState.addAnimation(track_index, this.skeleton.data.animations[index].name, loop, delay);
15239
+ }
15196
15240
  }
15197
15241
 
15198
15242
  addAnimationByName(track_index, animationName, loop = false, delay = 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@melonjs/spine-plugin",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "melonJS Spine plugin",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -52,11 +52,11 @@
52
52
  "@babel/eslint-parser": "^7.22.0",
53
53
  "@babel/plugin-syntax-import-assertions": "^7.22.5",
54
54
  "@rollup/plugin-commonjs": "^25.0.4",
55
- "@rollup/plugin-node-resolve": "^15.1.0",
55
+ "@rollup/plugin-node-resolve": "^15.2.0",
56
56
  "@rollup/plugin-replace": "^5.0.2",
57
57
  "del-cli": "^5.0.0",
58
58
  "eslint": "^8.47.0",
59
- "eslint-plugin-jsdoc": "^46.4.6",
59
+ "eslint-plugin-jsdoc": "^46.5.0",
60
60
  "rollup": "^3.28.0",
61
61
  "rollup-plugin-bundle-size": "^1.0.3",
62
62
  "typescript": "^5.1.6"
@@ -70,7 +70,7 @@
70
70
  "clean": "del-cli --force dist/*",
71
71
  "types": "tsc"
72
72
  },
73
- "homepage": "https://github.com/melonjs/debug-spine#readme",
73
+ "homepage": "https://github.com/melonjs/spine-plugin#readme",
74
74
  "directories": {
75
75
  "test": "test"
76
76
  }
@@ -1,5 +1,5 @@
1
- import { Color as MColor, Math as MMath } from "melonjs";
2
- import { RegionAttachment, Color } from "@esotericsoftware/spine-core";
1
+ import { Color as MColor, Math as MMath, Polygon } from "melonjs";
2
+ import { SkeletonClipping, ClippingAttachment, MeshAttachment, RegionAttachment, Color } from "@esotericsoftware/spine-core";
3
3
 
4
4
  const worldVertices = new Float32Array(8);
5
5
  const blendModeLUT = ["normal", "additive", "multiply", "screen"];
@@ -10,6 +10,11 @@ export default class SkeletonRenderer {
10
10
  runtime;
11
11
  tempColor = new Color();
12
12
  tintColor = new MColor();
13
+ vertexSize = 2 + 2 + 4;
14
+ debugRendering = false;
15
+ clipper = new SkeletonClipping();
16
+ clippingVertices = [];
17
+ clippingMask = new Polygon(0, 0);
13
18
 
14
19
  constructor(runtime) {
15
20
  this.runtime = runtime;
@@ -18,15 +23,20 @@ export default class SkeletonRenderer {
18
23
  }
19
24
 
20
25
  draw(renderer, skeleton) {
21
- // based on https://github.com/EsotericSoftware/spine-runtimes/blob/4.1/spine-ts/spine-canvas/src/SkeletonRenderer.ts
26
+ let clipper = this.clipper;
22
27
  let drawOrder = skeleton.drawOrder;
23
28
  let skeletonColor = skeleton.color;
24
29
 
25
30
  for (var i = 0, n = drawOrder.length; i < n; i++) {
31
+ let clippedVertexSize = clipper.isClipping() ? 2 : this.vertexSize;
26
32
  let slot = drawOrder[i];
27
33
  let bone = slot.bone;
28
34
 
29
- if (!bone.active) continue;
35
+ if (!bone.active) {
36
+ clipper.clipEndWithSlot(slot);
37
+ renderer.clearMask();
38
+ continue;
39
+ }
30
40
 
31
41
  let attachment = slot.getAttachment();
32
42
 
@@ -43,7 +53,7 @@ export default class SkeletonRenderer {
43
53
  skeletonColor.b * slotColor.b * regionColor.b,
44
54
  skeletonColor.a * slotColor.a * regionColor.a);
45
55
 
46
- attachment.computeWorldVertices(slot, worldVertices, 0, 2);
56
+ attachment.computeWorldVertices(slot, worldVertices, 0, clippedVertexSize);
47
57
 
48
58
  renderer.save();
49
59
  renderer.transform(bone.a, bone.c, bone.b, bone.d, bone.worldX, bone.worldY);
@@ -67,16 +77,40 @@ export default class SkeletonRenderer {
67
77
  renderer.setTint(color);
68
78
  renderer.setBlendMode(blendModeLUT[blendMode]);
69
79
  renderer.setGlobalAlpha(color.a);
80
+
81
+ if (clipper.isClipping()) {
82
+ renderer.setMask(this.clippingMask);
83
+ }
84
+
70
85
  renderer.drawImage(image, image.width * region.u, image.height * region.v, w, h, 0, 0, w, h);
86
+
87
+ if (this.debugRendering === true) {
88
+ renderer.setColor("green");
89
+ renderer.strokeRect(0, 0, w, h);
90
+ }
91
+
71
92
  renderer.restore();
93
+ } else if (attachment instanceof MeshAttachment) {
94
+ // do nothing for now;
95
+ } else if (attachment instanceof ClippingAttachment) {
96
+ let clip = attachment;
97
+ let vertices = this.clippingVertices;
98
+ clipper.clipStart(slot, clip);
99
+ clip.computeWorldVertices(slot, 0, clip.worldVerticesLength, vertices, 0, 2);
100
+ this.clippingMask.setVertices(vertices, clip.worldVerticesLength);
101
+ if (this.debugRendering === true) {
102
+ renderer.setColor("blue");
103
+ renderer.stroke(this.clippingMask);
104
+ }
105
+ continue;
106
+ } else {
107
+ clipper.clipEndWithSlot(slot);
108
+ renderer.clearMask();
109
+ continue;
72
110
  }
111
+ clipper.clipEndWithSlot(slot);
112
+ renderer.clearMask();
73
113
  }
74
- }
75
-
76
- updateSkeleton(skeleton) {
77
- // for update skeleton
78
- //if (this.isWebGLRenderer === false) {
79
- skeleton.scaleY = -1;
80
- //}
114
+ clipper.clipEnd();
81
115
  }
82
116
  }
package/src/index.js CHANGED
@@ -53,9 +53,19 @@ export default class Spine extends Renderable {
53
53
  }
54
54
  }
55
55
 
56
+ get debugRendering() {
57
+ return this.skeletonRenderer.debugRendering;
58
+ }
59
+
60
+ set debugRendering(value) {
61
+ this.skeletonRenderer.debugRendering = value;
62
+ }
63
+
56
64
  setSkeleton(atlasFile, jsonFile) {
57
65
  this.loadSpineAssets(atlasFile, jsonFile);
58
66
  this.root = this.skeleton.getRootBone();
67
+ // Spine uses Y-up, melonJS uses Y-down
68
+ this.root.scaleY *= -1;
59
69
  }
60
70
 
61
71
  loadSpineAssets(atlasFile, jsonFile) {
@@ -137,7 +147,7 @@ export default class Spine extends Renderable {
137
147
  * @param {number} dt - time since the last update in milliseconds.
138
148
  * @returns {boolean} true if the renderable is dirty
139
149
  */
140
- update(dt) { // eslint-disable-line no-unused-vars
150
+ update(dt) {
141
151
  if (typeof this.skeleton !== "undefined") {
142
152
  let rootBone = this.skeleton.getRootBone();
143
153
 
@@ -149,7 +159,6 @@ export default class Spine extends Renderable {
149
159
  rootBone.y = this.pos.y;
150
160
  }
151
161
 
152
- this.skeletonRenderer.updateSkeleton(rootBone);
153
162
  // Update and apply the animation state, update the skeleton's
154
163
  // world transforms and render the skeleton.
155
164
  this.animationState.update(dt / 1000);
@@ -174,12 +183,12 @@ export default class Spine extends Renderable {
174
183
  this.skeletonRenderer.draw(renderer, this.skeleton);
175
184
  }
176
185
 
177
-
178
186
  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); }
187
+ if (index < 0 || index >= this.skeleton.data.animations.length) {
188
+ return (console.log("Animation Index not found"));
189
+ } else {
190
+ this.animationState.setAnimation(track_index, this.skeleton.data.animations[index].name, loop);
191
+ }
183
192
  }
184
193
 
185
194
  setAnimation(track_index, name, loop = false) {
@@ -187,10 +196,11 @@ export default class Spine extends Renderable {
187
196
  }
188
197
 
189
198
  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); }
199
+ if (index < 0 || index >= this.skeleton.data.animations.length) {
200
+ return (console.log("Animation Index not found"));
201
+ } else {
202
+ this.animationState.addAnimation(track_index, this.skeleton.data.animations[index].name, loop, delay);
203
+ }
194
204
  }
195
205
 
196
206
  addAnimationByName(track_index, animationName, loop = false, delay = 0) {