minimojs 1.0.0-alpha.32 → 1.0.0-alpha.33
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/minimo-format.d.ts +95 -9
- package/dist/minimo-format.js +122 -26
- package/dist/minimo.d.ts +1 -1
- package/package.json +1 -1
package/dist/minimo-format.d.ts
CHANGED
|
@@ -304,9 +304,17 @@ export interface DesignBackground extends DesignObjectBase {
|
|
|
304
304
|
/** Any object of a design file. Its `type` says which. */
|
|
305
305
|
export type DesignObject = DesignImage | DesignEmoji | DesignText | DesignBackground;
|
|
306
306
|
/**
|
|
307
|
-
* The content of a design file, `*.design.json
|
|
308
|
-
*
|
|
309
|
-
*
|
|
307
|
+
* The content of a design file, `*.design.json`, which a game reads through
|
|
308
|
+
* {@link Design} and creates with `game.createObject` and
|
|
309
|
+
* `game.createObjectList`. A design without pages is a {@link DesignFileV1},
|
|
310
|
+
* one flat list of objects; a design divided into pages is a
|
|
311
|
+
* {@link DesignFileV2}. Both are current: a design is written as version 2
|
|
312
|
+
* only when it has pages, so a design without them also loads in an older
|
|
313
|
+
* MinimoJS.
|
|
314
|
+
*/
|
|
315
|
+
export type DesignFile = DesignFileV1 | DesignFileV2;
|
|
316
|
+
/**
|
|
317
|
+
* A design file without pages: one flat list of objects.
|
|
310
318
|
*
|
|
311
319
|
* @example
|
|
312
320
|
* ```json
|
|
@@ -320,14 +328,62 @@ export type DesignObject = DesignImage | DesignEmoji | DesignText | DesignBackgr
|
|
|
320
328
|
* }
|
|
321
329
|
* ```
|
|
322
330
|
*/
|
|
323
|
-
export interface
|
|
331
|
+
export interface DesignFileV1 {
|
|
324
332
|
/** Always `"minimo-design"`. */
|
|
325
333
|
format: "minimo-design";
|
|
326
|
-
/**
|
|
334
|
+
/** `1`: the design has no pages. */
|
|
327
335
|
version: 1;
|
|
328
336
|
/** The objects, in file order, which is the order they are created and drawn in. */
|
|
329
337
|
objects: DesignObject[];
|
|
330
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* A design file divided into pages. Pages only organize a design, such as
|
|
341
|
+
* one page per template, or a menu on one page and its button templates on
|
|
342
|
+
* another; they do not change how an object is created. Read one page's
|
|
343
|
+
* objects with {@link Design.findAllByPage}.
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```json
|
|
347
|
+
* {
|
|
348
|
+
* "format": "minimo-design",
|
|
349
|
+
* "version": 2,
|
|
350
|
+
* "pages": [
|
|
351
|
+
* { "id": "0b1d5e7a-3c2f-4d8e-9a61-7f4b2c8e1d05", "name": "Hero", "objects": [
|
|
352
|
+
* { "id": "a93e51b4-6c0d-4f2a-8b17-3e5d9c2f0a61", "name": "hero", "type": "image", "imageKey": "hero" }
|
|
353
|
+
* ] },
|
|
354
|
+
* { "id": "7c4a2e9f-1b3d-4f6a-8c05-2e9d7b1f4a36", "name": "Enemies", "objects": [
|
|
355
|
+
* { "id": "c07d9e3f-1a2b-4c3d-8e4f-5a6b7c8d9e0f", "name": "bat", "tags": ["enemy"], "type": "image", "imageKey": "bat" }
|
|
356
|
+
* ] }
|
|
357
|
+
* ]
|
|
358
|
+
* }
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
export interface DesignFileV2 {
|
|
362
|
+
/** Always `"minimo-design"`. */
|
|
363
|
+
format: "minimo-design";
|
|
364
|
+
/** `2`: the design is divided into pages. */
|
|
365
|
+
version: 2;
|
|
366
|
+
/** The pages, at least one, in file order. */
|
|
367
|
+
pages: DesignPage[];
|
|
368
|
+
}
|
|
369
|
+
/** One page of a {@link DesignFileV2}: a named group of the design's objects. */
|
|
370
|
+
export interface DesignPage {
|
|
371
|
+
/**
|
|
372
|
+
* Required. A UUID unique among the file's pages. Tools use it to keep a
|
|
373
|
+
* page through a rename; code finds a page by its `name`.
|
|
374
|
+
*/
|
|
375
|
+
id: string;
|
|
376
|
+
/**
|
|
377
|
+
* Required. Unique in the file, not empty, and with no leading or trailing
|
|
378
|
+
* spaces, such as `"Enemies"`. Code finds a page by it.
|
|
379
|
+
*/
|
|
380
|
+
name: string;
|
|
381
|
+
/**
|
|
382
|
+
* The page's objects, in file order. An object's `id` is unique in the whole
|
|
383
|
+
* file, across pages.
|
|
384
|
+
*/
|
|
385
|
+
objects: DesignObject[];
|
|
386
|
+
}
|
|
331
387
|
/**
|
|
332
388
|
* How one property of a design object is written, for tools that read or edit
|
|
333
389
|
* designs without the TypeScript types, such as a property panel. See
|
|
@@ -371,8 +427,8 @@ export interface DesignProperty {
|
|
|
371
427
|
*/
|
|
372
428
|
export declare const designObjectProperties: Readonly<Record<DesignObjectType, readonly DesignProperty[]>>;
|
|
373
429
|
/**
|
|
374
|
-
* The data of a design file, `*.design.json`, validated: its objects
|
|
375
|
-
* searches on them. A `Design` needs no game and creates nothing; pass its
|
|
430
|
+
* The data of a design file, `*.design.json`, validated: its objects, its
|
|
431
|
+
* pages, and searches on them. A `Design` needs no game and creates nothing; pass its
|
|
376
432
|
* objects, or the results of its searches, to `game.createObject` or
|
|
377
433
|
* `game.createObjectList`. Data alone also serves markers, such as spawn
|
|
378
434
|
* points, which code reads without creating anything.
|
|
@@ -393,12 +449,27 @@ export declare const designObjectProperties: Readonly<Record<DesignObjectType, r
|
|
|
393
449
|
* }
|
|
394
450
|
* }
|
|
395
451
|
* ```
|
|
452
|
+
*
|
|
453
|
+
* A design divided into pages, such as a screen on one page and templates on
|
|
454
|
+
* another, is created one page at a time:
|
|
455
|
+
*
|
|
456
|
+
* ```ts
|
|
457
|
+
* const ui = game.createObjectList(menu.findAllByPage("Menu"));
|
|
458
|
+
* const bat = game.createObject(menu.findByPage("Bat"));
|
|
459
|
+
* ```
|
|
396
460
|
*/
|
|
397
461
|
export declare class Design {
|
|
398
462
|
/**
|
|
399
|
-
* Every object of the design, as object data, in file order
|
|
463
|
+
* Every object of the design, as object data, in file order: for a design
|
|
464
|
+
* with pages, every page's objects, page after page. Passing them to
|
|
465
|
+
* `game.createObjectList` creates every page at once.
|
|
400
466
|
*/
|
|
401
467
|
readonly objects: readonly DesignObject[];
|
|
468
|
+
/**
|
|
469
|
+
* The design's pages, in file order, each with its objects as object data.
|
|
470
|
+
* Empty for a design without pages, a version 1 file.
|
|
471
|
+
*/
|
|
472
|
+
readonly pages: readonly Readonly<DesignPage>[];
|
|
402
473
|
/**
|
|
403
474
|
* Validates a design file's content and keeps a copy of it.
|
|
404
475
|
*
|
|
@@ -407,11 +478,26 @@ export declare class Design {
|
|
|
407
478
|
* `"minimo-design"`, a `version` newer than this engine reads, an object
|
|
408
479
|
* whose `type` is not one of the built-in types, an `id` that is not a UUID
|
|
409
480
|
* or is used twice, a value of the wrong type, a missing required property,
|
|
410
|
-
*
|
|
481
|
+
* a `data` that is not a JSON object, and a page without a UUID `id` or with
|
|
482
|
+
* a missing, padded, or repeated `name`.
|
|
411
483
|
*
|
|
412
484
|
* @param json - The content of a `*.design.json` file. See {@link DesignFile}.
|
|
413
485
|
*/
|
|
414
486
|
constructor(json: unknown);
|
|
487
|
+
/**
|
|
488
|
+
* Returns the first object of the page named `name`, in file order, such
|
|
489
|
+
* as a page holding one template. Throws an error naming the page when the
|
|
490
|
+
* design has no page with that name, or the page has no objects.
|
|
491
|
+
*/
|
|
492
|
+
findByPage(name: string): DesignObject;
|
|
493
|
+
/**
|
|
494
|
+
* Returns every object of the page named `name`, in file order; an empty
|
|
495
|
+
* list for an empty page. Throws an error naming the page when the design
|
|
496
|
+
* has no page with that name, because a page name that matches nothing is
|
|
497
|
+
* almost always a typo.
|
|
498
|
+
*/
|
|
499
|
+
findAllByPage(name: string): DesignObject[];
|
|
500
|
+
private pageNamed;
|
|
415
501
|
/**
|
|
416
502
|
* Returns the first object whose `name` is `name`, in file order.
|
|
417
503
|
* Throws an error naming `name` when there is none.
|
package/dist/minimo-format.js
CHANGED
|
@@ -102,7 +102,7 @@ export function parseAssetFile(json) {
|
|
|
102
102
|
/** The `format` value of every design file. */
|
|
103
103
|
const DESIGN_FILE_FORMAT = "minimo-design";
|
|
104
104
|
/** The newest design file `version` this engine reads. */
|
|
105
|
-
const DESIGN_FILE_VERSION =
|
|
105
|
+
const DESIGN_FILE_VERSION = 2;
|
|
106
106
|
function spec(type, defaultValue) {
|
|
107
107
|
return { type, nullable: false, values: null, required: false, default: defaultValue };
|
|
108
108
|
}
|
|
@@ -214,7 +214,11 @@ export const designObjectProperties = Object.freeze({
|
|
|
214
214
|
text: describeProperties(TEXT_SPECS),
|
|
215
215
|
background: describeProperties(BACKGROUND_SPECS),
|
|
216
216
|
});
|
|
217
|
-
const DESIGN_FILE_FIELDS =
|
|
217
|
+
const DESIGN_FILE_FIELDS = {
|
|
218
|
+
1: new Set(["format", "version", "objects"]),
|
|
219
|
+
2: new Set(["format", "version", "pages"]),
|
|
220
|
+
};
|
|
221
|
+
const DESIGN_PAGE_FIELDS = new Set(["id", "name", "objects"]);
|
|
218
222
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
219
223
|
const PROPERTIES_BY_TYPE = new Map(Object.entries(designObjectProperties).map(([type, properties]) => [
|
|
220
224
|
type,
|
|
@@ -308,11 +312,6 @@ function parseDesignFile(json) {
|
|
|
308
312
|
if (!isPlainObject(json)) {
|
|
309
313
|
throw fail("a design must be a JSON object.");
|
|
310
314
|
}
|
|
311
|
-
for (const field of Object.keys(json)) {
|
|
312
|
-
if (!DESIGN_FILE_FIELDS.has(field)) {
|
|
313
|
-
throw fail(`the design has an unknown field "${field}".`);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
315
|
if (json.format !== DESIGN_FILE_FORMAT) {
|
|
317
316
|
throw fail(`"format" must be "${DESIGN_FILE_FORMAT}", found ${describe(json.format)}.`);
|
|
318
317
|
}
|
|
@@ -323,24 +322,75 @@ function parseDesignFile(json) {
|
|
|
323
322
|
if (version > DESIGN_FILE_VERSION) {
|
|
324
323
|
throw fail(`"version" ${version} is newer than this engine reads (${DESIGN_FILE_VERSION}). Use a newer MinimoJS.`);
|
|
325
324
|
}
|
|
326
|
-
//
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
325
|
+
// Both versions are current, so neither is migrated: version 1 is a design
|
|
326
|
+
// without pages, and version 2 one divided into pages.
|
|
327
|
+
for (const field of Object.keys(json)) {
|
|
328
|
+
if (!DESIGN_FILE_FIELDS[version].has(field)) {
|
|
329
|
+
const hint = field === "pages" ? ` A design with pages has "version" 2.` : field === "objects" ? ` In version 2, objects belong to "pages".` : "";
|
|
330
|
+
throw fail(`the design has an unknown field "${field}".${hint}`);
|
|
331
|
+
}
|
|
330
332
|
}
|
|
331
|
-
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
333
|
+
// An object's id is unique in the whole file, across pages.
|
|
334
|
+
const whereById = new Map();
|
|
335
|
+
const parseObjects = (entries, where) => {
|
|
336
|
+
if (!Array.isArray(entries)) {
|
|
337
|
+
throw fail(`${where === "" ? "" : `${where}.`}"objects" must be a list, found ${describe(entries)}.`);
|
|
338
|
+
}
|
|
339
|
+
return entries.map((entry, index) => {
|
|
340
|
+
const at = `${where === "" ? "" : `${where}.`}objects[${index}]`;
|
|
341
|
+
const object = parseDesignObject(entry, at, "design");
|
|
342
|
+
const id = object.id.toLowerCase();
|
|
343
|
+
const first = whereById.get(id);
|
|
344
|
+
if (first !== undefined) {
|
|
345
|
+
throw fail(`the id "${object.id}" is used twice, by ${first} and ${at}.`);
|
|
346
|
+
}
|
|
347
|
+
whereById.set(id, at);
|
|
348
|
+
return object;
|
|
349
|
+
});
|
|
350
|
+
};
|
|
351
|
+
if (version === 1) {
|
|
352
|
+
return { format: DESIGN_FILE_FORMAT, version: 1, objects: parseObjects(json.objects, "") };
|
|
353
|
+
}
|
|
354
|
+
if (!Array.isArray(json.pages)) {
|
|
355
|
+
throw fail(`"pages" must be a list, found ${describe(json.pages)}.`);
|
|
356
|
+
}
|
|
357
|
+
if (json.pages.length === 0) {
|
|
358
|
+
throw fail(`"pages" must hold at least one page. A design without pages has "version" 1 and a top-level "objects".`);
|
|
359
|
+
}
|
|
360
|
+
const pageIndexById = new Map();
|
|
361
|
+
const pageIndexByName = new Map();
|
|
362
|
+
const pages = json.pages.map((entry, index) => {
|
|
363
|
+
const where = `pages[${index}]`;
|
|
364
|
+
if (!isPlainObject(entry)) {
|
|
365
|
+
throw fail(`${where} must be an object, found ${describe(entry)}.`);
|
|
366
|
+
}
|
|
367
|
+
const name = entry.name;
|
|
368
|
+
const label = typeof name === "string" ? `${where} ("${name}")` : where;
|
|
369
|
+
for (const field of Object.keys(entry)) {
|
|
370
|
+
if (!DESIGN_PAGE_FIELDS.has(field)) {
|
|
371
|
+
throw fail(`${label} has an unknown field "${field}".`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const id = entry.id;
|
|
375
|
+
if (typeof id !== "string" || !UUID_PATTERN.test(id)) {
|
|
376
|
+
throw fail(`${label} must have an "id" that is a UUID, such as "0b1d5e7a-3c2f-4d8e-9a61-7f4b2c8e1d05", found ${describe(id)}.`);
|
|
377
|
+
}
|
|
378
|
+
const firstWithId = pageIndexById.get(id.toLowerCase());
|
|
379
|
+
if (firstWithId !== undefined) {
|
|
380
|
+
throw fail(`the page id "${id}" is used twice, by pages[${firstWithId}] and ${where}.`);
|
|
381
|
+
}
|
|
382
|
+
pageIndexById.set(id.toLowerCase(), index);
|
|
383
|
+
if (typeof name !== "string" || name.length === 0 || name.trim() !== name) {
|
|
384
|
+
throw fail(`${where} must have a "name" that is a non-empty string with no leading or trailing spaces, found ${describe(name)}.`);
|
|
339
385
|
}
|
|
340
|
-
|
|
341
|
-
|
|
386
|
+
const firstWithName = pageIndexByName.get(name);
|
|
387
|
+
if (firstWithName !== undefined) {
|
|
388
|
+
throw fail(`the page name "${name}" is used twice, by pages[${firstWithName}] and ${where}.`);
|
|
389
|
+
}
|
|
390
|
+
pageIndexByName.set(name, index);
|
|
391
|
+
return { id, name, objects: parseObjects(entry.objects, where) };
|
|
342
392
|
});
|
|
343
|
-
return { format: DESIGN_FILE_FORMAT, version:
|
|
393
|
+
return { format: DESIGN_FILE_FORMAT, version: 2, pages };
|
|
344
394
|
}
|
|
345
395
|
/**
|
|
346
396
|
* Returns the first item `match` accepts, in order, or throws an error saying
|
|
@@ -357,8 +407,8 @@ export function findFirst(items, match, searched, where) {
|
|
|
357
407
|
return found;
|
|
358
408
|
}
|
|
359
409
|
/**
|
|
360
|
-
* The data of a design file, `*.design.json`, validated: its objects
|
|
361
|
-
* searches on them. A `Design` needs no game and creates nothing; pass its
|
|
410
|
+
* The data of a design file, `*.design.json`, validated: its objects, its
|
|
411
|
+
* pages, and searches on them. A `Design` needs no game and creates nothing; pass its
|
|
362
412
|
* objects, or the results of its searches, to `game.createObject` or
|
|
363
413
|
* `game.createObjectList`. Data alone also serves markers, such as spawn
|
|
364
414
|
* points, which code reads without creating anything.
|
|
@@ -379,6 +429,14 @@ export function findFirst(items, match, searched, where) {
|
|
|
379
429
|
* }
|
|
380
430
|
* }
|
|
381
431
|
* ```
|
|
432
|
+
*
|
|
433
|
+
* A design divided into pages, such as a screen on one page and templates on
|
|
434
|
+
* another, is created one page at a time:
|
|
435
|
+
*
|
|
436
|
+
* ```ts
|
|
437
|
+
* const ui = game.createObjectList(menu.findAllByPage("Menu"));
|
|
438
|
+
* const bat = game.createObject(menu.findByPage("Bat"));
|
|
439
|
+
* ```
|
|
382
440
|
*/
|
|
383
441
|
export class Design {
|
|
384
442
|
/**
|
|
@@ -389,12 +447,50 @@ export class Design {
|
|
|
389
447
|
* `"minimo-design"`, a `version` newer than this engine reads, an object
|
|
390
448
|
* whose `type` is not one of the built-in types, an `id` that is not a UUID
|
|
391
449
|
* or is used twice, a value of the wrong type, a missing required property,
|
|
392
|
-
*
|
|
450
|
+
* a `data` that is not a JSON object, and a page without a UUID `id` or with
|
|
451
|
+
* a missing, padded, or repeated `name`.
|
|
393
452
|
*
|
|
394
453
|
* @param json - The content of a `*.design.json` file. See {@link DesignFile}.
|
|
395
454
|
*/
|
|
396
455
|
constructor(json) {
|
|
397
|
-
|
|
456
|
+
const file = parseDesignFile(json);
|
|
457
|
+
if (file.version === 1) {
|
|
458
|
+
this.pages = Object.freeze([]);
|
|
459
|
+
this.objects = Object.freeze(file.objects);
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
this.pages = Object.freeze(file.pages.map((page) => Object.freeze({ ...page, objects: Object.freeze(page.objects) })));
|
|
463
|
+
this.objects = Object.freeze(file.pages.flatMap((page) => page.objects));
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Returns the first object of the page named `name`, in file order, such
|
|
468
|
+
* as a page holding one template. Throws an error naming the page when the
|
|
469
|
+
* design has no page with that name, or the page has no objects.
|
|
470
|
+
*/
|
|
471
|
+
findByPage(name) {
|
|
472
|
+
const page = this.pageNamed(name);
|
|
473
|
+
if (page.objects.length === 0) {
|
|
474
|
+
throw new Error(`MinimoJS: The page "${name}" of the design has no objects.`);
|
|
475
|
+
}
|
|
476
|
+
return page.objects[0];
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Returns every object of the page named `name`, in file order; an empty
|
|
480
|
+
* list for an empty page. Throws an error naming the page when the design
|
|
481
|
+
* has no page with that name, because a page name that matches nothing is
|
|
482
|
+
* almost always a typo.
|
|
483
|
+
*/
|
|
484
|
+
findAllByPage(name) {
|
|
485
|
+
return [...this.pageNamed(name).objects];
|
|
486
|
+
}
|
|
487
|
+
pageNamed(name) {
|
|
488
|
+
const page = this.pages.find((candidate) => candidate.name === name);
|
|
489
|
+
if (page === undefined) {
|
|
490
|
+
const known = this.pages.length === 0 ? "it has no pages" : `its pages are ${this.pages.map((p) => `"${p.name}"`).join(", ")}`;
|
|
491
|
+
throw new Error(`MinimoJS: No page named "${name}" in the design; ${known}.`);
|
|
492
|
+
}
|
|
493
|
+
return page;
|
|
398
494
|
}
|
|
399
495
|
/**
|
|
400
496
|
* Returns the first object whose `name` is `name`, in file order.
|
package/dist/minimo.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { DesignObject } from "./minimo-format.js";
|
|
|
13
13
|
export { Design, parseAssetFile } from "./minimo-format.js";
|
|
14
14
|
export { SoundPlayback } from "./internal/SoundSystem.js";
|
|
15
15
|
export type { PlaySoundOptions, SoundPlaybackState } from "./internal/SoundSystem.js";
|
|
16
|
-
export type { AssetFile, DesignBackground, DesignEmoji, DesignFile, DesignImage, DesignObject, DesignObjectBase, DesignObjectType, DesignSpriteBase, DesignText, ImageAsset, SoundAsset, UnknownAsset, } from "./minimo-format.js";
|
|
16
|
+
export type { AssetFile, DesignBackground, DesignEmoji, DesignFile, DesignFileV1, DesignFileV2, DesignImage, DesignObject, DesignObjectBase, DesignObjectType, DesignPage, DesignSpriteBase, DesignText, ImageAsset, SoundAsset, UnknownAsset, } from "./minimo-format.js";
|
|
17
17
|
/**
|
|
18
18
|
* Optional tuning values for {@link Game.animateExplode}.
|
|
19
19
|
*
|
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.33",
|
|
4
4
|
"description": "MinimoJS v1 — ultra-minimal, flat, deterministic 2D web game engine. Image-first sprites with emoji as a zero-asset alternative, rAF loop, TypeScript-first, LLM-friendly.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/minimo.js",
|