minimojs 1.0.0-alpha.41 → 1.0.0-alpha.43
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/AssetSystem.d.ts +2 -0
- package/dist/internal/AssetSystem.js +145 -5
- package/dist/internal/DesignNamingCheck.js +7 -1
- package/dist/internal/RenderSystem.js +28 -5
- package/dist/minimo-format.d.ts +225 -14
- package/dist/minimo-format.js +261 -8
- package/dist/minimo-leaderboard.d.ts +3 -3
- package/dist/minimo-leaderboard.js +3 -3
- package/dist/minimo-multiplayer.d.ts +8 -8
- package/dist/minimo-multiplayer.js +9 -9
- package/dist/minimo-outline.d.ts +106 -0
- package/dist/minimo-outline.js +299 -0
- package/dist/minimo.d.ts +13 -2
- package/dist/minimo.js +64 -4
- package/package.json +1 -1
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import { checkFramesFitImage, frameKey, parseTextureAtlas, spritesheetFrames, } from "../minimo-format.js";
|
|
2
|
+
async function fetchJson(src) {
|
|
3
|
+
const response = await fetch(src);
|
|
4
|
+
if (!response.ok) {
|
|
5
|
+
throw new Error(`MinimoJS: Failed to load "${src}" (${response.status}).`);
|
|
6
|
+
}
|
|
7
|
+
return response.json();
|
|
8
|
+
}
|
|
1
9
|
/**
|
|
2
10
|
* Images and sounds, loaded by key. Both kinds share one queue, one progress
|
|
3
11
|
* count, one failure report, and one namespace of keys, so a game's loading
|
|
@@ -6,8 +14,9 @@
|
|
|
6
14
|
* @internal
|
|
7
15
|
*/
|
|
8
16
|
export class AssetSystem {
|
|
9
|
-
constructor(_soundLoader = null) {
|
|
17
|
+
constructor(_soundLoader = null, _dataLoader = fetchJson) {
|
|
10
18
|
this._soundLoader = _soundLoader;
|
|
19
|
+
this._dataLoader = _dataLoader;
|
|
11
20
|
/** @internal */
|
|
12
21
|
this._queuedImages = new Map();
|
|
13
22
|
/** @internal */
|
|
@@ -32,6 +41,16 @@ export class AssetSystem {
|
|
|
32
41
|
this._loadedSounds = new Map();
|
|
33
42
|
/** @internal */
|
|
34
43
|
this._soundSources = new Map();
|
|
44
|
+
/** Spritesheets and atlases, by key. @internal */
|
|
45
|
+
this._sheets = new Map();
|
|
46
|
+
/** Each atlas's JSON source, by the atlas's key. @internal */
|
|
47
|
+
this._atlasSources = new Map();
|
|
48
|
+
/** @internal */
|
|
49
|
+
this._queuedAtlases = new Map();
|
|
50
|
+
/** @internal */
|
|
51
|
+
this._loadedAtlases = new Map();
|
|
52
|
+
/** The sheet that made each frame key. @internal */
|
|
53
|
+
this._frameOwners = new Map();
|
|
35
54
|
}
|
|
36
55
|
queueImage(key, src) {
|
|
37
56
|
const safeKey = key.trim();
|
|
@@ -73,6 +92,10 @@ export class AssetSystem {
|
|
|
73
92
|
registerAssetFile(file, source) {
|
|
74
93
|
for (const entry of file.assets) {
|
|
75
94
|
const owner = this._assetFileByKey.get(entry.key);
|
|
95
|
+
const sheet = this._frameOwners.get(entry.key);
|
|
96
|
+
if (sheet !== undefined) {
|
|
97
|
+
throw new Error(`MinimoJS: Asset key "${entry.key}" is a frame of "${sheet}". Keys must be unique in the whole game.`);
|
|
98
|
+
}
|
|
76
99
|
if (owner === undefined) {
|
|
77
100
|
if (this._imageSources.has(entry.key) || this._loadedImages.has(entry.key)) {
|
|
78
101
|
throw new Error(`MinimoJS: Asset key "${entry.key}" is already registered with loadImage() or createTexture(). Keys must be unique in the whole game.`);
|
|
@@ -93,6 +116,25 @@ export class AssetSystem {
|
|
|
93
116
|
else if (entry.type === "sound") {
|
|
94
117
|
this.enqueueSound(entry.key, entry.src.trim());
|
|
95
118
|
}
|
|
119
|
+
else if (entry.type === "spritesheet" || entry.type === "atlas") {
|
|
120
|
+
const { src, frameWidth, frameHeight, data } = entry;
|
|
121
|
+
if (!this._sheets.has(entry.key)) {
|
|
122
|
+
this._sheets.set(entry.key, {
|
|
123
|
+
type: entry.type,
|
|
124
|
+
frameWidth: frameWidth ?? 0,
|
|
125
|
+
frameHeight: frameHeight ?? 0,
|
|
126
|
+
built: false,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
this.enqueueImage(entry.key, src.trim());
|
|
130
|
+
if (data !== undefined) {
|
|
131
|
+
const safeData = data.trim();
|
|
132
|
+
this._atlasSources.set(entry.key, safeData);
|
|
133
|
+
if (!this._loadedAtlases.has(entry.key)) {
|
|
134
|
+
this._queuedAtlases.set(entry.key, safeData);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
96
138
|
}
|
|
97
139
|
const isFirstRegistration = !this._registeredAssetFiles.has(source);
|
|
98
140
|
this._registeredAssetFiles.add(source);
|
|
@@ -159,9 +201,13 @@ export class AssetSystem {
|
|
|
159
201
|
hasSound(key) {
|
|
160
202
|
return this._loadedSounds.has(key);
|
|
161
203
|
}
|
|
162
|
-
/**
|
|
204
|
+
/** The key of the spritesheet or atlas whose frame `key` is, if it is one. */
|
|
205
|
+
getFrameOwner(key) {
|
|
206
|
+
return this._frameOwners.get(key);
|
|
207
|
+
}
|
|
208
|
+
/** Counts the queued images, sounds, and atlas files that are not loaded yet. */
|
|
163
209
|
getQueuedAssetCount() {
|
|
164
|
-
let total =
|
|
210
|
+
let total = this._queuedAtlases.size;
|
|
165
211
|
for (const key of this._queuedImages.keys()) {
|
|
166
212
|
if (!this._loadedImages.has(key)) {
|
|
167
213
|
total++;
|
|
@@ -184,13 +230,15 @@ export class AssetSystem {
|
|
|
184
230
|
async loadQueuedAssets(onProgress) {
|
|
185
231
|
const images = [...this._queuedImages.entries()].filter(([key]) => !this._loadedImages.has(key));
|
|
186
232
|
const sounds = [...this._queuedSounds.entries()].filter(([key]) => !this._loadedSounds.has(key));
|
|
187
|
-
const
|
|
233
|
+
const atlases = [...this._queuedAtlases.entries()];
|
|
234
|
+
const total = images.length + sounds.length + atlases.length;
|
|
188
235
|
let settled = 0;
|
|
189
236
|
const failed = [];
|
|
190
237
|
onProgress?.(0, total, null);
|
|
191
238
|
if (total === 0) {
|
|
192
239
|
this._queuedImages.clear();
|
|
193
240
|
this._queuedSounds.clear();
|
|
241
|
+
this.buildSheets(failed);
|
|
194
242
|
return failed;
|
|
195
243
|
}
|
|
196
244
|
const settle = (key) => {
|
|
@@ -223,13 +271,94 @@ export class AssetSystem {
|
|
|
223
271
|
}
|
|
224
272
|
settle(key);
|
|
225
273
|
}),
|
|
274
|
+
...atlases.map(async ([key, src]) => {
|
|
275
|
+
try {
|
|
276
|
+
this._loadedAtlases.set(key, parseTextureAtlas(await this._dataLoader(src)));
|
|
277
|
+
this._queuedAtlases.delete(key);
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
console.error(`MinimoJS: The atlas "${key}" could not be read from "${src}".`, error);
|
|
281
|
+
failed.push(key);
|
|
282
|
+
}
|
|
283
|
+
settle(key);
|
|
284
|
+
}),
|
|
226
285
|
]);
|
|
227
286
|
for (const key of [...this._queuedImages.keys()]) {
|
|
228
287
|
if (this._loadedImages.has(key)) {
|
|
229
288
|
this._queuedImages.delete(key);
|
|
230
289
|
}
|
|
231
290
|
}
|
|
232
|
-
|
|
291
|
+
this.buildSheets(failed);
|
|
292
|
+
// An atlas whose image and JSON both failed is one failure, under its key.
|
|
293
|
+
return [...new Set(failed)];
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Makes the frames of every spritesheet and atlas whose image, and atlas
|
|
297
|
+
* file, are loaded and whose frames are not made yet. A sheet whose frames
|
|
298
|
+
* cannot be made is added to `failed` once, with the reason on the console.
|
|
299
|
+
*
|
|
300
|
+
* @internal
|
|
301
|
+
*/
|
|
302
|
+
buildSheets(failed) {
|
|
303
|
+
for (const [key, sheet] of this._sheets) {
|
|
304
|
+
if (sheet.built)
|
|
305
|
+
continue;
|
|
306
|
+
const image = this._loadedImages.get(key);
|
|
307
|
+
const atlas = this._loadedAtlases.get(key);
|
|
308
|
+
if (!image || (sheet.type === "atlas" && !atlas))
|
|
309
|
+
continue;
|
|
310
|
+
try {
|
|
311
|
+
const width = imageWidth(image);
|
|
312
|
+
const height = imageHeight(image);
|
|
313
|
+
const frames = sheet.type === "atlas"
|
|
314
|
+
? atlas.frames
|
|
315
|
+
: spritesheetFrames({ key, frameWidth: sheet.frameWidth, frameHeight: sheet.frameHeight }, width, height);
|
|
316
|
+
checkFramesFitImage(key, frames, width, height);
|
|
317
|
+
for (const frame of frames) {
|
|
318
|
+
const name = frameKey(key, frame.name);
|
|
319
|
+
if (this.isKeyTaken(name)) {
|
|
320
|
+
throw new Error(`MinimoJS: The frame "${frame.name}" of "${key}" would be the key "${name}", which is already in use. Keys must be unique in the whole game.`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
for (const frame of frames) {
|
|
324
|
+
const name = frameKey(key, frame.name);
|
|
325
|
+
this._loadedImages.set(name, this.cutFrame(image, frame));
|
|
326
|
+
this._frameOwners.set(name, key);
|
|
327
|
+
this.bumpImageVersion(name);
|
|
328
|
+
}
|
|
329
|
+
sheet.built = true;
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
console.error(error instanceof Error ? error.message : error);
|
|
333
|
+
if (!failed.includes(key))
|
|
334
|
+
failed.push(key);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/** @internal */
|
|
339
|
+
isKeyTaken(key) {
|
|
340
|
+
return (this._loadedImages.has(key) ||
|
|
341
|
+
this._imageSources.has(key) ||
|
|
342
|
+
this._soundSources.has(key) ||
|
|
343
|
+
this._assetFileByKey.has(key) ||
|
|
344
|
+
this._frameOwners.has(key));
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Copies one frame into a texture of its untrimmed size, at the place the
|
|
348
|
+
* trimmed rectangle had in it.
|
|
349
|
+
*
|
|
350
|
+
* @internal
|
|
351
|
+
*/
|
|
352
|
+
cutFrame(image, frame) {
|
|
353
|
+
const canvas = document.createElement("canvas");
|
|
354
|
+
canvas.width = frame.sourceWidth;
|
|
355
|
+
canvas.height = frame.sourceHeight;
|
|
356
|
+
const ctx = canvas.getContext("2d");
|
|
357
|
+
if (!ctx) {
|
|
358
|
+
throw new Error("MinimoJS: Could not acquire a texture rendering context.");
|
|
359
|
+
}
|
|
360
|
+
ctx.drawImage(image, frame.x, frame.y, frame.width, frame.height, frame.offsetX, frame.offsetY, frame.width, frame.height);
|
|
361
|
+
return canvas;
|
|
233
362
|
}
|
|
234
363
|
/** Returns the source registered for an image or sound key, if any. */
|
|
235
364
|
getAssetSource(key) {
|
|
@@ -242,6 +371,7 @@ export class AssetSystem {
|
|
|
242
371
|
discardQueuedAssets() {
|
|
243
372
|
this._queuedImages.clear();
|
|
244
373
|
this._queuedSounds.clear();
|
|
374
|
+
this._queuedAtlases.clear();
|
|
245
375
|
}
|
|
246
376
|
/** @internal */
|
|
247
377
|
refuseSoundKey(safeKey, caller) {
|
|
@@ -251,6 +381,10 @@ export class AssetSystem {
|
|
|
251
381
|
}
|
|
252
382
|
/** @internal */
|
|
253
383
|
refuseAssetFileKey(safeKey, caller) {
|
|
384
|
+
const sheet = this._frameOwners.get(safeKey);
|
|
385
|
+
if (sheet !== undefined) {
|
|
386
|
+
throw new Error(`MinimoJS: ${caller} cannot use the key "${safeKey}", which is a frame of "${sheet}". Keys must be unique in the whole game.`);
|
|
387
|
+
}
|
|
254
388
|
if (this._assetFileByKey.has(safeKey)) {
|
|
255
389
|
throw new Error(`MinimoJS: ${caller} cannot use the key "${safeKey}", which an asset file declares. Keys must be unique in the whole game.`);
|
|
256
390
|
}
|
|
@@ -279,3 +413,9 @@ export class AssetSystem {
|
|
|
279
413
|
this._imageVersions.set(key, (this._imageVersions.get(key) ?? 0) + 1);
|
|
280
414
|
}
|
|
281
415
|
}
|
|
416
|
+
function imageWidth(image) {
|
|
417
|
+
return image.naturalWidth || image.width;
|
|
418
|
+
}
|
|
419
|
+
function imageHeight(image) {
|
|
420
|
+
return image.naturalHeight || image.height;
|
|
421
|
+
}
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
// object, except `id` and `type`, exists on its runtime class under the same
|
|
3
3
|
// name and with a compatible type (X-002). Designs use the runtime's names so
|
|
4
4
|
// agents learn one vocabulary for code and files. The build fails when a
|
|
5
|
-
// property is misnamed; a property that must be named differently
|
|
5
|
+
// property is misnamed; a property that must be named differently is
|
|
6
6
|
// declared here as an explicit exception.
|
|
7
|
+
//
|
|
8
|
+
// The one exception is a decoration's properties, `DesignOutline`'s
|
|
9
|
+
// `outlineWidth` and `outlineColor` (X-023). They belong to the optional
|
|
10
|
+
// `outline` module, not to the sprite, which the core never gives an outline
|
|
11
|
+
// of its own; in code they are `outlines.set(sprite, { width, color })`, and
|
|
12
|
+
// the prefix says which module reads them in a file.
|
|
7
13
|
export {};
|
|
@@ -478,12 +478,26 @@ export class RenderSystem {
|
|
|
478
478
|
return this.getDrawSurface(sprite);
|
|
479
479
|
}
|
|
480
480
|
const cacheKey = sprite.getRenderCacheKey();
|
|
481
|
-
const
|
|
482
|
-
if (
|
|
483
|
-
this.
|
|
484
|
-
|
|
481
|
+
const effect = sprite._surfaceEffect;
|
|
482
|
+
if (effect === null)
|
|
483
|
+
return this.getBaseSurface(sprite, cacheKey);
|
|
484
|
+
// The effect's result shares the bounded cache with the base surfaces,
|
|
485
|
+
// under a key of its own, so the base stays as it was for every other
|
|
486
|
+
// sprite that looks the same.
|
|
487
|
+
const effectKey = `${cacheKey}\u0000effect:${effect.key}`;
|
|
488
|
+
const cached = this.takeCachedSurface(effectKey);
|
|
489
|
+
if (cached)
|
|
490
|
+
return cached;
|
|
491
|
+
const surface = effect.apply(this.getBaseSurface(sprite, cacheKey));
|
|
492
|
+
this._surfaceCache.set(effectKey, surface);
|
|
493
|
+
this.trimSurfaceCache();
|
|
494
|
+
return surface;
|
|
495
|
+
}
|
|
496
|
+
/** @internal The sprite's own surface, before any effect. */
|
|
497
|
+
getBaseSurface(sprite, cacheKey) {
|
|
498
|
+
const cached = this.takeCachedSurface(cacheKey);
|
|
499
|
+
if (cached)
|
|
485
500
|
return cached;
|
|
486
|
-
}
|
|
487
501
|
const surface = this.isTextSprite(sprite)
|
|
488
502
|
? this.createTextSurface(sprite)
|
|
489
503
|
: this.isImageSprite(sprite)
|
|
@@ -493,6 +507,15 @@ export class RenderSystem {
|
|
|
493
507
|
this.trimSurfaceCache();
|
|
494
508
|
return surface;
|
|
495
509
|
}
|
|
510
|
+
/** @internal A cached surface, marked as the most recently used. */
|
|
511
|
+
takeCachedSurface(key) {
|
|
512
|
+
const cached = this._surfaceCache.get(key);
|
|
513
|
+
if (cached) {
|
|
514
|
+
this._surfaceCache.delete(key);
|
|
515
|
+
this._surfaceCache.set(key, cached);
|
|
516
|
+
}
|
|
517
|
+
return cached;
|
|
518
|
+
}
|
|
496
519
|
/** @internal */
|
|
497
520
|
trimSurfaceCache() {
|
|
498
521
|
while (this._surfaceCache.size > RenderSystem.MAX_SURFACE_CACHE_ENTRIES) {
|
package/dist/minimo-format.d.ts
CHANGED
|
@@ -51,6 +51,97 @@ export interface SoundAsset {
|
|
|
51
51
|
/** The audio file's URL or path, as `game.loadSound(key, src)` takes it. Non-empty. */
|
|
52
52
|
src: string;
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* A spritesheet declared in an asset file: one image cut into a grid of equal
|
|
56
|
+
* frames. The image loads under `key`, as an `image` entry's does, and each
|
|
57
|
+
* frame becomes an image of its own, keyed `<key>/<n>`, numbered from `0`
|
|
58
|
+
* left to right and top to bottom, such as `"frog/0"` and `"frog/1"`. A frame
|
|
59
|
+
* key works wherever an image key does: `ImageSprite`, backgrounds, designs,
|
|
60
|
+
* and `game.getImage`.
|
|
61
|
+
*
|
|
62
|
+
* The grid starts at the image's top-left corner, with no margin or spacing;
|
|
63
|
+
* pixels left over at the right or bottom edge belong to no frame.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```json
|
|
67
|
+
* { "key": "frog", "type": "spritesheet", "src": "assets/frog-frames.png", "frameWidth": 320, "frameHeight": 320 }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export interface SpritesheetAsset {
|
|
71
|
+
/** The key of the whole image, and the prefix of its frames' keys. Unique in the whole game. */
|
|
72
|
+
key: string;
|
|
73
|
+
/** Always `"spritesheet"`. */
|
|
74
|
+
type: "spritesheet";
|
|
75
|
+
/** The image's URL or path, as `game.loadImage(key, src)` takes it. Non-empty. */
|
|
76
|
+
src: string;
|
|
77
|
+
/** Width of every frame in pixels. A positive integer. */
|
|
78
|
+
frameWidth: number;
|
|
79
|
+
/** Height of every frame in pixels. A positive integer. */
|
|
80
|
+
frameHeight: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* A texture atlas declared in an asset file: one image holding named frames
|
|
84
|
+
* of any size, described by a JSON file in the JSON Hash format that
|
|
85
|
+
* TexturePacker (*JSON (Hash)*) and free-tex-packer (`JsonHash`) export; see
|
|
86
|
+
* {@link parseTextureAtlas} for what is read from it. The image loads under
|
|
87
|
+
* `key`, and each frame becomes an image of its own, keyed `<key>/<frame>`
|
|
88
|
+
* with the frame's name as the packer wrote it, such as `"pond/lotus"`.
|
|
89
|
+
*
|
|
90
|
+
* The image is the one `src` names; the JSON's own `meta.image`, the file
|
|
91
|
+
* name the packer gave the image, is ignored, so the image may be stored
|
|
92
|
+
* under any name.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```json
|
|
96
|
+
* { "key": "pond", "type": "atlas", "src": "assets/pond.png", "data": "assets/pond.json" }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export interface AtlasAsset {
|
|
100
|
+
/** The key of the whole image, and the prefix of its frames' keys. Unique in the whole game. */
|
|
101
|
+
key: string;
|
|
102
|
+
/** Always `"atlas"`. */
|
|
103
|
+
type: "atlas";
|
|
104
|
+
/** The image's URL or path, as `game.loadImage(key, src)` takes it. Non-empty. */
|
|
105
|
+
src: string;
|
|
106
|
+
/** The URL or path of the atlas JSON, in the JSON Hash format. Non-empty. */
|
|
107
|
+
data: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* One frame of a spritesheet or an atlas: where it is in the image, and the
|
|
111
|
+
* size of the picture it came from. A frame trimmed of its transparent border
|
|
112
|
+
* is drawn at `offsetX`, `offsetY` inside a texture of `sourceWidth` by
|
|
113
|
+
* `sourceHeight`, so a sprite of it is the size of the untrimmed picture and
|
|
114
|
+
* trimming never moves what it shows.
|
|
115
|
+
*/
|
|
116
|
+
export interface TextureFrame {
|
|
117
|
+
/** The frame's name, which follows the sheet's key in its image key: `<key>/<name>`. */
|
|
118
|
+
name: string;
|
|
119
|
+
/** Left edge of the frame in the image, in pixels. */
|
|
120
|
+
x: number;
|
|
121
|
+
/** Top edge of the frame in the image, in pixels. */
|
|
122
|
+
y: number;
|
|
123
|
+
/** Width of the frame in the image, in pixels. */
|
|
124
|
+
width: number;
|
|
125
|
+
/** Height of the frame in the image, in pixels. */
|
|
126
|
+
height: number;
|
|
127
|
+
/** Width of the picture before trimming, the texture's width. */
|
|
128
|
+
sourceWidth: number;
|
|
129
|
+
/** Height of the picture before trimming, the texture's height. */
|
|
130
|
+
sourceHeight: number;
|
|
131
|
+
/** Where the frame's left edge sits in the untrimmed picture. */
|
|
132
|
+
offsetX: number;
|
|
133
|
+
/** Where the frame's top edge sits in the untrimmed picture. */
|
|
134
|
+
offsetY: number;
|
|
135
|
+
}
|
|
136
|
+
/** A texture atlas, as {@link parseTextureAtlas} reads it from its JSON. */
|
|
137
|
+
export interface TextureAtlas {
|
|
138
|
+
/** The atlas's frames, in the JSON's order. */
|
|
139
|
+
frames: TextureFrame[];
|
|
140
|
+
/** The image size the JSON's `meta.size` states, or `null` when it states none. */
|
|
141
|
+
imageWidth: number | null;
|
|
142
|
+
/** The image size the JSON's `meta.size` states, or `null` when it states none. */
|
|
143
|
+
imageHeight: number | null;
|
|
144
|
+
}
|
|
54
145
|
/**
|
|
55
146
|
* An entry whose `type` this engine does not support, such as one written for
|
|
56
147
|
* a newer engine. It is kept as written, so a tool that saves the file does
|
|
@@ -80,7 +171,9 @@ export interface UnknownAsset {
|
|
|
80
171
|
* "assets": [
|
|
81
172
|
* { "key": "player", "type": "image", "src": "assets/player.png" },
|
|
82
173
|
* { "key": "bat", "type": "image", "src": "assets/bat.png" },
|
|
83
|
-
* { "key": "jump", "type": "sound", "src": "assets/jump.mp3" }
|
|
174
|
+
* { "key": "jump", "type": "sound", "src": "assets/jump.mp3" },
|
|
175
|
+
* { "key": "frog", "type": "spritesheet", "src": "assets/frog.png", "frameWidth": 64, "frameHeight": 64 },
|
|
176
|
+
* { "key": "pond", "type": "atlas", "src": "assets/pond.png", "data": "assets/pond.json" }
|
|
84
177
|
* ]
|
|
85
178
|
* }
|
|
86
179
|
* ```
|
|
@@ -91,8 +184,12 @@ export interface AssetFile {
|
|
|
91
184
|
/** The version of the file's shape. Version `1` is the only one so far. */
|
|
92
185
|
version: 1;
|
|
93
186
|
/** The declared assets, in file order. */
|
|
94
|
-
assets:
|
|
187
|
+
assets: AssetEntry[];
|
|
95
188
|
}
|
|
189
|
+
/** One entry of an asset file. */
|
|
190
|
+
export type AssetEntry = ImageAsset | SoundAsset | SpritesheetAsset | AtlasAsset | UnknownAsset;
|
|
191
|
+
/** The entry types this engine loads. */
|
|
192
|
+
export declare const ASSET_TYPES: readonly string[];
|
|
96
193
|
/**
|
|
97
194
|
* Validates the content of an asset file and returns a copy of it.
|
|
98
195
|
*
|
|
@@ -103,7 +200,9 @@ export interface AssetFile {
|
|
|
103
200
|
* - an entry without a string `key` and `type`;
|
|
104
201
|
* - an empty key, or one with leading or trailing spaces;
|
|
105
202
|
* - a key repeated in the file, whatever the types of its entries;
|
|
106
|
-
* - an `image` or `
|
|
203
|
+
* - an `image`, `sound`, `spritesheet`, or `atlas` entry without a non-empty string `src`;
|
|
204
|
+
* - a `spritesheet` whose `frameWidth` or `frameHeight` is not a positive integer;
|
|
205
|
+
* - an `atlas` without a non-empty string `data`.
|
|
107
206
|
*
|
|
108
207
|
* An entry of a type this engine does not support is kept unchanged, without
|
|
109
208
|
* checking its other fields, so nothing is lost when a tool saves the file.
|
|
@@ -117,6 +216,48 @@ export interface AssetFile {
|
|
|
117
216
|
* ```
|
|
118
217
|
*/
|
|
119
218
|
export declare function parseAssetFile(json: unknown): AssetFile;
|
|
219
|
+
/**
|
|
220
|
+
* The image key of a spritesheet's or an atlas's frame: the sheet's key, a
|
|
221
|
+
* slash, and the frame's name, such as `"frog/0"` or `"pond/lotus"`.
|
|
222
|
+
*/
|
|
223
|
+
export declare function frameKey(sheetKey: string, frameName: string): string;
|
|
224
|
+
/**
|
|
225
|
+
* The frames of a spritesheet whose image is `imageWidth` by `imageHeight`
|
|
226
|
+
* pixels: every whole cell of the grid, numbered from `0` left to right and
|
|
227
|
+
* top to bottom. Throws when the image holds no whole frame.
|
|
228
|
+
*/
|
|
229
|
+
export declare function spritesheetFrames(sheet: Pick<SpritesheetAsset, "key" | "frameWidth" | "frameHeight">, imageWidth: number, imageHeight: number): TextureFrame[];
|
|
230
|
+
/**
|
|
231
|
+
* Reads a texture atlas in the JSON Hash format, as TexturePacker
|
|
232
|
+
* (*JSON (Hash)*) and free-tex-packer (`JsonHash`) export it, and returns its
|
|
233
|
+
* frames.
|
|
234
|
+
*
|
|
235
|
+
* From each entry of `frames` it reads `frame`, the rectangle in the image;
|
|
236
|
+
* `sourceSize`, the picture's size before trimming; and `spriteSourceSize`,
|
|
237
|
+
* where the trimmed rectangle sat in it. The last two may be left out for an
|
|
238
|
+
* untrimmed frame. Other fields, such as `pivot`, `anchor`, `trimmed`, and
|
|
239
|
+
* the rest of `meta`, are ignored.
|
|
240
|
+
*
|
|
241
|
+
* It throws an error naming what is wrong, and which packer setting changes
|
|
242
|
+
* it, for:
|
|
243
|
+
* - a value that is not an object with a `frames` object, including the JSON
|
|
244
|
+
* Array format, whose `frames` is a list, and multi-image atlases, which
|
|
245
|
+
* list `textures`;
|
|
246
|
+
* - no frames, or a frame name that is empty or has leading or trailing spaces;
|
|
247
|
+
* - a rotated frame, `"rotated": true`;
|
|
248
|
+
* - a `meta.scale` other than 1;
|
|
249
|
+
* - a rectangle that is not made of whole, non-negative pixels, or a trimmed
|
|
250
|
+
* rectangle that does not fit in its source size;
|
|
251
|
+
* - a frame outside the image size `meta.size` states.
|
|
252
|
+
*
|
|
253
|
+
* @param json - The parsed atlas JSON.
|
|
254
|
+
*/
|
|
255
|
+
export declare function parseTextureAtlas(json: unknown): TextureAtlas;
|
|
256
|
+
/**
|
|
257
|
+
* Throws unless every frame lies inside an image of `imageWidth` by
|
|
258
|
+
* `imageHeight` pixels. `sheet` names the sheet in the error.
|
|
259
|
+
*/
|
|
260
|
+
export declare function checkFramesFitImage(sheet: string, frames: readonly TextureFrame[], imageWidth: number, imageHeight: number): void;
|
|
120
261
|
/**
|
|
121
262
|
* The type of a design object. Each built-in type maps to one runtime class:
|
|
122
263
|
* `image` to `ImageSprite`, `emoji` to `EmojiSprite`, `text` to `TextSprite`,
|
|
@@ -191,6 +332,24 @@ export interface DesignSpriteBase extends DesignObjectBase {
|
|
|
191
332
|
/** Vertical offset of the body's center from the sprite's center, in pixels, before scale. Default: `0`. */
|
|
192
333
|
bodyOffsetY?: number;
|
|
193
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* The outline an `image`, `emoji`, or `text` object can have, drawn by the
|
|
337
|
+
* optional `outline` module around the object's visible shape (X-023). The
|
|
338
|
+
* scene creates the module, `new Outline(game)`, before creating a design
|
|
339
|
+
* object with an outline, and a design holding either property is version 3.
|
|
340
|
+
* See {@link designDecorations}.
|
|
341
|
+
*/
|
|
342
|
+
export interface DesignOutline {
|
|
343
|
+
/**
|
|
344
|
+
* Width of the outline in the object's own pixels, before scale, so it
|
|
345
|
+
* grows with the object; `0` draws none. At most `64`. The outline is drawn
|
|
346
|
+
* around the object and leaves its size, body, and hit area as they are.
|
|
347
|
+
* Default: `0`.
|
|
348
|
+
*/
|
|
349
|
+
outlineWidth?: number;
|
|
350
|
+
/** CSS color of the outline, which may be translucent, such as `"rgba(0, 0, 0, 0.5)"`. Default: `"#ffffff"`. */
|
|
351
|
+
outlineColor?: string;
|
|
352
|
+
}
|
|
194
353
|
/**
|
|
195
354
|
* An `ImageSprite` in a design file.
|
|
196
355
|
*
|
|
@@ -199,7 +358,7 @@ export interface DesignSpriteBase extends DesignObjectBase {
|
|
|
199
358
|
* { "id": "a93e51b4-6c0d-4f2a-8b17-3e5d9c2f0a61", "name": "play", "type": "image", "imageKey": "btn-play", "x": 360, "y": 700 }
|
|
200
359
|
* ```
|
|
201
360
|
*/
|
|
202
|
-
export interface DesignImage extends DesignSpriteBase {
|
|
361
|
+
export interface DesignImage extends DesignSpriteBase, DesignOutline {
|
|
203
362
|
/** Always `"image"`. */
|
|
204
363
|
type: "image";
|
|
205
364
|
/** Required. The key of an image declared in one of the game's asset files. */
|
|
@@ -213,7 +372,7 @@ export interface DesignImage extends DesignSpriteBase {
|
|
|
213
372
|
* { "id": "0b9a7c3e-2d4f-4e61-a8c5-7f1e3d2b6a90", "type": "emoji", "sprite": "⭐", "size": 64, "x": 200, "y": 300 }
|
|
214
373
|
* ```
|
|
215
374
|
*/
|
|
216
|
-
export interface DesignEmoji extends DesignSpriteBase {
|
|
375
|
+
export interface DesignEmoji extends DesignSpriteBase, DesignOutline {
|
|
217
376
|
/** Always `"emoji"`. */
|
|
218
377
|
type: "emoji";
|
|
219
378
|
/** Required. The emoji to draw. */
|
|
@@ -229,7 +388,7 @@ export interface DesignEmoji extends DesignSpriteBase {
|
|
|
229
388
|
* { "id": "5c2e8f1a-9b3d-4a7e-b6c0-1d4f8e2a7c35", "type": "text", "text": "GAME OVER", "fontSize": 64, "x": 360, "y": 400 }
|
|
230
389
|
* ```
|
|
231
390
|
*/
|
|
232
|
-
export interface DesignText extends DesignSpriteBase {
|
|
391
|
+
export interface DesignText extends DesignSpriteBase, DesignOutline {
|
|
233
392
|
/** Always `"text"`. */
|
|
234
393
|
type: "text";
|
|
235
394
|
/** Required. The text to draw. */
|
|
@@ -335,8 +494,9 @@ export type DesignObject = DesignImage | DesignEmoji | DesignText | DesignBackgr
|
|
|
335
494
|
* {@link Design} and creates with `game.createObject` and
|
|
336
495
|
* `game.createObjectList`. A design without pages is a {@link DesignFileV1},
|
|
337
496
|
* one flat list of objects; a design divided into pages is a
|
|
338
|
-
* {@link DesignFileV2}; a design holding objects of an optional module
|
|
339
|
-
*
|
|
497
|
+
* {@link DesignFileV2}; a design holding objects of an optional module or
|
|
498
|
+
* properties of a decoration, such as an outline, with or without pages, is a
|
|
499
|
+
* {@link DesignFileV3}. All three are current: a
|
|
340
500
|
* design is written in the lowest version that holds it, so it also loads in
|
|
341
501
|
* an older MinimoJS whenever it can.
|
|
342
502
|
*/
|
|
@@ -395,10 +555,11 @@ export interface DesignFileV2 {
|
|
|
395
555
|
pages: DesignPage[];
|
|
396
556
|
}
|
|
397
557
|
/**
|
|
398
|
-
* A design file holding objects of an optional module, {@link DesignModule}
|
|
399
|
-
*
|
|
400
|
-
* `
|
|
401
|
-
*
|
|
558
|
+
* A design file holding objects of an optional module, {@link DesignModule},
|
|
559
|
+
* or properties of a decoration, such as the {@link DesignOutline} the
|
|
560
|
+
* `outline` module draws. Like the earlier versions, it has either a flat list
|
|
561
|
+
* of `objects` or `pages`, never both; it is version 3 only because an engine
|
|
562
|
+
* older than either cannot read it.
|
|
402
563
|
*
|
|
403
564
|
* @example
|
|
404
565
|
* ```json
|
|
@@ -415,7 +576,7 @@ export interface DesignFileV2 {
|
|
|
415
576
|
export interface DesignFileV3 {
|
|
416
577
|
/** Always `"minimo-design"`. */
|
|
417
578
|
format: "minimo-design";
|
|
418
|
-
/** `3`: the design holds objects of an optional module. */
|
|
579
|
+
/** `3`: the design holds objects of an optional module or properties of a decoration. */
|
|
419
580
|
version: 3;
|
|
420
581
|
/** The objects of a design without pages, in file order. Present exactly when `pages` is not. */
|
|
421
582
|
objects?: DesignObject[];
|
|
@@ -541,6 +702,55 @@ export interface DesignModuleSchema {
|
|
|
541
702
|
* ```
|
|
542
703
|
*/
|
|
543
704
|
export declare const designModules: Readonly<Record<string, DesignModuleSchema>>;
|
|
705
|
+
/**
|
|
706
|
+
* What an optional module adds to the objects of a design that are not its
|
|
707
|
+
* own, such as an outline the `outline` module draws around an image, for the
|
|
708
|
+
* engine and for tools such as an editor. See {@link designDecorations}.
|
|
709
|
+
*/
|
|
710
|
+
export interface DesignDecorationSchema {
|
|
711
|
+
/** The module's id, such as `"outline"`. */
|
|
712
|
+
readonly module: string;
|
|
713
|
+
/** The module's file, next to `minimo.js`, such as `"minimo-outline.js"`. */
|
|
714
|
+
readonly file: string;
|
|
715
|
+
/**
|
|
716
|
+
* The class the module exports, which a scene creates with
|
|
717
|
+
* `new Class(game)` before creating a design object that uses it, such as
|
|
718
|
+
* `"Outline"`.
|
|
719
|
+
*/
|
|
720
|
+
readonly className: string;
|
|
721
|
+
/** The types of design object that take the decoration's properties. */
|
|
722
|
+
readonly types: readonly DesignObjectType[];
|
|
723
|
+
/**
|
|
724
|
+
* The properties of those types that belong to the module, as
|
|
725
|
+
* {@link designObjectProperties} lists them with their types and defaults.
|
|
726
|
+
* A design holding any of them is version 3.
|
|
727
|
+
*/
|
|
728
|
+
readonly properties: readonly string[];
|
|
729
|
+
/**
|
|
730
|
+
* The number property that turns the decoration on when it is above `0`,
|
|
731
|
+
* such as `"outlineWidth"`. Only then does creating the object need the
|
|
732
|
+
* module.
|
|
733
|
+
*/
|
|
734
|
+
readonly enabledBy: string;
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* The optional modules that decorate design objects of the core's types,
|
|
738
|
+
* by id. Unlike a {@link DesignModule}, a decoration adds no object: it is a
|
|
739
|
+
* look of an `image`, `emoji`, or `text` object, held in properties of that
|
|
740
|
+
* object, which the module draws.
|
|
741
|
+
*
|
|
742
|
+
* A tool that draws a design creates the module, with `new Class(game)`,
|
|
743
|
+
* before creating an object whose `enabledBy` property is above `0`.
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```ts
|
|
747
|
+
* for (const schema of Object.values(designDecorations)) {
|
|
748
|
+
* const used = design.objects.some((object) => (object[schema.enabledBy] ?? 0) > 0);
|
|
749
|
+
* if (used) console.log(`${schema.className} from ${schema.file}`);
|
|
750
|
+
* }
|
|
751
|
+
* ```
|
|
752
|
+
*/
|
|
753
|
+
export declare const designDecorations: Readonly<Record<string, DesignDecorationSchema>>;
|
|
544
754
|
/**
|
|
545
755
|
* The data of a design file, `*.design.json`, validated: its objects, its
|
|
546
756
|
* pages, and searches on them. A `Design` needs no game and creates nothing; pass its
|
|
@@ -596,7 +806,8 @@ export declare class Design {
|
|
|
596
806
|
* a `data` that is not a JSON object, a page without a UUID `id` or with
|
|
597
807
|
* a missing, padded, or repeated `name`, and a `module` object in a design
|
|
598
808
|
* older than version 3 or with a module, kind, property, or option that
|
|
599
|
-
* {@link designModules} does not list
|
|
809
|
+
* {@link designModules} does not list, and a property of a decoration, such
|
|
810
|
+
* as `outlineWidth`, in a design older than version 3.
|
|
600
811
|
*
|
|
601
812
|
* @param json - The content of a `*.design.json` file. See {@link DesignFile}.
|
|
602
813
|
*/
|