minimojs 1.0.0-alpha.4 → 1.0.0-alpha.41
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 +58 -20
- package/dist/internal/AnimationSystem.js +345 -0
- package/dist/internal/AssetSystem.d.ts +6 -0
- package/dist/internal/AssetSystem.js +281 -0
- package/dist/internal/BackgroundSystem.d.ts +1 -0
- package/dist/internal/BackgroundSystem.js +26 -0
- package/dist/internal/CanvasSystem.d.ts +1 -0
- package/dist/internal/CanvasSystem.js +51 -0
- package/dist/internal/DesignNamingCheck.d.ts +1 -0
- package/dist/internal/DesignNamingCheck.js +7 -0
- package/dist/internal/ExplosionSystem.d.ts +1 -0
- package/dist/internal/ExplosionSystem.js +540 -0
- package/dist/internal/InputSystem.d.ts +1 -0
- package/dist/internal/InputSystem.js +292 -0
- package/dist/internal/LoopSystem.d.ts +1 -0
- package/dist/internal/LoopSystem.js +61 -0
- package/dist/internal/PhysicsSystem.d.ts +1 -0
- package/dist/internal/PhysicsSystem.js +174 -0
- package/dist/internal/RenderSystem.d.ts +1 -0
- package/dist/internal/RenderSystem.js +1021 -0
- package/dist/internal/SoundSystem.d.ts +39 -0
- package/dist/internal/SoundSystem.js +292 -0
- package/dist/internal/SpriteSystem.d.ts +1 -0
- package/dist/internal/SpriteSystem.js +32 -0
- package/dist/internal/TextSystem.d.ts +1 -0
- package/dist/internal/TextSystem.js +16 -0
- package/dist/internal/TimerSystem.d.ts +1 -0
- package/dist/internal/TimerSystem.js +43 -0
- package/dist/internal/TrailSystem.d.ts +1 -0
- package/dist/internal/TrailSystem.js +116 -0
- package/dist/internal/TransitionSystem.d.ts +1 -0
- package/dist/internal/TransitionSystem.js +74 -0
- package/dist/internal/arcade-racer/ArcadeRacerCollisionSystem.d.ts +1 -0
- package/dist/internal/arcade-racer/ArcadeRacerCollisionSystem.js +198 -0
- package/dist/internal/arcade-racer/ArcadeRacerLaneSystem.d.ts +1 -0
- package/dist/internal/arcade-racer/ArcadeRacerLaneSystem.js +120 -0
- package/dist/internal/arcade-racer/ArcadeRacerRenderSystem.d.ts +1 -0
- package/dist/internal/arcade-racer/ArcadeRacerRenderSystem.js +599 -0
- package/dist/internal/arcade-racer/ArcadeRacerRoadSprite.d.ts +1 -0
- package/dist/internal/arcade-racer/ArcadeRacerRoadSprite.js +13 -0
- package/dist/internal/arcade-racer/ArcadeRacerTrackSystem.d.ts +1 -0
- package/dist/internal/arcade-racer/ArcadeRacerTrackSystem.js +447 -0
- package/dist/minimo-ambient.d.ts +299 -0
- package/dist/minimo-ambient.js +1445 -0
- package/dist/minimo-arcaderacer.d.ts +1431 -0
- package/dist/minimo-arcaderacer.js +2060 -0
- package/dist/minimo-cards.d.ts +359 -0
- package/dist/minimo-cards.js +819 -0
- package/dist/minimo-dice.d.ts +206 -0
- package/dist/minimo-dice.js +719 -0
- package/dist/minimo-fire.d.ts +282 -0
- package/dist/minimo-fire.js +930 -0
- package/dist/minimo-format.d.ts +632 -0
- package/dist/minimo-format.js +669 -0
- package/dist/minimo-leaderboard.d.ts +199 -0
- package/dist/minimo-leaderboard.js +566 -0
- package/dist/minimo-multiplayer.d.ts +387 -0
- package/dist/minimo-multiplayer.js +1323 -0
- package/dist/minimo.d.ts +1690 -233
- package/dist/minimo.js +2587 -892
- package/package.json +10 -3
- package/dist/animations.js +0 -30
- package/dist/audio.js +0 -17
- package/dist/game.js +0 -1105
- package/dist/input.js +0 -185
- package/dist/internal-types.js +0 -4
- package/dist/physics.js +0 -10
- package/dist/render.js +0 -75
- package/dist/sprite.js +0 -149
- package/dist/timers.js +0 -23
- /package/dist/{pointer-info.js → internal/AnimationSystem.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ This README is intentionally high-level. It explains what the project is and how
|
|
|
10
10
|
|
|
11
11
|
- ESM-only TypeScript-first engine
|
|
12
12
|
- Single `Game` entry point
|
|
13
|
-
-
|
|
13
|
+
- Image-backed sprite rendering, with emoji as a zero-asset alternative
|
|
14
14
|
- rAF-driven loop (timers/animations/updates)
|
|
15
15
|
- Responsive auto-centered canvas
|
|
16
16
|
|
|
@@ -21,12 +21,13 @@ This README is intentionally high-level. It explains what the project is and how
|
|
|
21
21
|
- Runtime update delta (`dt`) is seconds
|
|
22
22
|
- Coordinate system is center-based world space
|
|
23
23
|
- Positive Y goes downward
|
|
24
|
+
- For new mobile-first games, prefer a portrait canvas of `720x1280`
|
|
24
25
|
|
|
25
26
|
## What It Intentionally Avoids
|
|
26
27
|
|
|
27
|
-
-
|
|
28
|
+
- Heavy scene-manager/ECS architecture
|
|
28
29
|
- Heavy physics engine features
|
|
29
|
-
-
|
|
30
|
+
- Spritesheets, texture atlases, and asset pipelines
|
|
30
31
|
- Nested subsystem APIs
|
|
31
32
|
- `setTimeout` / `setInterval` game loops
|
|
32
33
|
|
|
@@ -39,29 +40,58 @@ npm install minimojs
|
|
|
39
40
|
## Quick Start
|
|
40
41
|
|
|
41
42
|
```ts
|
|
42
|
-
import { Game,
|
|
43
|
+
import { DrawSprite, EmojiSprite, Game, type IScene } from "minimojs";
|
|
43
44
|
|
|
44
|
-
const game = new Game(
|
|
45
|
+
const game = new Game(720, 1280);
|
|
45
46
|
game.gravityY = 980;
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
class MeterSprite extends DrawSprite {
|
|
49
|
+
public value = 0.5;
|
|
50
|
+
|
|
51
|
+
constructor(x: number, y: number) {
|
|
52
|
+
super(160, 28, x, y);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
redraw(ctx: CanvasRenderingContext2D) {
|
|
56
|
+
ctx.fillStyle = "#1a2236";
|
|
57
|
+
ctx.fillRect(0, 0, this.width, this.height);
|
|
58
|
+
ctx.fillStyle = "#7ce7ff";
|
|
59
|
+
ctx.fillRect(0, 0, this.width * this.value, this.height);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class DemoScene implements IScene {
|
|
64
|
+
private player: EmojiSprite | null = null;
|
|
65
|
+
private meter: MeterSprite | null = null;
|
|
66
|
+
|
|
67
|
+
onCreate() {
|
|
68
|
+
this.player = game.add(new EmojiSprite("🐢", 360, 640, 48));
|
|
69
|
+
this.player.gravityScale = 1;
|
|
70
|
+
this.meter = game.add(new MeterSprite(360, 80));
|
|
71
|
+
this.meter.ignoreScroll = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
onUpdate(dt: number) {
|
|
75
|
+
if (!this.player) return;
|
|
76
|
+
|
|
77
|
+
if (game.isKeyDown("ArrowLeft")) this.player.vx = -200;
|
|
78
|
+
else if (game.isKeyDown("ArrowRight")) this.player.vx = 200;
|
|
79
|
+
else this.player.vx = 0;
|
|
80
|
+
|
|
81
|
+
if (game.isKeyPressed(" ")) this.player.vy = -600;
|
|
82
|
+
if (this.meter) this.meter.value = Math.abs(this.player.vx) / 200;
|
|
83
|
+
game.drawText("MinimoJS", 10, 10, 16);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
game.start(new DemoScene());
|
|
88
|
+
```
|
|
56
89
|
|
|
57
|
-
|
|
58
|
-
game.drawText("MinimoJS", 10, 10, 16);
|
|
59
|
-
};
|
|
90
|
+
Note: text defaults to `monospace`, so a game that names no font downloads none. To use a web font, declare it in your HTML, register it with `game.requireFont(...)`, and set it as a `TextSprite`'s `fontFamily`.
|
|
60
91
|
|
|
61
|
-
game.
|
|
62
|
-
```
|
|
92
|
+
Load images in `game.onPreload`, once, at boot. Only a game with many scenes that each carry many images of their own, such as one with many levels, should give scenes an `onPreload()` so each loads its images when the player reaches it; see `examples/level-loading/`.
|
|
63
93
|
|
|
64
|
-
|
|
94
|
+
Sounds come from files too. Declare them as `sound` entries in an asset file, or load them with `game.loadSound(key, src)`, and play them with `game.playSound(key)`, which returns a playback you can `pause()`, `resume()`, and `stop()`; pass `{ loop: true }` for music. MP3 plays in every browser. `game.sound` and `game.soundSequence` still synthesize tones with no file at all; see `examples/sound-files/`.
|
|
65
95
|
|
|
66
96
|
## Examples
|
|
67
97
|
|
|
@@ -70,6 +100,14 @@ Note: `drawText()` uses `"Press Start 2P", monospace`. Load the font in your HTM
|
|
|
70
100
|
- `examples/space-invader/`
|
|
71
101
|
- `examples/super-minimo-bros/`
|
|
72
102
|
- `examples/scale-shift/`
|
|
103
|
+
- `examples/background-desert/`
|
|
104
|
+
- `examples/scene-lab/`
|
|
105
|
+
- `examples/image-sprite-sad-plush/`
|
|
106
|
+
- `examples/draw-sprite/`
|
|
107
|
+
- `examples/pseudo-3d-racer/`
|
|
108
|
+
- `examples/animations/`
|
|
109
|
+
- `examples/level-loading/`
|
|
110
|
+
- `examples/sound-files/`
|
|
73
111
|
|
|
74
112
|
Run locally from the `minimojs` directory:
|
|
75
113
|
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
export class AnimationSystem {
|
|
3
|
+
constructor() {
|
|
4
|
+
/** @internal */
|
|
5
|
+
this._animations = [];
|
|
6
|
+
/** @internal */
|
|
7
|
+
this._deformAnimations = [];
|
|
8
|
+
/** @internal */
|
|
9
|
+
this._motionAnimations = [];
|
|
10
|
+
/** @internal */
|
|
11
|
+
this._shakeAnimations = [];
|
|
12
|
+
/** @internal */
|
|
13
|
+
this._blinkAnimations = [];
|
|
14
|
+
/** @internal */
|
|
15
|
+
this._flickerAnimations = [];
|
|
16
|
+
/** @internal */
|
|
17
|
+
this._renderDataSprites = new Set();
|
|
18
|
+
}
|
|
19
|
+
update(dtMs) {
|
|
20
|
+
const animations = this._animations;
|
|
21
|
+
const toRemove = [];
|
|
22
|
+
for (let i = 0; i < animations.length; i++) {
|
|
23
|
+
const anim = animations[i];
|
|
24
|
+
anim.elapsed += dtMs;
|
|
25
|
+
const t = Math.min(anim.elapsed / anim.durationMs, 1);
|
|
26
|
+
anim.sprite[anim.property] = anim.from + (anim.to - anim.from) * t;
|
|
27
|
+
if (t >= 1) {
|
|
28
|
+
toRemove.push(i);
|
|
29
|
+
anim.onComplete?.();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
for (let i = toRemove.length - 1; i >= 0; i--) {
|
|
33
|
+
animations.splice(toRemove[i], 1);
|
|
34
|
+
}
|
|
35
|
+
const deformAnimations = this._deformAnimations;
|
|
36
|
+
const deformToRemove = [];
|
|
37
|
+
for (let i = 0; i < deformAnimations.length; i++) {
|
|
38
|
+
const anim = deformAnimations[i];
|
|
39
|
+
anim.elapsed += dtMs;
|
|
40
|
+
const t = Math.min(anim.elapsed / anim.durationMs, 1);
|
|
41
|
+
if (t >= 1) {
|
|
42
|
+
deformToRemove.push(i);
|
|
43
|
+
anim.onComplete?.();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
for (let i = deformToRemove.length - 1; i >= 0; i--) {
|
|
47
|
+
deformAnimations.splice(deformToRemove[i], 1);
|
|
48
|
+
}
|
|
49
|
+
const motionAnimations = this._motionAnimations;
|
|
50
|
+
const motionToRemove = [];
|
|
51
|
+
for (let i = 0; i < motionAnimations.length; i++) {
|
|
52
|
+
const anim = motionAnimations[i];
|
|
53
|
+
anim.elapsed += dtMs;
|
|
54
|
+
const t = Math.min(anim.elapsed / anim.durationMs, 1);
|
|
55
|
+
if (anim.kind === "arc") {
|
|
56
|
+
anim.sprite.x = anim.fromX + (anim.toX - anim.fromX) * t;
|
|
57
|
+
anim.sprite.y =
|
|
58
|
+
anim.fromY +
|
|
59
|
+
(anim.toY - anim.fromY) * t -
|
|
60
|
+
anim.magnitude * 4 * t * (1 - t);
|
|
61
|
+
}
|
|
62
|
+
else if (anim.kind === "bounce") {
|
|
63
|
+
anim.sprite.x = anim.fromX;
|
|
64
|
+
anim.sprite.y = anim.fromY - anim.magnitude * 4 * t * (1 - t);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
anim.sprite.x = anim.fromX;
|
|
68
|
+
anim.sprite.y = anim.fromY - Math.sin(t * Math.PI) * anim.magnitude;
|
|
69
|
+
}
|
|
70
|
+
if (t >= 1) {
|
|
71
|
+
motionToRemove.push(i);
|
|
72
|
+
anim.onComplete?.();
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (let i = motionToRemove.length - 1; i >= 0; i--) {
|
|
76
|
+
motionAnimations.splice(motionToRemove[i], 1);
|
|
77
|
+
}
|
|
78
|
+
this.rebuildRenderData(dtMs);
|
|
79
|
+
}
|
|
80
|
+
animateAlpha(sprite, to, durationMs, onComplete) {
|
|
81
|
+
const animations = this._animations.filter((a) => !(a.sprite === sprite && a.property === "alpha"));
|
|
82
|
+
animations.push({
|
|
83
|
+
sprite,
|
|
84
|
+
property: "alpha",
|
|
85
|
+
from: sprite.alpha,
|
|
86
|
+
to,
|
|
87
|
+
durationMs,
|
|
88
|
+
elapsed: 0,
|
|
89
|
+
onComplete,
|
|
90
|
+
});
|
|
91
|
+
this._animations = animations;
|
|
92
|
+
}
|
|
93
|
+
animateRotation(sprite, to, durationMs, onComplete) {
|
|
94
|
+
const animations = this._animations.filter((a) => !(a.sprite === sprite && a.property === "rotation"));
|
|
95
|
+
animations.push({
|
|
96
|
+
sprite,
|
|
97
|
+
property: "rotation",
|
|
98
|
+
from: sprite.rotation,
|
|
99
|
+
to,
|
|
100
|
+
durationMs,
|
|
101
|
+
elapsed: 0,
|
|
102
|
+
onComplete,
|
|
103
|
+
});
|
|
104
|
+
this._animations = animations;
|
|
105
|
+
}
|
|
106
|
+
animateDeform(sprite, toScaleX, toScaleY, pivotX, pivotY, durationMs, onComplete) {
|
|
107
|
+
const current = sprite._renderData;
|
|
108
|
+
const fromScaleX = current?.scaleX ?? 1;
|
|
109
|
+
const fromScaleY = current?.scaleY ?? 1;
|
|
110
|
+
const deformAnimations = this._deformAnimations.filter((a) => a.sprite !== sprite);
|
|
111
|
+
deformAnimations.push({
|
|
112
|
+
sprite,
|
|
113
|
+
fromScaleX,
|
|
114
|
+
fromScaleY,
|
|
115
|
+
toScaleX: this.sanitizeScale(toScaleX),
|
|
116
|
+
toScaleY: this.sanitizeScale(toScaleY),
|
|
117
|
+
pivotX: this.sanitizePivot(pivotX),
|
|
118
|
+
pivotY: this.sanitizePivot(pivotY),
|
|
119
|
+
durationMs,
|
|
120
|
+
elapsed: 0,
|
|
121
|
+
onComplete,
|
|
122
|
+
});
|
|
123
|
+
this._deformAnimations = deformAnimations;
|
|
124
|
+
}
|
|
125
|
+
animateShake(sprite, intensity, durationMs, onComplete) {
|
|
126
|
+
const animations = this._shakeAnimations.filter((a) => a.sprite !== sprite);
|
|
127
|
+
animations.push({
|
|
128
|
+
sprite,
|
|
129
|
+
intensity: this.sanitizeNonNegative(intensity),
|
|
130
|
+
durationMs: this.sanitizeDuration(durationMs),
|
|
131
|
+
elapsed: 0,
|
|
132
|
+
onComplete,
|
|
133
|
+
});
|
|
134
|
+
this._shakeAnimations = animations;
|
|
135
|
+
}
|
|
136
|
+
animateBounce(sprite, height, durationMs, onComplete) {
|
|
137
|
+
this.replaceMotionAnimation({
|
|
138
|
+
sprite,
|
|
139
|
+
kind: "bounce",
|
|
140
|
+
fromX: sprite.x,
|
|
141
|
+
fromY: sprite.y,
|
|
142
|
+
toX: sprite.x,
|
|
143
|
+
toY: sprite.y,
|
|
144
|
+
magnitude: this.sanitizeNonNegative(height),
|
|
145
|
+
durationMs: this.sanitizeDuration(durationMs),
|
|
146
|
+
elapsed: 0,
|
|
147
|
+
onComplete,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
animateFloat(sprite, distance, durationMs, onComplete) {
|
|
151
|
+
this.replaceMotionAnimation({
|
|
152
|
+
sprite,
|
|
153
|
+
kind: "float",
|
|
154
|
+
fromX: sprite.x,
|
|
155
|
+
fromY: sprite.y,
|
|
156
|
+
toX: sprite.x,
|
|
157
|
+
toY: sprite.y,
|
|
158
|
+
magnitude: this.sanitizeNonNegative(distance),
|
|
159
|
+
durationMs: this.sanitizeDuration(durationMs),
|
|
160
|
+
elapsed: 0,
|
|
161
|
+
onComplete,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
animateArc(sprite, toX, toY, arcHeight, durationMs, onComplete) {
|
|
165
|
+
this.replaceMotionAnimation({
|
|
166
|
+
sprite,
|
|
167
|
+
kind: "arc",
|
|
168
|
+
fromX: sprite.x,
|
|
169
|
+
fromY: sprite.y,
|
|
170
|
+
toX,
|
|
171
|
+
toY,
|
|
172
|
+
magnitude: this.sanitizeNonNegative(arcHeight),
|
|
173
|
+
durationMs: this.sanitizeDuration(durationMs),
|
|
174
|
+
elapsed: 0,
|
|
175
|
+
onComplete,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
animateBlink(sprite, times, durationMs, onComplete) {
|
|
179
|
+
const animations = this._blinkAnimations.filter((a) => a.sprite !== sprite);
|
|
180
|
+
animations.push({
|
|
181
|
+
sprite,
|
|
182
|
+
times: Math.max(1, Math.round(Number.isFinite(times) ? times : 1)),
|
|
183
|
+
durationMs: this.sanitizeDuration(durationMs),
|
|
184
|
+
elapsed: 0,
|
|
185
|
+
onComplete,
|
|
186
|
+
});
|
|
187
|
+
this._blinkAnimations = animations;
|
|
188
|
+
}
|
|
189
|
+
animateFlicker(sprite, durationMs, onComplete) {
|
|
190
|
+
const animations = this._flickerAnimations.filter((a) => a.sprite !== sprite);
|
|
191
|
+
animations.push({
|
|
192
|
+
sprite,
|
|
193
|
+
durationMs: this.sanitizeDuration(durationMs),
|
|
194
|
+
elapsed: 0,
|
|
195
|
+
onComplete,
|
|
196
|
+
});
|
|
197
|
+
this._flickerAnimations = animations;
|
|
198
|
+
}
|
|
199
|
+
clearSpriteAnimations(sprite) {
|
|
200
|
+
this._animations = this._animations.filter((a) => a.sprite !== sprite);
|
|
201
|
+
this._deformAnimations = this._deformAnimations.filter((a) => a.sprite !== sprite);
|
|
202
|
+
this._motionAnimations = this._motionAnimations.filter((a) => a.sprite !== sprite);
|
|
203
|
+
this._shakeAnimations = this._shakeAnimations.filter((a) => a.sprite !== sprite);
|
|
204
|
+
this._blinkAnimations = this._blinkAnimations.filter((a) => a.sprite !== sprite);
|
|
205
|
+
this._flickerAnimations = this._flickerAnimations.filter((a) => a.sprite !== sprite);
|
|
206
|
+
this._renderDataSprites.delete(sprite);
|
|
207
|
+
sprite._renderData = null;
|
|
208
|
+
}
|
|
209
|
+
clearAll() {
|
|
210
|
+
for (const sprite of this._renderDataSprites) {
|
|
211
|
+
sprite._renderData = null;
|
|
212
|
+
}
|
|
213
|
+
this._animations = [];
|
|
214
|
+
this._deformAnimations = [];
|
|
215
|
+
this._motionAnimations = [];
|
|
216
|
+
this._shakeAnimations = [];
|
|
217
|
+
this._blinkAnimations = [];
|
|
218
|
+
this._flickerAnimations = [];
|
|
219
|
+
this._renderDataSprites.clear();
|
|
220
|
+
}
|
|
221
|
+
/** @internal */
|
|
222
|
+
sanitizeScale(value) {
|
|
223
|
+
if (!Number.isFinite(value))
|
|
224
|
+
return 1;
|
|
225
|
+
return Math.max(0, value);
|
|
226
|
+
}
|
|
227
|
+
/** @internal */
|
|
228
|
+
sanitizeDuration(value) {
|
|
229
|
+
if (!Number.isFinite(value))
|
|
230
|
+
return 1;
|
|
231
|
+
return Math.max(1, value);
|
|
232
|
+
}
|
|
233
|
+
/** @internal */
|
|
234
|
+
sanitizeNonNegative(value) {
|
|
235
|
+
if (!Number.isFinite(value))
|
|
236
|
+
return 0;
|
|
237
|
+
return Math.max(0, value);
|
|
238
|
+
}
|
|
239
|
+
/** @internal */
|
|
240
|
+
sanitizePivot(value) {
|
|
241
|
+
if (!Number.isFinite(value))
|
|
242
|
+
return 0.5;
|
|
243
|
+
return Math.max(0, Math.min(1, value));
|
|
244
|
+
}
|
|
245
|
+
/** @internal */
|
|
246
|
+
replaceMotionAnimation(entry) {
|
|
247
|
+
const animations = this._motionAnimations.filter((a) => a.sprite !== entry.sprite);
|
|
248
|
+
animations.push(entry);
|
|
249
|
+
this._motionAnimations = animations;
|
|
250
|
+
}
|
|
251
|
+
/** @internal */
|
|
252
|
+
rebuildRenderData(dtMs) {
|
|
253
|
+
const next = new Map();
|
|
254
|
+
const nextSprites = new Set();
|
|
255
|
+
const upsert = (sprite) => {
|
|
256
|
+
let state = next.get(sprite);
|
|
257
|
+
if (!state) {
|
|
258
|
+
state = {
|
|
259
|
+
scaleX: 1,
|
|
260
|
+
scaleY: 1,
|
|
261
|
+
pivotX: 0.5,
|
|
262
|
+
pivotY: 0.5,
|
|
263
|
+
offsetX: 0,
|
|
264
|
+
offsetY: 0,
|
|
265
|
+
alphaMultiplier: 1,
|
|
266
|
+
};
|
|
267
|
+
next.set(sprite, state);
|
|
268
|
+
nextSprites.add(sprite);
|
|
269
|
+
}
|
|
270
|
+
return state;
|
|
271
|
+
};
|
|
272
|
+
for (const anim of this._deformAnimations) {
|
|
273
|
+
const state = upsert(anim.sprite);
|
|
274
|
+
const t = Math.min(anim.elapsed / anim.durationMs, 1);
|
|
275
|
+
state.scaleX = anim.fromScaleX + (anim.toScaleX - anim.fromScaleX) * t;
|
|
276
|
+
state.scaleY = anim.fromScaleY + (anim.toScaleY - anim.fromScaleY) * t;
|
|
277
|
+
state.pivotX = anim.pivotX;
|
|
278
|
+
state.pivotY = anim.pivotY;
|
|
279
|
+
}
|
|
280
|
+
const shakeToRemove = [];
|
|
281
|
+
for (let i = 0; i < this._shakeAnimations.length; i++) {
|
|
282
|
+
const anim = this._shakeAnimations[i];
|
|
283
|
+
anim.elapsed += dtMs;
|
|
284
|
+
const t = Math.min(anim.elapsed / anim.durationMs, 1);
|
|
285
|
+
const decay = 1 - t;
|
|
286
|
+
const state = upsert(anim.sprite);
|
|
287
|
+
state.offsetX += (Math.random() * 2 - 1) * anim.intensity * decay;
|
|
288
|
+
state.offsetY += (Math.random() * 2 - 1) * anim.intensity * decay;
|
|
289
|
+
if (t >= 1) {
|
|
290
|
+
shakeToRemove.push(i);
|
|
291
|
+
anim.onComplete?.();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
for (let i = shakeToRemove.length - 1; i >= 0; i--) {
|
|
295
|
+
this._shakeAnimations.splice(shakeToRemove[i], 1);
|
|
296
|
+
}
|
|
297
|
+
const blinkToRemove = [];
|
|
298
|
+
for (let i = 0; i < this._blinkAnimations.length; i++) {
|
|
299
|
+
const anim = this._blinkAnimations[i];
|
|
300
|
+
anim.elapsed += dtMs;
|
|
301
|
+
const t = Math.min(anim.elapsed / anim.durationMs, 1);
|
|
302
|
+
const state = upsert(anim.sprite);
|
|
303
|
+
const step = Math.floor(t * anim.times * 2);
|
|
304
|
+
state.alphaMultiplier *= step % 2 === 0 ? 1 : 0;
|
|
305
|
+
if (t >= 1) {
|
|
306
|
+
blinkToRemove.push(i);
|
|
307
|
+
anim.onComplete?.();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
for (let i = blinkToRemove.length - 1; i >= 0; i--) {
|
|
311
|
+
this._blinkAnimations.splice(blinkToRemove[i], 1);
|
|
312
|
+
}
|
|
313
|
+
const flickerToRemove = [];
|
|
314
|
+
for (let i = 0; i < this._flickerAnimations.length; i++) {
|
|
315
|
+
const anim = this._flickerAnimations[i];
|
|
316
|
+
anim.elapsed += dtMs;
|
|
317
|
+
const t = Math.min(anim.elapsed / anim.durationMs, 1);
|
|
318
|
+
const state = upsert(anim.sprite);
|
|
319
|
+
state.alphaMultiplier *= 0.25 + Math.random() * 0.75;
|
|
320
|
+
if (t >= 1) {
|
|
321
|
+
flickerToRemove.push(i);
|
|
322
|
+
anim.onComplete?.();
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
for (let i = flickerToRemove.length - 1; i >= 0; i--) {
|
|
326
|
+
this._flickerAnimations.splice(flickerToRemove[i], 1);
|
|
327
|
+
}
|
|
328
|
+
for (const sprite of this._renderDataSprites) {
|
|
329
|
+
if (!nextSprites.has(sprite)) {
|
|
330
|
+
sprite._renderData = null;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
for (const [sprite, state] of next.entries()) {
|
|
334
|
+
const isDefault = state.scaleX === 1 &&
|
|
335
|
+
state.scaleY === 1 &&
|
|
336
|
+
state.pivotX === 0.5 &&
|
|
337
|
+
state.pivotY === 0.5 &&
|
|
338
|
+
state.offsetX === 0 &&
|
|
339
|
+
state.offsetY === 0 &&
|
|
340
|
+
state.alphaMultiplier === 1;
|
|
341
|
+
sprite._renderData = isDefault ? null : state;
|
|
342
|
+
}
|
|
343
|
+
this._renderDataSprites = nextSprites;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches and decodes one audio file. The engine passes one backed by its
|
|
3
|
+
* `AudioContext`; the asset system only stores what it returns, so it needs no
|
|
4
|
+
* audio API of its own and runs in tests without a browser.
|
|
5
|
+
*/
|
|
6
|
+
export type SoundLoader = (src: string) => Promise<unknown>;
|