minimojs 1.0.0-alpha.12 → 1.0.0-alpha.13
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/dist/internal/ExplosionSystem.js +421 -116
- package/dist/internal/RenderSystem.js +63 -0
- package/dist/minimo.d.ts +160 -1
- package/dist/minimo.js +83 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const DEFAULT_EXPLODE_OPTIONS = {
|
|
2
2
|
rows: 3,
|
|
3
3
|
cols: 3,
|
|
4
4
|
durationMs: 500,
|
|
@@ -8,43 +8,299 @@ const DEFAULT_OPTIONS = {
|
|
|
8
8
|
fade: true,
|
|
9
9
|
destroySprite: true,
|
|
10
10
|
};
|
|
11
|
+
const DEFAULT_ASSEMBLE_OPTIONS = {
|
|
12
|
+
rows: 3,
|
|
13
|
+
cols: 3,
|
|
14
|
+
durationMs: 500,
|
|
15
|
+
speed: 260,
|
|
16
|
+
gravityY: 900,
|
|
17
|
+
spin: 220,
|
|
18
|
+
fade: true,
|
|
19
|
+
};
|
|
20
|
+
const DEFAULT_DISINTEGRATE_OPTIONS = {
|
|
21
|
+
rows: 4,
|
|
22
|
+
cols: 4,
|
|
23
|
+
durationMs: 420,
|
|
24
|
+
direction: "left-to-right",
|
|
25
|
+
distance: 42,
|
|
26
|
+
spin: 120,
|
|
27
|
+
fade: true,
|
|
28
|
+
destroySprite: true,
|
|
29
|
+
};
|
|
30
|
+
const DEFAULT_INTEGRATE_OPTIONS = {
|
|
31
|
+
rows: 4,
|
|
32
|
+
cols: 4,
|
|
33
|
+
durationMs: 420,
|
|
34
|
+
direction: "left-to-right",
|
|
35
|
+
distance: 42,
|
|
36
|
+
spin: 120,
|
|
37
|
+
fade: true,
|
|
38
|
+
};
|
|
39
|
+
const DEFAULT_WIPE_OPTIONS = {
|
|
40
|
+
mode: "reveal",
|
|
41
|
+
direction: "left-to-right",
|
|
42
|
+
durationMs: 360,
|
|
43
|
+
destroySprite: true,
|
|
44
|
+
};
|
|
11
45
|
/** @internal */
|
|
12
46
|
export class ExplosionSystem {
|
|
13
47
|
constructor() {
|
|
14
|
-
this.
|
|
48
|
+
this._pieceEffects = [];
|
|
49
|
+
this._wipes = [];
|
|
15
50
|
}
|
|
16
51
|
animateExplode(sprite, glyphCanvas, options, onComplete) {
|
|
17
|
-
this.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
const context = this.buildPieceSpriteContext(sprite, glyphCanvas);
|
|
53
|
+
this.spawnRadialEffect("explode", sprite, glyphCanvas, context, {
|
|
54
|
+
rows: this.sanitizeCount(options?.rows, DEFAULT_EXPLODE_OPTIONS.rows),
|
|
55
|
+
cols: this.sanitizeCount(options?.cols, DEFAULT_EXPLODE_OPTIONS.cols),
|
|
56
|
+
durationMs: this.sanitizeDuration(options?.durationMs, DEFAULT_EXPLODE_OPTIONS.durationMs),
|
|
57
|
+
speed: this.sanitizeNonNegative(options?.speed, DEFAULT_EXPLODE_OPTIONS.speed),
|
|
58
|
+
gravityY: this.sanitizeNumber(options?.gravityY, DEFAULT_EXPLODE_OPTIONS.gravityY),
|
|
59
|
+
spin: this.sanitizeNonNegative(options?.spin, DEFAULT_EXPLODE_OPTIONS.spin),
|
|
60
|
+
fade: options?.fade ?? DEFAULT_EXPLODE_OPTIONS.fade,
|
|
61
|
+
}, false, options?.destroySprite === false, options?.destroySprite === false ? sprite.visible : false, onComplete);
|
|
62
|
+
}
|
|
63
|
+
animateAssemble(sprite, glyphCanvas, options, onComplete) {
|
|
64
|
+
const context = this.buildPieceSpriteContext(sprite, glyphCanvas);
|
|
65
|
+
this.spawnRadialEffect("assemble", sprite, glyphCanvas, context, {
|
|
66
|
+
rows: this.sanitizeCount(options?.rows, DEFAULT_ASSEMBLE_OPTIONS.rows),
|
|
67
|
+
cols: this.sanitizeCount(options?.cols, DEFAULT_ASSEMBLE_OPTIONS.cols),
|
|
68
|
+
durationMs: this.sanitizeDuration(options?.durationMs, DEFAULT_ASSEMBLE_OPTIONS.durationMs),
|
|
69
|
+
speed: this.sanitizeNonNegative(options?.speed, DEFAULT_ASSEMBLE_OPTIONS.speed),
|
|
70
|
+
gravityY: this.sanitizeNumber(options?.gravityY, DEFAULT_ASSEMBLE_OPTIONS.gravityY),
|
|
71
|
+
spin: this.sanitizeNonNegative(options?.spin, DEFAULT_ASSEMBLE_OPTIONS.spin),
|
|
72
|
+
fade: options?.fade ?? DEFAULT_ASSEMBLE_OPTIONS.fade,
|
|
73
|
+
}, true, true, true, onComplete);
|
|
74
|
+
}
|
|
75
|
+
animateDisintegrate(sprite, glyphCanvas, options, onComplete) {
|
|
76
|
+
const context = this.buildPieceSpriteContext(sprite, glyphCanvas);
|
|
77
|
+
this.spawnDirectionalEffect("disintegrate", sprite, glyphCanvas, context, {
|
|
78
|
+
rows: this.sanitizeCount(options?.rows, DEFAULT_DISINTEGRATE_OPTIONS.rows),
|
|
79
|
+
cols: this.sanitizeCount(options?.cols, DEFAULT_DISINTEGRATE_OPTIONS.cols),
|
|
80
|
+
durationMs: this.sanitizeDuration(options?.durationMs, DEFAULT_DISINTEGRATE_OPTIONS.durationMs),
|
|
81
|
+
direction: options?.direction ?? DEFAULT_DISINTEGRATE_OPTIONS.direction,
|
|
82
|
+
distance: this.sanitizeNonNegative(options?.distance, DEFAULT_DISINTEGRATE_OPTIONS.distance),
|
|
83
|
+
spin: this.sanitizeNonNegative(options?.spin, DEFAULT_DISINTEGRATE_OPTIONS.spin),
|
|
84
|
+
fade: options?.fade ?? DEFAULT_DISINTEGRATE_OPTIONS.fade,
|
|
85
|
+
}, false, options?.destroySprite === false, false, onComplete);
|
|
86
|
+
}
|
|
87
|
+
animateIntegrate(sprite, glyphCanvas, options, onComplete) {
|
|
88
|
+
const context = this.buildPieceSpriteContext(sprite, glyphCanvas);
|
|
89
|
+
this.spawnDirectionalEffect("integrate", sprite, glyphCanvas, context, {
|
|
90
|
+
rows: this.sanitizeCount(options?.rows, DEFAULT_INTEGRATE_OPTIONS.rows),
|
|
91
|
+
cols: this.sanitizeCount(options?.cols, DEFAULT_INTEGRATE_OPTIONS.cols),
|
|
92
|
+
durationMs: this.sanitizeDuration(options?.durationMs, DEFAULT_INTEGRATE_OPTIONS.durationMs),
|
|
93
|
+
direction: options?.direction ?? DEFAULT_INTEGRATE_OPTIONS.direction,
|
|
94
|
+
distance: this.sanitizeNonNegative(options?.distance, DEFAULT_INTEGRATE_OPTIONS.distance),
|
|
95
|
+
spin: this.sanitizeNonNegative(options?.spin, DEFAULT_INTEGRATE_OPTIONS.spin),
|
|
96
|
+
fade: options?.fade ?? DEFAULT_INTEGRATE_OPTIONS.fade,
|
|
97
|
+
}, true, true, true, onComplete);
|
|
98
|
+
}
|
|
99
|
+
animateWipe(sprite, snapshot, options, onComplete) {
|
|
100
|
+
this.clearSpriteEffects(sprite);
|
|
101
|
+
const mode = options?.mode ?? DEFAULT_WIPE_OPTIONS.mode;
|
|
102
|
+
const durationMs = this.sanitizeDuration(options?.durationMs, DEFAULT_WIPE_OPTIONS.durationMs);
|
|
103
|
+
const direction = options?.direction ?? DEFAULT_WIPE_OPTIONS.direction;
|
|
104
|
+
const destroySprite = options?.destroySprite ?? DEFAULT_WIPE_OPTIONS.destroySprite;
|
|
105
|
+
const restoreVisibility = mode === "reveal";
|
|
106
|
+
sprite.visible = false;
|
|
107
|
+
this._wipes.push({
|
|
108
|
+
sprite,
|
|
109
|
+
canvas: snapshot.canvas,
|
|
110
|
+
x: snapshot.x,
|
|
111
|
+
y: snapshot.y,
|
|
112
|
+
rotation: snapshot.rotation,
|
|
113
|
+
alpha: snapshot.alpha,
|
|
114
|
+
flipX: snapshot.flipX,
|
|
115
|
+
flipY: snapshot.flipY,
|
|
116
|
+
drawWidth: snapshot.drawWidth,
|
|
117
|
+
drawHeight: snapshot.drawHeight,
|
|
118
|
+
layer: snapshot.layer,
|
|
119
|
+
ignoreScroll: snapshot.ignoreScroll,
|
|
120
|
+
mode,
|
|
121
|
+
direction,
|
|
122
|
+
progress: mode === "reveal" ? 0 : 1,
|
|
123
|
+
elapsedMs: 0,
|
|
124
|
+
durationMs,
|
|
125
|
+
restoreVisibility,
|
|
126
|
+
finalVisible: mode === "reveal",
|
|
127
|
+
onComplete,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
update(_dt, dtMs) {
|
|
131
|
+
const pieceToRemove = [];
|
|
132
|
+
for (let i = 0; i < this._pieceEffects.length; i++) {
|
|
133
|
+
const effect = this._pieceEffects[i];
|
|
134
|
+
effect.elapsedMs += dtMs;
|
|
135
|
+
const t = Math.min(effect.elapsedMs / effect.durationMs, 1);
|
|
136
|
+
for (const piece of effect.pieces) {
|
|
137
|
+
const localT = t <= piece.delayT ? 0 : Math.min(1, (t - piece.delayT) / (1 - piece.delayT));
|
|
138
|
+
const eased = this.easePieceProgress(effect.mode, localT);
|
|
139
|
+
piece.x = this.lerp(piece.startX, piece.endX, eased);
|
|
140
|
+
piece.y = this.lerp(piece.startY, piece.endY, eased);
|
|
141
|
+
piece.rotation = this.lerp(piece.startRotation, piece.endRotation, eased);
|
|
142
|
+
piece.alpha = this.lerp(piece.startAlpha, piece.endAlpha, eased);
|
|
143
|
+
}
|
|
144
|
+
if (t >= 1) {
|
|
145
|
+
pieceToRemove.push(i);
|
|
146
|
+
if (effect.restoreVisibility) {
|
|
147
|
+
effect.sprite.visible = effect.finalVisible;
|
|
148
|
+
}
|
|
149
|
+
effect.onComplete?.();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (let i = pieceToRemove.length - 1; i >= 0; i--) {
|
|
153
|
+
this._pieceEffects.splice(pieceToRemove[i], 1);
|
|
154
|
+
}
|
|
155
|
+
const wipeToRemove = [];
|
|
156
|
+
for (let i = 0; i < this._wipes.length; i++) {
|
|
157
|
+
const wipe = this._wipes[i];
|
|
158
|
+
wipe.elapsedMs += dtMs;
|
|
159
|
+
const t = Math.min(wipe.elapsedMs / wipe.durationMs, 1);
|
|
160
|
+
wipe.progress = wipe.mode === "reveal" ? t : 1 - t;
|
|
161
|
+
if (t >= 1) {
|
|
162
|
+
wipeToRemove.push(i);
|
|
163
|
+
if (wipe.restoreVisibility) {
|
|
164
|
+
wipe.sprite.visible = wipe.finalVisible;
|
|
165
|
+
}
|
|
166
|
+
wipe.onComplete?.();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (let i = wipeToRemove.length - 1; i >= 0; i--) {
|
|
170
|
+
this._wipes.splice(wipeToRemove[i], 1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
getRenderEntries() {
|
|
174
|
+
return this._pieceEffects;
|
|
175
|
+
}
|
|
176
|
+
getWipeEntries() {
|
|
177
|
+
return this._wipes;
|
|
178
|
+
}
|
|
179
|
+
clearAll() {
|
|
180
|
+
for (const effect of this._pieceEffects) {
|
|
181
|
+
if (effect.restoreVisibility) {
|
|
182
|
+
effect.sprite.visible = effect.finalVisible;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
for (const wipe of this._wipes) {
|
|
186
|
+
if (wipe.restoreVisibility) {
|
|
187
|
+
wipe.sprite.visible = wipe.finalVisible;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
this._pieceEffects = [];
|
|
191
|
+
this._wipes = [];
|
|
192
|
+
}
|
|
193
|
+
spawnRadialEffect(mode, sprite, glyphCanvas, context, options, forceHideSprite, restoreVisibility, finalVisible, onComplete) {
|
|
194
|
+
this.clearSpriteEffects(sprite);
|
|
195
|
+
if (forceHideSprite || restoreVisibility) {
|
|
196
|
+
sprite.visible = false;
|
|
197
|
+
}
|
|
198
|
+
const durationSeconds = options.durationMs / 1000;
|
|
199
|
+
const pieces = this.buildPieceGrid(glyphCanvas, context, options.rows, options.cols, (_row, _col, sx, sy, sw, sh, centerX, centerY) => {
|
|
200
|
+
const velocity = this.getRadialVelocity(centerX, centerY);
|
|
201
|
+
const distance = options.speed * durationSeconds * (0.65 + Math.random() * 0.7);
|
|
202
|
+
const targetX = context.baseX + centerX + velocity.x * distance;
|
|
203
|
+
const targetY = context.baseY +
|
|
204
|
+
centerY +
|
|
205
|
+
velocity.y * distance +
|
|
206
|
+
0.5 * options.gravityY * durationSeconds * durationSeconds;
|
|
207
|
+
const assembledX = context.baseX + centerX;
|
|
208
|
+
const assembledY = context.baseY + centerY;
|
|
209
|
+
const startRotation = mode === "explode"
|
|
210
|
+
? context.rotation
|
|
211
|
+
: context.rotation + (Math.random() * 2 - 1) * options.spin * durationSeconds;
|
|
212
|
+
const endRotation = mode === "explode"
|
|
213
|
+
? context.rotation + (Math.random() * 2 - 1) * options.spin * durationSeconds
|
|
214
|
+
: context.rotation;
|
|
215
|
+
return {
|
|
216
|
+
sx,
|
|
217
|
+
sy,
|
|
218
|
+
sw,
|
|
219
|
+
sh,
|
|
220
|
+
assembledX,
|
|
221
|
+
assembledY,
|
|
222
|
+
offsetX: targetX - assembledX,
|
|
223
|
+
offsetY: targetY - assembledY,
|
|
224
|
+
startRotation,
|
|
225
|
+
endRotation,
|
|
226
|
+
delayT: 0,
|
|
227
|
+
fade: options.fade,
|
|
228
|
+
};
|
|
229
|
+
}, mode);
|
|
230
|
+
this._pieceEffects.push({
|
|
231
|
+
sprite,
|
|
232
|
+
canvas: glyphCanvas,
|
|
233
|
+
layer: context.layer,
|
|
234
|
+
ignoreScroll: context.ignoreScroll,
|
|
235
|
+
pieces,
|
|
236
|
+
elapsedMs: 0,
|
|
237
|
+
durationMs: options.durationMs,
|
|
238
|
+
mode,
|
|
239
|
+
restoreVisibility,
|
|
240
|
+
finalVisible,
|
|
241
|
+
onComplete,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
spawnDirectionalEffect(mode, sprite, glyphCanvas, context, options, forceHideSprite, restoreVisibility, finalVisible, onComplete) {
|
|
245
|
+
this.clearSpriteEffects(sprite);
|
|
246
|
+
if (forceHideSprite || restoreVisibility) {
|
|
247
|
+
sprite.visible = false;
|
|
248
|
+
}
|
|
249
|
+
const durationSeconds = options.durationMs / 1000;
|
|
250
|
+
const maxDelayT = 0.58;
|
|
251
|
+
const inPlace = options.direction === "in-place";
|
|
252
|
+
const directionalFlow = inPlace
|
|
253
|
+
? "left-to-right"
|
|
254
|
+
: options.direction;
|
|
255
|
+
const directionVector = inPlace ? { x: 0, y: 0 } : this.getDirectionVector(directionalFlow);
|
|
256
|
+
const perpendicular = inPlace ? { x: 0, y: 0 } : { x: -directionVector.y, y: directionVector.x };
|
|
257
|
+
const pieces = this.buildPieceGrid(glyphCanvas, context, options.rows, options.cols, (row, col, sx, sy, sw, sh, centerX, centerY) => {
|
|
258
|
+
const baseDistance = inPlace ? 0 : options.distance * (0.45 + Math.random() * 0.75);
|
|
259
|
+
const sidewaysDistance = inPlace ? 0 : options.distance * 0.22 * (Math.random() * 2 - 1);
|
|
260
|
+
const offsetX = directionVector.x * baseDistance + perpendicular.x * sidewaysDistance;
|
|
261
|
+
const offsetY = directionVector.y * baseDistance + perpendicular.y * sidewaysDistance;
|
|
262
|
+
const assembledX = context.baseX + centerX;
|
|
263
|
+
const assembledY = context.baseY + centerY;
|
|
264
|
+
const delayT = inPlace
|
|
265
|
+
? Math.random() * (maxDelayT + 0.05)
|
|
266
|
+
: this.getDirectionalProgress(row, col, options.rows, options.cols, directionalFlow) *
|
|
267
|
+
maxDelayT +
|
|
268
|
+
Math.random() * 0.05;
|
|
269
|
+
const rotationOffset = inPlace
|
|
270
|
+
? 0
|
|
271
|
+
: (Math.random() * 2 - 1) * options.spin * durationSeconds;
|
|
272
|
+
return {
|
|
273
|
+
sx,
|
|
274
|
+
sy,
|
|
275
|
+
sw,
|
|
276
|
+
sh,
|
|
277
|
+
assembledX,
|
|
278
|
+
assembledY,
|
|
279
|
+
offsetX,
|
|
280
|
+
offsetY,
|
|
281
|
+
startRotation: mode === "disintegrate" ? context.rotation : context.rotation + rotationOffset,
|
|
282
|
+
endRotation: mode === "disintegrate" ? context.rotation + rotationOffset : context.rotation,
|
|
283
|
+
delayT: Math.min(maxDelayT + 0.05, delayT),
|
|
284
|
+
fade: options.fade,
|
|
285
|
+
};
|
|
286
|
+
}, mode);
|
|
287
|
+
this._pieceEffects.push({
|
|
288
|
+
sprite,
|
|
289
|
+
canvas: glyphCanvas,
|
|
290
|
+
layer: context.layer,
|
|
291
|
+
ignoreScroll: context.ignoreScroll,
|
|
292
|
+
pieces,
|
|
293
|
+
elapsedMs: 0,
|
|
294
|
+
durationMs: options.durationMs,
|
|
295
|
+
mode,
|
|
296
|
+
restoreVisibility,
|
|
297
|
+
finalVisible,
|
|
298
|
+
onComplete,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
buildPieceGrid(glyphCanvas, context, rows, cols, builder, mode) {
|
|
47
302
|
const pieces = [];
|
|
303
|
+
const rotationRad = (context.rotation * Math.PI) / 180;
|
|
48
304
|
for (let row = 0; row < rows; row++) {
|
|
49
305
|
const sy = Math.floor((row * glyphCanvas.height) / rows);
|
|
50
306
|
const nextSy = Math.floor(((row + 1) * glyphCanvas.height) / rows);
|
|
@@ -53,102 +309,142 @@ export class ExplosionSystem {
|
|
|
53
309
|
const sx = Math.floor((col * glyphCanvas.width) / cols);
|
|
54
310
|
const nextSx = Math.floor(((col + 1) * glyphCanvas.width) / cols);
|
|
55
311
|
const sw = Math.max(1, nextSx - sx);
|
|
56
|
-
const localX = (sx + sw / 2 - glyphCanvas.width / 2) * signedScaleX;
|
|
57
|
-
const localY = (sy + sh / 2 - glyphCanvas.height / 2) * signedScaleY;
|
|
312
|
+
const localX = (sx + sw / 2 - glyphCanvas.width / 2) * context.signedScaleX;
|
|
313
|
+
const localY = (sy + sh / 2 - glyphCanvas.height / 2) * context.signedScaleY;
|
|
58
314
|
const rotated = this.rotate(localX, localY, rotationRad);
|
|
59
|
-
const
|
|
315
|
+
const generated = builder(row, col, sx, sy, sw, sh, rotated.x, rotated.y);
|
|
316
|
+
const startX = mode === "explode" || mode === "disintegrate"
|
|
317
|
+
? generated.assembledX
|
|
318
|
+
: generated.assembledX + generated.offsetX;
|
|
319
|
+
const startY = mode === "explode" || mode === "disintegrate"
|
|
320
|
+
? generated.assembledY
|
|
321
|
+
: generated.assembledY + generated.offsetY;
|
|
322
|
+
const endX = mode === "explode" || mode === "disintegrate"
|
|
323
|
+
? generated.assembledX + generated.offsetX
|
|
324
|
+
: generated.assembledX;
|
|
325
|
+
const endY = mode === "explode" || mode === "disintegrate"
|
|
326
|
+
? generated.assembledY + generated.offsetY
|
|
327
|
+
: generated.assembledY;
|
|
328
|
+
const startAlpha = mode === "explode" || mode === "disintegrate"
|
|
329
|
+
? context.baseAlpha
|
|
330
|
+
: generated.fade
|
|
331
|
+
? 0
|
|
332
|
+
: context.baseAlpha;
|
|
333
|
+
const endAlpha = mode === "explode" || mode === "disintegrate"
|
|
334
|
+
? generated.fade
|
|
335
|
+
? 0
|
|
336
|
+
: context.baseAlpha
|
|
337
|
+
: context.baseAlpha;
|
|
60
338
|
pieces.push({
|
|
61
|
-
x:
|
|
62
|
-
y:
|
|
63
|
-
rotation:
|
|
64
|
-
alpha:
|
|
65
|
-
flipX:
|
|
66
|
-
flipY:
|
|
67
|
-
sx,
|
|
68
|
-
sy,
|
|
69
|
-
sw,
|
|
70
|
-
sh,
|
|
71
|
-
drawWidth: sw * absScaleX,
|
|
72
|
-
drawHeight: sh * absScaleY,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
339
|
+
x: startX,
|
|
340
|
+
y: startY,
|
|
341
|
+
rotation: generated.startRotation,
|
|
342
|
+
alpha: startAlpha,
|
|
343
|
+
flipX: context.flipX,
|
|
344
|
+
flipY: context.flipY,
|
|
345
|
+
sx: generated.sx,
|
|
346
|
+
sy: generated.sy,
|
|
347
|
+
sw: generated.sw,
|
|
348
|
+
sh: generated.sh,
|
|
349
|
+
drawWidth: generated.sw * context.absScaleX,
|
|
350
|
+
drawHeight: generated.sh * context.absScaleY,
|
|
351
|
+
startX,
|
|
352
|
+
startY,
|
|
353
|
+
endX,
|
|
354
|
+
endY,
|
|
355
|
+
startRotation: generated.startRotation,
|
|
356
|
+
endRotation: generated.endRotation,
|
|
357
|
+
startAlpha,
|
|
358
|
+
endAlpha,
|
|
359
|
+
delayT: generated.delayT,
|
|
76
360
|
});
|
|
77
361
|
}
|
|
78
362
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
363
|
+
return pieces;
|
|
364
|
+
}
|
|
365
|
+
buildPieceSpriteContext(sprite, glyphCanvas) {
|
|
366
|
+
const renderData = sprite._renderData;
|
|
367
|
+
const deformScaleX = this.getSafeScale(renderData?.scaleX);
|
|
368
|
+
const deformScaleY = this.getSafeScale(renderData?.scaleY);
|
|
369
|
+
const pivotX = this.getSafePivot(renderData?.pivotX);
|
|
370
|
+
const pivotY = this.getSafePivot(renderData?.pivotY);
|
|
371
|
+
const offsetX = this.getSafeNumber(renderData?.offsetX, 0);
|
|
372
|
+
const offsetY = this.getSafeNumber(renderData?.offsetY, 0);
|
|
373
|
+
const alphaMultiplier = this.getSafeNumber(renderData?.alphaMultiplier, 1);
|
|
374
|
+
const baseDisplayWidth = sprite.displayWidth;
|
|
375
|
+
const baseDisplayHeight = sprite.displayHeight;
|
|
376
|
+
const pivotOffsetX = (pivotX - 0.5) * baseDisplayWidth * (1 - deformScaleX);
|
|
377
|
+
const pivotOffsetY = (pivotY - 0.5) * baseDisplayHeight * (1 - deformScaleY);
|
|
378
|
+
const signedScaleX = this.getSafeScale(sprite.scale) * deformScaleX * (sprite.flipX ? -1 : 1);
|
|
379
|
+
const signedScaleY = this.getSafeScale(sprite.scale) * deformScaleY * (sprite.flipY ? -1 : 1);
|
|
380
|
+
return {
|
|
85
381
|
layer: sprite.layer,
|
|
86
382
|
ignoreScroll: sprite.ignoreScroll,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
383
|
+
baseX: sprite.renderX + pivotOffsetX + offsetX,
|
|
384
|
+
baseY: sprite.renderY + pivotOffsetY + offsetY,
|
|
385
|
+
rotation: sprite.rotation,
|
|
386
|
+
baseAlpha: Math.max(0, Math.min(1, sprite.alpha * alphaMultiplier)),
|
|
387
|
+
flipX: sprite.flipX,
|
|
388
|
+
flipY: sprite.flipY,
|
|
389
|
+
signedScaleX,
|
|
390
|
+
signedScaleY,
|
|
391
|
+
absScaleX: Math.abs(signedScaleX),
|
|
392
|
+
absScaleY: Math.abs(signedScaleY),
|
|
393
|
+
};
|
|
97
394
|
}
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const t = Math.min(explosion.elapsedMs / explosion.durationMs, 1);
|
|
105
|
-
for (const piece of explosion.pieces) {
|
|
106
|
-
piece.vy += explosion.gravityY * dt;
|
|
107
|
-
piece.x += piece.vx * dt;
|
|
108
|
-
piece.y += piece.vy * dt;
|
|
109
|
-
piece.rotation += piece.angularVelocity * dt;
|
|
110
|
-
piece.alpha = explosion.fade
|
|
111
|
-
? explosion.baseAlpha * (1 - t)
|
|
112
|
-
: explosion.baseAlpha;
|
|
113
|
-
}
|
|
114
|
-
if (t >= 1) {
|
|
115
|
-
toRemove.push(i);
|
|
116
|
-
if (explosion.restoreVisibility) {
|
|
117
|
-
explosion.sprite.visible = explosion.originalVisible;
|
|
395
|
+
clearSpriteEffects(sprite) {
|
|
396
|
+
const remainingPieces = [];
|
|
397
|
+
for (const effect of this._pieceEffects) {
|
|
398
|
+
if (effect.sprite === sprite) {
|
|
399
|
+
if (effect.restoreVisibility) {
|
|
400
|
+
effect.sprite.visible = effect.finalVisible;
|
|
118
401
|
}
|
|
119
|
-
explosion.onComplete?.();
|
|
120
402
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
explosions.splice(toRemove[i], 1);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
getRenderEntries() {
|
|
127
|
-
return this._explosions;
|
|
128
|
-
}
|
|
129
|
-
clearAll() {
|
|
130
|
-
for (const explosion of this._explosions) {
|
|
131
|
-
if (explosion.restoreVisibility) {
|
|
132
|
-
explosion.sprite.visible = explosion.originalVisible;
|
|
403
|
+
else {
|
|
404
|
+
remainingPieces.push(effect);
|
|
133
405
|
}
|
|
134
406
|
}
|
|
135
|
-
this.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (explosion.restoreVisibility) {
|
|
142
|
-
explosion.sprite.visible = explosion.originalVisible;
|
|
407
|
+
this._pieceEffects = remainingPieces;
|
|
408
|
+
const remainingWipes = [];
|
|
409
|
+
for (const wipe of this._wipes) {
|
|
410
|
+
if (wipe.sprite === sprite) {
|
|
411
|
+
if (wipe.restoreVisibility) {
|
|
412
|
+
wipe.sprite.visible = wipe.finalVisible;
|
|
143
413
|
}
|
|
144
414
|
}
|
|
145
415
|
else {
|
|
146
|
-
|
|
416
|
+
remainingWipes.push(wipe);
|
|
147
417
|
}
|
|
148
418
|
}
|
|
149
|
-
this.
|
|
419
|
+
this._wipes = remainingWipes;
|
|
420
|
+
}
|
|
421
|
+
getDirectionalProgress(row, col, rows, cols, direction) {
|
|
422
|
+
const rowProgress = rows <= 1 ? 0 : row / (rows - 1);
|
|
423
|
+
const colProgress = cols <= 1 ? 0 : col / (cols - 1);
|
|
424
|
+
switch (direction) {
|
|
425
|
+
case "left-to-right":
|
|
426
|
+
return colProgress;
|
|
427
|
+
case "right-to-left":
|
|
428
|
+
return 1 - colProgress;
|
|
429
|
+
case "top-to-bottom":
|
|
430
|
+
return rowProgress;
|
|
431
|
+
case "bottom-to-top":
|
|
432
|
+
return 1 - rowProgress;
|
|
433
|
+
}
|
|
150
434
|
}
|
|
151
|
-
|
|
435
|
+
getDirectionVector(direction) {
|
|
436
|
+
switch (direction) {
|
|
437
|
+
case "left-to-right":
|
|
438
|
+
return { x: 1, y: 0 };
|
|
439
|
+
case "right-to-left":
|
|
440
|
+
return { x: -1, y: 0 };
|
|
441
|
+
case "top-to-bottom":
|
|
442
|
+
return { x: 0, y: 1 };
|
|
443
|
+
case "bottom-to-top":
|
|
444
|
+
return { x: 0, y: -1 };
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
getRadialVelocity(x, y) {
|
|
152
448
|
const magnitude = Math.hypot(x, y);
|
|
153
449
|
let dirX = 0;
|
|
154
450
|
let dirY = 0;
|
|
@@ -162,12 +458,18 @@ export class ExplosionSystem {
|
|
|
162
458
|
dirY = Math.sin(angle);
|
|
163
459
|
}
|
|
164
460
|
const jitterAngle = (Math.random() - 0.5) * (Math.PI / 2);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
461
|
+
return this.rotate(dirX, dirY, jitterAngle);
|
|
462
|
+
}
|
|
463
|
+
easePieceProgress(mode, t) {
|
|
464
|
+
const clamped = Math.max(0, Math.min(1, t));
|
|
465
|
+
switch (mode) {
|
|
466
|
+
case "explode":
|
|
467
|
+
case "disintegrate":
|
|
468
|
+
return 1 - (1 - clamped) * (1 - clamped);
|
|
469
|
+
case "assemble":
|
|
470
|
+
case "integrate":
|
|
471
|
+
return clamped * clamped * (3 - 2 * clamped);
|
|
472
|
+
}
|
|
171
473
|
}
|
|
172
474
|
rotate(x, y, radians) {
|
|
173
475
|
const cos = Math.cos(radians);
|
|
@@ -177,6 +479,9 @@ export class ExplosionSystem {
|
|
|
177
479
|
y: x * sin + y * cos,
|
|
178
480
|
};
|
|
179
481
|
}
|
|
482
|
+
lerp(from, to, t) {
|
|
483
|
+
return from + (to - from) * t;
|
|
484
|
+
}
|
|
180
485
|
getSafeScale(value) {
|
|
181
486
|
if (!Number.isFinite(value))
|
|
182
487
|
return 1;
|
|
@@ -95,6 +95,31 @@ export class RenderSystem {
|
|
|
95
95
|
ctx.drawImage(snapshot.canvas, -snapshot.drawWidth / 2, -snapshot.drawHeight / 2, snapshot.drawWidth, snapshot.drawHeight);
|
|
96
96
|
ctx.restore();
|
|
97
97
|
}
|
|
98
|
+
const wipeEntries = [...options.wipes].sort((a, b) => a.layer - b.layer);
|
|
99
|
+
for (const entry of wipeEntries) {
|
|
100
|
+
const visibleProgress = Math.max(0, Math.min(1, entry.progress));
|
|
101
|
+
if (visibleProgress <= 0)
|
|
102
|
+
continue;
|
|
103
|
+
ctx.save();
|
|
104
|
+
ctx.globalAlpha = entry.alpha;
|
|
105
|
+
const drawX = Math.round(entry.ignoreScroll ? entry.x : entry.x - options.scrollX);
|
|
106
|
+
const drawY = Math.round(entry.ignoreScroll ? entry.y : entry.y - options.scrollY);
|
|
107
|
+
ctx.translate(drawX, drawY);
|
|
108
|
+
if (entry.rotation !== 0) {
|
|
109
|
+
ctx.rotate((entry.rotation * Math.PI) / 180);
|
|
110
|
+
}
|
|
111
|
+
if (entry.flipX || entry.flipY) {
|
|
112
|
+
ctx.scale(entry.flipX ? -1 : 1, entry.flipY ? -1 : 1);
|
|
113
|
+
}
|
|
114
|
+
const clip = this.getWipeClipRect(entry.drawWidth, entry.drawHeight, entry.direction, visibleProgress);
|
|
115
|
+
if (clip.width > 0 && clip.height > 0) {
|
|
116
|
+
ctx.beginPath();
|
|
117
|
+
ctx.rect(clip.x, clip.y, clip.width, clip.height);
|
|
118
|
+
ctx.clip();
|
|
119
|
+
ctx.drawImage(entry.canvas, -entry.drawWidth / 2, -entry.drawHeight / 2, entry.drawWidth, entry.drawHeight);
|
|
120
|
+
}
|
|
121
|
+
ctx.restore();
|
|
122
|
+
}
|
|
98
123
|
const explosionEntries = [...options.explosions].sort((a, b) => a.layer - b.layer);
|
|
99
124
|
for (const entry of explosionEntries) {
|
|
100
125
|
for (const piece of entry.pieces) {
|
|
@@ -278,6 +303,44 @@ export class RenderSystem {
|
|
|
278
303
|
isImageSprite(sprite) {
|
|
279
304
|
return "imageKey" in sprite && "setTexture" in sprite;
|
|
280
305
|
}
|
|
306
|
+
getWipeClipRect(drawWidth, drawHeight, direction, progress) {
|
|
307
|
+
const halfWidth = drawWidth / 2;
|
|
308
|
+
const halfHeight = drawHeight / 2;
|
|
309
|
+
switch (direction) {
|
|
310
|
+
case "left-to-right":
|
|
311
|
+
return {
|
|
312
|
+
x: -halfWidth,
|
|
313
|
+
y: -halfHeight,
|
|
314
|
+
width: drawWidth * progress,
|
|
315
|
+
height: drawHeight,
|
|
316
|
+
};
|
|
317
|
+
case "right-to-left": {
|
|
318
|
+
const width = drawWidth * progress;
|
|
319
|
+
return {
|
|
320
|
+
x: halfWidth - width,
|
|
321
|
+
y: -halfHeight,
|
|
322
|
+
width,
|
|
323
|
+
height: drawHeight,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
case "top-to-bottom":
|
|
327
|
+
return {
|
|
328
|
+
x: -halfWidth,
|
|
329
|
+
y: -halfHeight,
|
|
330
|
+
width: drawWidth,
|
|
331
|
+
height: drawHeight * progress,
|
|
332
|
+
};
|
|
333
|
+
case "bottom-to-top": {
|
|
334
|
+
const height = drawHeight * progress;
|
|
335
|
+
return {
|
|
336
|
+
x: -halfWidth,
|
|
337
|
+
y: halfHeight - height,
|
|
338
|
+
width: drawWidth,
|
|
339
|
+
height,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
281
344
|
asEmojiSprite(sprite) {
|
|
282
345
|
if ("sprite" in sprite && "size" in sprite) {
|
|
283
346
|
return sprite;
|
package/dist/minimo.d.ts
CHANGED
|
@@ -30,6 +30,102 @@ export interface ExplodeOptions {
|
|
|
30
30
|
/** Whether the original sprite is destroyed instead of restored after the effect. */
|
|
31
31
|
destroySprite?: boolean;
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Cardinal flow direction used by directional visual effects.
|
|
35
|
+
*/
|
|
36
|
+
export type FlowDirection = "left-to-right" | "right-to-left" | "top-to-bottom" | "bottom-to-top";
|
|
37
|
+
/**
|
|
38
|
+
* Direction used by piece-based local integration and disintegration effects.
|
|
39
|
+
*
|
|
40
|
+
* - Cardinal values apply a directional sweep across the sprite.
|
|
41
|
+
* - `"in-place"` keeps pieces in their final positions and only randomizes
|
|
42
|
+
* when each piece appears or disappears.
|
|
43
|
+
*/
|
|
44
|
+
export type PieceFlowDirection = FlowDirection | "in-place";
|
|
45
|
+
/**
|
|
46
|
+
* Optional tuning values for {@link Game.animateAssemble}.
|
|
47
|
+
*
|
|
48
|
+
* All fields are optional. Omitted fields use the engine defaults.
|
|
49
|
+
*/
|
|
50
|
+
export interface AssembleOptions {
|
|
51
|
+
/** Number of vertical slices used to reconstruct the sprite. */
|
|
52
|
+
rows?: number;
|
|
53
|
+
/** Number of horizontal slices used to reconstruct the sprite. */
|
|
54
|
+
cols?: number;
|
|
55
|
+
/** Total effect duration in **milliseconds**. */
|
|
56
|
+
durationMs?: number;
|
|
57
|
+
/** Initial outward distance magnitude in pixels per second equivalent. */
|
|
58
|
+
speed?: number;
|
|
59
|
+
/** Downward pull applied while pieces converge, in pixels per second squared. */
|
|
60
|
+
gravityY?: number;
|
|
61
|
+
/** Maximum angular speed applied to pieces, in degrees per second. */
|
|
62
|
+
spin?: number;
|
|
63
|
+
/** Whether pieces fade in while assembling. */
|
|
64
|
+
fade?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Optional tuning values for {@link Game.animateDisintegrate}.
|
|
68
|
+
*
|
|
69
|
+
* All fields are optional. Omitted fields use the engine defaults.
|
|
70
|
+
*/
|
|
71
|
+
export interface DisintegrateOptions {
|
|
72
|
+
/** Number of vertical slices used to dissolve the sprite. */
|
|
73
|
+
rows?: number;
|
|
74
|
+
/** Number of horizontal slices used to dissolve the sprite. */
|
|
75
|
+
cols?: number;
|
|
76
|
+
/** Total effect duration in **milliseconds**. */
|
|
77
|
+
durationMs?: number;
|
|
78
|
+
/** Primary sweep direction across the sprite. */
|
|
79
|
+
direction?: PieceFlowDirection;
|
|
80
|
+
/** Maximum local travel distance in pixels for each piece. */
|
|
81
|
+
distance?: number;
|
|
82
|
+
/** Maximum angular speed applied to pieces, in degrees per second. */
|
|
83
|
+
spin?: number;
|
|
84
|
+
/** Whether pieces fade out as they disintegrate. */
|
|
85
|
+
fade?: boolean;
|
|
86
|
+
/** Whether the original sprite is destroyed instead of left hidden after the effect. */
|
|
87
|
+
destroySprite?: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Optional tuning values for {@link Game.animateIntegrate}.
|
|
91
|
+
*
|
|
92
|
+
* All fields are optional. Omitted fields use the engine defaults.
|
|
93
|
+
*/
|
|
94
|
+
export interface IntegrateOptions {
|
|
95
|
+
/** Number of vertical slices used to reconstruct the sprite. */
|
|
96
|
+
rows?: number;
|
|
97
|
+
/** Number of horizontal slices used to reconstruct the sprite. */
|
|
98
|
+
cols?: number;
|
|
99
|
+
/** Total effect duration in **milliseconds**. */
|
|
100
|
+
durationMs?: number;
|
|
101
|
+
/** Primary sweep direction across the sprite. */
|
|
102
|
+
direction?: PieceFlowDirection;
|
|
103
|
+
/** Maximum local travel distance in pixels for each piece. */
|
|
104
|
+
distance?: number;
|
|
105
|
+
/** Maximum angular speed applied to pieces, in degrees per second. */
|
|
106
|
+
spin?: number;
|
|
107
|
+
/** Whether pieces fade in while integrating. */
|
|
108
|
+
fade?: boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Wipe mode used by {@link Game.animateWipe}.
|
|
112
|
+
*/
|
|
113
|
+
export type WipeMode = "reveal" | "cover";
|
|
114
|
+
/**
|
|
115
|
+
* Optional tuning values for {@link Game.animateWipe}.
|
|
116
|
+
*
|
|
117
|
+
* All fields are optional. Omitted fields use the engine defaults.
|
|
118
|
+
*/
|
|
119
|
+
export interface WipeOptions {
|
|
120
|
+
/** Whether the wipe reveals the sprite or covers it away. */
|
|
121
|
+
mode?: WipeMode;
|
|
122
|
+
/** Primary wipe direction. */
|
|
123
|
+
direction?: FlowDirection;
|
|
124
|
+
/** Total effect duration in **milliseconds**. */
|
|
125
|
+
durationMs?: number;
|
|
126
|
+
/** Whether the original sprite is destroyed for `mode: "cover"`. */
|
|
127
|
+
destroySprite?: boolean;
|
|
128
|
+
}
|
|
33
129
|
/**
|
|
34
130
|
* Optional tuning values for {@link Game.animateTrail}.
|
|
35
131
|
*
|
|
@@ -1682,6 +1778,69 @@ export declare class Game {
|
|
|
1682
1778
|
* ```
|
|
1683
1779
|
*/
|
|
1684
1780
|
animateExplode(sprite: BaseSprite, options?: ExplodeOptions, onComplete?: () => void): void;
|
|
1781
|
+
/**
|
|
1782
|
+
* Spawns visual pieces around a sprite and converges them back into its
|
|
1783
|
+
* current rendered appearance.
|
|
1784
|
+
*
|
|
1785
|
+
* This is a render-only effect. The spawned pieces are not real sprites and
|
|
1786
|
+
* do not appear in {@link Game.getSprites}, receive physics, or collide.
|
|
1787
|
+
*
|
|
1788
|
+
* The original sprite is hidden while the assembly plays and is restored when
|
|
1789
|
+
* the effect finishes.
|
|
1790
|
+
*
|
|
1791
|
+
* @param sprite - The sprite to assemble.
|
|
1792
|
+
* @param options - Optional assembly tuning values.
|
|
1793
|
+
* @param onComplete - Optional callback invoked when the effect finishes.
|
|
1794
|
+
*/
|
|
1795
|
+
animateAssemble(sprite: BaseSprite, options?: AssembleOptions, onComplete?: () => void): void;
|
|
1796
|
+
/**
|
|
1797
|
+
* Breaks a sprite apart locally inside its own bounds using a directional
|
|
1798
|
+
* dissolve sweep.
|
|
1799
|
+
*
|
|
1800
|
+
* This is a render-only effect. The spawned pieces are not real sprites and
|
|
1801
|
+
* do not appear in {@link Game.getSprites}, receive physics, or collide.
|
|
1802
|
+
*
|
|
1803
|
+
* By default, the original sprite is destroyed immediately after the effect
|
|
1804
|
+
* is spawned. Set `destroySprite: false` to leave the sprite hidden instead.
|
|
1805
|
+
*
|
|
1806
|
+
* @param sprite - The sprite to disintegrate.
|
|
1807
|
+
* @param options - Optional disintegration tuning values.
|
|
1808
|
+
* @param onComplete - Optional callback invoked when the effect finishes.
|
|
1809
|
+
*/
|
|
1810
|
+
animateDisintegrate(sprite: BaseSprite, options?: DisintegrateOptions, onComplete?: () => void): void;
|
|
1811
|
+
/**
|
|
1812
|
+
* Reconstructs a sprite locally inside its own bounds using a directional
|
|
1813
|
+
* integration sweep.
|
|
1814
|
+
*
|
|
1815
|
+
* This is a render-only effect. The spawned pieces are not real sprites and
|
|
1816
|
+
* do not appear in {@link Game.getSprites}, receive physics, or collide.
|
|
1817
|
+
*
|
|
1818
|
+
* The original sprite is hidden while the integration plays and is restored
|
|
1819
|
+
* when the effect finishes.
|
|
1820
|
+
*
|
|
1821
|
+
* @param sprite - The sprite to integrate.
|
|
1822
|
+
* @param options - Optional integration tuning values.
|
|
1823
|
+
* @param onComplete - Optional callback invoked when the effect finishes.
|
|
1824
|
+
*/
|
|
1825
|
+
animateIntegrate(sprite: BaseSprite, options?: IntegrateOptions, onComplete?: () => void): void;
|
|
1826
|
+
/**
|
|
1827
|
+
* Reveals or covers a sprite with a directional wipe mask.
|
|
1828
|
+
*
|
|
1829
|
+
* This is a render-only effect that captures the sprite's current appearance
|
|
1830
|
+
* and animates a clipping region over that snapshot.
|
|
1831
|
+
*
|
|
1832
|
+
* For `mode: "reveal"`, the original sprite is hidden during the wipe and is
|
|
1833
|
+
* restored when the effect finishes.
|
|
1834
|
+
*
|
|
1835
|
+
* For `mode: "cover"`, the effect hides the sprite over time. If
|
|
1836
|
+
* `destroySprite` is omitted or `true`, the sprite is destroyed immediately
|
|
1837
|
+
* after the wipe starts and only the wipe snapshot remains visible.
|
|
1838
|
+
*
|
|
1839
|
+
* @param sprite - The sprite to wipe.
|
|
1840
|
+
* @param options - Optional wipe tuning values.
|
|
1841
|
+
* @param onComplete - Optional callback invoked when the effect finishes.
|
|
1842
|
+
*/
|
|
1843
|
+
animateWipe(sprite: BaseSprite, options?: WipeOptions, onComplete?: () => void): void;
|
|
1685
1844
|
/**
|
|
1686
1845
|
* Schedules a callback to fire after `delayMs` milliseconds, driven by the
|
|
1687
1846
|
* rAF loop (not `setTimeout`). Timers accumulate elapsed time each frame and
|
|
@@ -1883,7 +2042,7 @@ export declare class Game {
|
|
|
1883
2042
|
* overlays. Order per frame:
|
|
1884
2043
|
* 1. Accumulate timer elapsed time; fire ready callbacks.
|
|
1885
2044
|
* 2. Advance animations (linear interpolation).
|
|
1886
|
-
* 3. Advance active
|
|
2045
|
+
* 3. Advance active piece and wipe effects.
|
|
1887
2046
|
* 4. Apply gravity to sprite velocities.
|
|
1888
2047
|
* 5. Apply velocities to sprite positions.
|
|
1889
2048
|
* 6. Call `onUpdate(dt)`.
|
package/dist/minimo.js
CHANGED
|
@@ -1982,6 +1982,87 @@ export class Game {
|
|
|
1982
1982
|
this.destroySprite(sprite);
|
|
1983
1983
|
}
|
|
1984
1984
|
}
|
|
1985
|
+
/**
|
|
1986
|
+
* Spawns visual pieces around a sprite and converges them back into its
|
|
1987
|
+
* current rendered appearance.
|
|
1988
|
+
*
|
|
1989
|
+
* This is a render-only effect. The spawned pieces are not real sprites and
|
|
1990
|
+
* do not appear in {@link Game.getSprites}, receive physics, or collide.
|
|
1991
|
+
*
|
|
1992
|
+
* The original sprite is hidden while the assembly plays and is restored when
|
|
1993
|
+
* the effect finishes.
|
|
1994
|
+
*
|
|
1995
|
+
* @param sprite - The sprite to assemble.
|
|
1996
|
+
* @param options - Optional assembly tuning values.
|
|
1997
|
+
* @param onComplete - Optional callback invoked when the effect finishes.
|
|
1998
|
+
*/
|
|
1999
|
+
animateAssemble(sprite, options, onComplete) {
|
|
2000
|
+
const glyphCanvas = this._renderSystem.getSpriteGlyphCanvasForEffects(sprite);
|
|
2001
|
+
this._explosionSystem.animateAssemble(sprite, glyphCanvas, options, onComplete);
|
|
2002
|
+
}
|
|
2003
|
+
/**
|
|
2004
|
+
* Breaks a sprite apart locally inside its own bounds using a directional
|
|
2005
|
+
* dissolve sweep.
|
|
2006
|
+
*
|
|
2007
|
+
* This is a render-only effect. The spawned pieces are not real sprites and
|
|
2008
|
+
* do not appear in {@link Game.getSprites}, receive physics, or collide.
|
|
2009
|
+
*
|
|
2010
|
+
* By default, the original sprite is destroyed immediately after the effect
|
|
2011
|
+
* is spawned. Set `destroySprite: false` to leave the sprite hidden instead.
|
|
2012
|
+
*
|
|
2013
|
+
* @param sprite - The sprite to disintegrate.
|
|
2014
|
+
* @param options - Optional disintegration tuning values.
|
|
2015
|
+
* @param onComplete - Optional callback invoked when the effect finishes.
|
|
2016
|
+
*/
|
|
2017
|
+
animateDisintegrate(sprite, options, onComplete) {
|
|
2018
|
+
const glyphCanvas = this._renderSystem.getSpriteGlyphCanvasForEffects(sprite);
|
|
2019
|
+
this._explosionSystem.animateDisintegrate(sprite, glyphCanvas, options, onComplete);
|
|
2020
|
+
if (options?.destroySprite !== false) {
|
|
2021
|
+
this.destroySprite(sprite);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Reconstructs a sprite locally inside its own bounds using a directional
|
|
2026
|
+
* integration sweep.
|
|
2027
|
+
*
|
|
2028
|
+
* This is a render-only effect. The spawned pieces are not real sprites and
|
|
2029
|
+
* do not appear in {@link Game.getSprites}, receive physics, or collide.
|
|
2030
|
+
*
|
|
2031
|
+
* The original sprite is hidden while the integration plays and is restored
|
|
2032
|
+
* when the effect finishes.
|
|
2033
|
+
*
|
|
2034
|
+
* @param sprite - The sprite to integrate.
|
|
2035
|
+
* @param options - Optional integration tuning values.
|
|
2036
|
+
* @param onComplete - Optional callback invoked when the effect finishes.
|
|
2037
|
+
*/
|
|
2038
|
+
animateIntegrate(sprite, options, onComplete) {
|
|
2039
|
+
const glyphCanvas = this._renderSystem.getSpriteGlyphCanvasForEffects(sprite);
|
|
2040
|
+
this._explosionSystem.animateIntegrate(sprite, glyphCanvas, options, onComplete);
|
|
2041
|
+
}
|
|
2042
|
+
/**
|
|
2043
|
+
* Reveals or covers a sprite with a directional wipe mask.
|
|
2044
|
+
*
|
|
2045
|
+
* This is a render-only effect that captures the sprite's current appearance
|
|
2046
|
+
* and animates a clipping region over that snapshot.
|
|
2047
|
+
*
|
|
2048
|
+
* For `mode: "reveal"`, the original sprite is hidden during the wipe and is
|
|
2049
|
+
* restored when the effect finishes.
|
|
2050
|
+
*
|
|
2051
|
+
* For `mode: "cover"`, the effect hides the sprite over time. If
|
|
2052
|
+
* `destroySprite` is omitted or `true`, the sprite is destroyed immediately
|
|
2053
|
+
* after the wipe starts and only the wipe snapshot remains visible.
|
|
2054
|
+
*
|
|
2055
|
+
* @param sprite - The sprite to wipe.
|
|
2056
|
+
* @param options - Optional wipe tuning values.
|
|
2057
|
+
* @param onComplete - Optional callback invoked when the effect finishes.
|
|
2058
|
+
*/
|
|
2059
|
+
animateWipe(sprite, options, onComplete) {
|
|
2060
|
+
const snapshot = this._renderSystem.getSpriteRenderSnapshot(sprite);
|
|
2061
|
+
this._explosionSystem.animateWipe(sprite, snapshot, options, onComplete);
|
|
2062
|
+
if ((options?.mode ?? "reveal") === "cover" && options?.destroySprite !== false) {
|
|
2063
|
+
this.destroySprite(sprite);
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
1985
2066
|
// -------------------------------------------------------------------------
|
|
1986
2067
|
// Timers
|
|
1987
2068
|
// -------------------------------------------------------------------------
|
|
@@ -2240,7 +2321,7 @@ export class Game {
|
|
|
2240
2321
|
* overlays. Order per frame:
|
|
2241
2322
|
* 1. Accumulate timer elapsed time; fire ready callbacks.
|
|
2242
2323
|
* 2. Advance animations (linear interpolation).
|
|
2243
|
-
* 3. Advance active
|
|
2324
|
+
* 3. Advance active piece and wipe effects.
|
|
2244
2325
|
* 4. Apply gravity to sprite velocities.
|
|
2245
2326
|
* 5. Apply velocities to sprite positions.
|
|
2246
2327
|
* 6. Call `onUpdate(dt)`.
|
|
@@ -2425,6 +2506,7 @@ export class Game {
|
|
|
2425
2506
|
sprites: this._spriteSystem.getMutableSprites(),
|
|
2426
2507
|
trails: this._trailSystem.getRenderEntries(),
|
|
2427
2508
|
explosions: this._explosionSystem.getRenderEntries(),
|
|
2509
|
+
wipes: this._explosionSystem.getWipeEntries(),
|
|
2428
2510
|
textEntries: this._textSystem.getEntries(),
|
|
2429
2511
|
scrollX: this.scrollX,
|
|
2430
2512
|
scrollY: this.scrollY,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minimojs",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.13",
|
|
4
4
|
"description": "MinimoJS v1 — ultra-minimal, flat, deterministic 2D web game engine. Emoji-only sprites, rAF loop, TypeScript-first, LLM-friendly.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/minimo.js",
|