@gg-web-engine/core 0.0.58 → 0.0.59
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/2d/components/rendering/i-camera-2d.component.d.ts +5 -0
- package/dist/2d/components/rendering/i-visual-scene-2d.component.d.ts +1 -2
- package/dist/2d/entities/renderer-2d.entity.d.ts +2 -1
- package/dist/2d/gg-2d-world.d.ts +5 -1
- package/dist/2d/gg-2d-world.js +4 -2
- package/dist/2d/index.d.ts +3 -0
- package/dist/2d/index.js +3 -0
- package/dist/2d/level-loader.d.ts +101 -0
- package/dist/2d/level-loader.js +88 -0
- package/dist/2d/loader.d.ts +11 -0
- package/dist/2d/loader.js +10 -0
- package/dist/3d/components/rendering/{i-camera.component.d.ts → i-camera-3d.component.d.ts} +1 -1
- package/dist/3d/components/rendering/i-camera-3d.component.js +1 -0
- package/dist/3d/components/rendering/i-renderer-3d.component.d.ts +0 -1
- package/dist/3d/components/rendering/i-visual-scene-3d.component.d.ts +1 -2
- package/dist/3d/entities/camera-3d.entity.d.ts +24 -0
- package/dist/3d/entities/camera-3d.entity.js +35 -0
- package/dist/3d/entities/renderer-3d.entity.d.ts +0 -5
- package/dist/3d/entities/renderer-3d.entity.js +0 -15
- package/dist/3d/gg-3d-world.d.ts +2 -2
- package/dist/3d/index.d.ts +3 -1
- package/dist/3d/index.js +3 -1
- package/dist/3d/level-loader.d.ts +300 -0
- package/dist/3d/level-loader.js +267 -0
- package/dist/3d/loader.d.ts +44 -3
- package/dist/3d/loader.js +31 -3
- package/dist/base/blueprint/blueprint-node.d.ts +75 -0
- package/dist/base/blueprint/blueprint-node.js +51 -0
- package/dist/base/blueprint/blueprint.d.ts +123 -0
- package/dist/base/blueprint/blueprint.js +86 -0
- package/dist/base/blueprint/nodes/remove-entity.node.d.ts +26 -0
- package/dist/base/blueprint/nodes/remove-entity.node.js +29 -0
- package/dist/base/components/rendering/i-renderer.component.d.ts +1 -0
- package/dist/base/components/rendering/i-visual-scene.component.d.ts +2 -0
- package/dist/base/entities/group.entity.d.ts +17 -0
- package/dist/base/entities/group.entity.js +19 -0
- package/dist/base/entities/i-entity.d.ts +11 -0
- package/dist/base/entities/i-entity.js +28 -0
- package/dist/base/entities/i-renderer.entity.d.ts +7 -1
- package/dist/base/entities/i-renderer.entity.js +15 -0
- package/dist/base/gg-world.d.ts +13 -0
- package/dist/base/gg-world.js +23 -1
- package/dist/base/index.d.ts +5 -0
- package/dist/base/index.js +5 -0
- package/dist/base/inputs/mouse.input.js +1 -0
- package/dist/base/level-loader.d.ts +217 -0
- package/dist/base/level-loader.js +258 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/tsconfig.json +3 -1
- /package/dist/{3d/components/rendering/i-camera.component.js → 2d/components/rendering/i-camera-2d.component.js} +0 -0
package/dist/base/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export * from './blueprint/blueprint';
|
|
2
|
+
export * from './blueprint/blueprint-node';
|
|
3
|
+
export * from './blueprint/nodes/remove-entity.node';
|
|
1
4
|
export * from './clock/global-clock';
|
|
2
5
|
export * from './clock/i-clock';
|
|
3
6
|
export * from './clock/pausable-clock';
|
|
@@ -14,6 +17,7 @@ export * from './data-structures/bitmask';
|
|
|
14
17
|
export * from './data-structures/graph';
|
|
15
18
|
export * from './entities/controllers/animation-mixer';
|
|
16
19
|
export * from './entities/controllers/inline-controller';
|
|
20
|
+
export * from './entities/group.entity';
|
|
17
21
|
export * from './entities/i-entity';
|
|
18
22
|
export * from './entities/i-renderer.entity';
|
|
19
23
|
export * from './entities/i-renderable.entity';
|
|
@@ -36,3 +40,4 @@ export * from './math/matrix4';
|
|
|
36
40
|
export * from './math/splines';
|
|
37
41
|
export * from './pipes/gg-elastic.pipe';
|
|
38
42
|
export * from './gg-world';
|
|
43
|
+
export * from './level-loader';
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { GgWorld, GgWorldTypeDocRepo } from './gg-world';
|
|
2
|
+
import { GroupEntity } from './entities/group.entity';
|
|
3
|
+
import { BlueprintJson, BlueprintNodeFactory } from './blueprint/blueprint';
|
|
4
|
+
/**
|
|
5
|
+
* A function that turns per-entity JSON settings into a spawned `IEntity` (e.g. a primitive body,
|
|
6
|
+
* a trigger, a camera). Registered against a class alias via {@link LevelLoader.registerClass}.
|
|
7
|
+
* May be `async`/return a `Promise` (e.g. the built-in `"Glb"` 3D class, which fetches a model) -
|
|
8
|
+
* {@link LevelLoader.loadLevel} awaits every generator before moving to the next entity. A
|
|
9
|
+
* generator that returns anything other than an `IEntity` (including `null`/`undefined`) has its
|
|
10
|
+
* result discarded - see {@link LevelLoader.loadLevel}.
|
|
11
|
+
* @template D - The position type
|
|
12
|
+
* @template R - The rotation type
|
|
13
|
+
* @template TypeDoc - The type document repository
|
|
14
|
+
* @template Settings - The settings object type
|
|
15
|
+
* @template W - The world type
|
|
16
|
+
*/
|
|
17
|
+
export type EntityGenerator<D, R, TypeDoc extends GgWorldTypeDocRepo<D, R>, Settings = any, W = GgWorld<D, R, TypeDoc>> = (world: W, settings: Settings) => any;
|
|
18
|
+
/**
|
|
19
|
+
* A level/scene, serializable as a single JSON document (e.g. to be hosted as a static file and
|
|
20
|
+
* loaded via {@link LevelLoader.loadLevelFromUrl}).
|
|
21
|
+
*/
|
|
22
|
+
export interface LevelJson {
|
|
23
|
+
/**
|
|
24
|
+
* Entities in the level
|
|
25
|
+
*/
|
|
26
|
+
entities: EntityJson[];
|
|
27
|
+
/**
|
|
28
|
+
* Blueprint graphs available to this level's entities, keyed by name - referenced from an
|
|
29
|
+
* `EntityJson.events` entry to run a blueprint whenever the named observable on that entity
|
|
30
|
+
* fires. See {@link BlueprintJson} and the `gg-engine-level-json` skill's "Blueprints" section.
|
|
31
|
+
*/
|
|
32
|
+
blueprints?: Record<string, BlueprintJson>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* JSON description of a single entity in a level. `position`/`rotation` are left untyped here
|
|
36
|
+
* since their shape depends on the dimensionality (`Point2`/`number` for 2D, `Point3`/`Point4`
|
|
37
|
+
* for 3D) of whichever `LevelLoader` subclass parses this JSON.
|
|
38
|
+
*/
|
|
39
|
+
export interface EntityJson {
|
|
40
|
+
/**
|
|
41
|
+
* Class alias for the entity, matching a class registered via `registerClass`. Built-in
|
|
42
|
+
* primitive shapes (box/sphere/square/circle/...) all share the single `"Primitive"` alias and
|
|
43
|
+
* are distinguished by `shape` instead of by a per-shape class - e.g. `{ class: "Primitive",
|
|
44
|
+
* shape: "BOX" }` rather than `{ class: "BOX" }`. Apps register their own aliases (e.g.
|
|
45
|
+
* `"ShapeSpawner"`) the same way the dimensionality-specific `LevelLoader` subclasses register
|
|
46
|
+
* their built-ins, via `registerClass`.
|
|
47
|
+
*/
|
|
48
|
+
class: string;
|
|
49
|
+
/**
|
|
50
|
+
* Shape identifier for the built-in `"Primitive"` entity class (e.g. `"Box"`, `"Circle"`) - see
|
|
51
|
+
* the dimensionality-specific `LevelLoader` subclass (`Gg2dLevelLoader`/`Gg3dLevelLoader`) for
|
|
52
|
+
* the supported values. Ignored for any other `class`.
|
|
53
|
+
*/
|
|
54
|
+
shape?: string;
|
|
55
|
+
/**
|
|
56
|
+
* Position of the entity
|
|
57
|
+
*/
|
|
58
|
+
position?: any;
|
|
59
|
+
/**
|
|
60
|
+
* Rotation of the entity
|
|
61
|
+
*/
|
|
62
|
+
rotation?: any;
|
|
63
|
+
/**
|
|
64
|
+
* Name of the entity. `loadLevel` sets the generator's returned `IEntity`'s `.name` to this
|
|
65
|
+
* (overriding whatever default the generator gave it), so it can be found afterwards with
|
|
66
|
+
* `GgWorld.getEntityByName`/`IEntity.getChildEntityByName`. Moot if the generator doesn't return
|
|
67
|
+
* an `IEntity` - that result is discarded (with a console warning) before naming is applied.
|
|
68
|
+
*/
|
|
69
|
+
name?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Configuration for the entity, passed to its generator alongside position/rotation/name
|
|
72
|
+
*/
|
|
73
|
+
config?: any;
|
|
74
|
+
/**
|
|
75
|
+
* Maps an observable property name on this entity's generated `IEntity` (e.g. `Trigger3dEntity`'s
|
|
76
|
+
* `"onEntityEntered"`) to what should run whenever that observable fires - see
|
|
77
|
+
* {@link EntityEventBinding}. `loadLevel` subscribes to the observable and triggers a fresh
|
|
78
|
+
* `Blueprint` instance (via its `"in"` entry point) with whatever value it emits, each time it
|
|
79
|
+
* fires - see `LevelLoader.loadLevel` and the `gg-engine-level-json` skill's "Blueprints"
|
|
80
|
+
* section. Silently ignored (with a console warning) if the binding can't be resolved to a
|
|
81
|
+
* blueprint, or the named property isn't an `Observable`.
|
|
82
|
+
*/
|
|
83
|
+
events?: Record<string, EntityEventBinding>;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* What an `EntityJson.events` entry runs. Either:
|
|
87
|
+
* - a plain `string` - first tried as a key into the level's top-level `blueprints` map (a named,
|
|
88
|
+
* possibly multi-node graph); if not found there, tried as a blueprint node type alias
|
|
89
|
+
* registered via `registerBlueprintNode` (e.g. the built-in `"RemoveEntity"`) instead, with no
|
|
90
|
+
* settings - shorthand for the single-node form below with `settings` omitted.
|
|
91
|
+
* - `{ type, settings? }` - a single built-in/registered blueprint node used directly as the
|
|
92
|
+
* handler, with inline `settings`, no `blueprints` entry needed at all - e.g.
|
|
93
|
+
* `{ "type": "RemoveEntity", "settings": { "dispose": true } }`. Only node types registered with
|
|
94
|
+
* a default input pin (every built-in one is - see `registerBlueprintNode`) support this form;
|
|
95
|
+
* others require a full graph declared in `blueprints` instead, addressing the desired input pin
|
|
96
|
+
* explicitly via `inputs`.
|
|
97
|
+
*/
|
|
98
|
+
export type EntityEventBinding = string | {
|
|
99
|
+
type: string;
|
|
100
|
+
settings?: Record<string, any>;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Base class for level loaders: parses a {@link LevelJson} document into world entities by
|
|
104
|
+
* dispatching each `EntityJson.class` to a generator function registered with {@link registerClass}.
|
|
105
|
+
*
|
|
106
|
+
* A generator is required to return an `IEntity`. Every `IEntity` a generator produces is parented
|
|
107
|
+
* under one {@link GroupEntity} per `loadLevel`/`loadLevelFromUrl` call (added to the world
|
|
108
|
+
* immediately, and handed back once loading completes) - so a whole level can be torn down in one
|
|
109
|
+
* shot with `world.removeEntity(level, true)`, which cascades removal/disposal to every child, and
|
|
110
|
+
* any named entity can be found afterwards with `level.getChildEntityByName(name)`. If a generator
|
|
111
|
+
* returns anything other than an `IEntity` (including `null`/`undefined`), `loadLevel` logs a
|
|
112
|
+
* `console.warn` and skips that entity - it's never parented, named, or tracked.
|
|
113
|
+
* @template D - The position type
|
|
114
|
+
* @template R - The rotation type
|
|
115
|
+
* @template TypeDoc - The type document repository
|
|
116
|
+
*/
|
|
117
|
+
export declare abstract class LevelLoader<D, R, TypeDoc extends GgWorldTypeDocRepo<D, R>> {
|
|
118
|
+
protected readonly world: GgWorld<D, R, TypeDoc>;
|
|
119
|
+
/**
|
|
120
|
+
* Map of class aliases to generator functions
|
|
121
|
+
*/
|
|
122
|
+
protected generators: Map<string, EntityGenerator<D, R, TypeDoc, any, any>>;
|
|
123
|
+
/**
|
|
124
|
+
* Map of blueprint node type aliases to node factory functions - see {@link registerBlueprintNode}.
|
|
125
|
+
*/
|
|
126
|
+
protected blueprintNodes: Map<string, BlueprintNodeFactory<D, R, TypeDoc>>;
|
|
127
|
+
/**
|
|
128
|
+
* Map of blueprint node type aliases to their default input pin name, for node types registered
|
|
129
|
+
* with one - see {@link registerBlueprintNode}.
|
|
130
|
+
*/
|
|
131
|
+
protected blueprintNodeDefaultInputs: Map<string, string>;
|
|
132
|
+
/**
|
|
133
|
+
* Constructor
|
|
134
|
+
* @param world - The world instance
|
|
135
|
+
*/
|
|
136
|
+
constructor(world: GgWorld<D, R, TypeDoc>);
|
|
137
|
+
/**
|
|
138
|
+
* Register a generator function for a class alias
|
|
139
|
+
* @param classAlias - The class alias
|
|
140
|
+
* @param generator - The generator function
|
|
141
|
+
*/
|
|
142
|
+
registerClass<Settings, W = any>(classAlias: string, generator: EntityGenerator<D, R, TypeDoc, Settings, W>): void;
|
|
143
|
+
/**
|
|
144
|
+
* Register a {@link BlueprintNode} factory for a node type alias, so a `BlueprintJson`'s
|
|
145
|
+
* `nodes` can reference it by `type` (e.g. the built-in `"RemoveEntity"`, registered by every
|
|
146
|
+
* `LevelLoader` out of the box). Same pattern as {@link registerClass}, one level down (node
|
|
147
|
+
* types within a blueprint graph, rather than entity classes within a level).
|
|
148
|
+
* @param typeAlias - The node type alias
|
|
149
|
+
* @param factory - Builds a node instance from its baked-in settings
|
|
150
|
+
* @param defaultInputPin - This node type's sole "trigger me" input pin name, if it has one
|
|
151
|
+
* canonical one (e.g. `"RemoveEntity"`'s `"entity"`). Enables the node type to be used directly
|
|
152
|
+
* as an `EntityJson.events` binding (`{ "eventName": "TypeAlias" }` or
|
|
153
|
+
* `{ "eventName": { "type": "TypeAlias", "settings": {...} } }`) without declaring a full
|
|
154
|
+
* `BlueprintJson` graph in `blueprints` - see {@link EntityEventBinding}. Omit for a node type
|
|
155
|
+
* with zero or multiple input pins, or one with no single obviously-correct default; it remains
|
|
156
|
+
* usable from a full graph either way.
|
|
157
|
+
*/
|
|
158
|
+
registerBlueprintNode(typeAlias: string, factory: BlueprintNodeFactory<D, R, TypeDoc>, defaultInputPin?: string): void;
|
|
159
|
+
/**
|
|
160
|
+
* Load a level from an already-parsed JSON document. Every `IEntity` the level's entities
|
|
161
|
+
* produce is parented under - and, on failure, torn down along with - the returned
|
|
162
|
+
* {@link GroupEntity}, already added to the world.
|
|
163
|
+
* @param levelJson - The level JSON
|
|
164
|
+
* @param levelName - Optional name for the returned group entity (e.g. so a debugger/console
|
|
165
|
+
* listing entities by name shows something more meaningful than the default auto-generated one)
|
|
166
|
+
* @returns The level's root group entity
|
|
167
|
+
*/
|
|
168
|
+
loadLevel(levelJson: LevelJson, levelName?: string): Promise<GroupEntity<D, R, TypeDoc>>;
|
|
169
|
+
/**
|
|
170
|
+
* Resolve `eventBinding` (see {@link EntityEventBinding}) to a `BlueprintJson`, instantiate a
|
|
171
|
+
* fresh `Blueprint` from it, and subscribe it to `entity[eventName]` so every value that
|
|
172
|
+
* observable emits triggers the blueprint's `"in"` entry point. Wrapped in a
|
|
173
|
+
* `BlueprintBindingEntity` so the subscription (and the blueprint's own node state) is torn down
|
|
174
|
+
* automatically once that entity is disposed - the caller parents the returned entity under the
|
|
175
|
+
* level's group for that reason.
|
|
176
|
+
* @param entity - The entity carrying the observable property
|
|
177
|
+
* @param eventName - Name of the observable property on `entity`
|
|
178
|
+
* @param eventBinding - What to run - a `blueprints` name, a bare node type alias, or `{ type,
|
|
179
|
+
* settings? }`
|
|
180
|
+
* @param blueprints - The level's top-level blueprint map, if any
|
|
181
|
+
* @returns The binding entity to parent under the level, or `undefined` if `eventBinding`
|
|
182
|
+
* couldn't be resolved or the named property isn't an `Observable` (both logged via
|
|
183
|
+
* `console.warn`)
|
|
184
|
+
*/
|
|
185
|
+
private bindEvent;
|
|
186
|
+
/**
|
|
187
|
+
* Turn an `EntityEventBinding` into a `BlueprintJson` to run. An object form (`{ type,
|
|
188
|
+
* settings? }`) always builds a single-node inline graph via {@link inlineNodeBlueprint}. A
|
|
189
|
+
* string form is tried first as a key into `blueprints` (a named, possibly multi-node graph),
|
|
190
|
+
* then - if not found there - as a bare node type alias, same as the object form with no
|
|
191
|
+
* settings.
|
|
192
|
+
* @param eventName - Name of the observable property being bound, for warning messages
|
|
193
|
+
* @param eventBinding - The binding to resolve
|
|
194
|
+
* @param blueprints - The level's top-level blueprint map, if any
|
|
195
|
+
* @returns The resolved graph, or `undefined` (logged via `console.warn`) if it couldn't be
|
|
196
|
+
*/
|
|
197
|
+
private resolveEventBlueprint;
|
|
198
|
+
/**
|
|
199
|
+
* Build a single-node `BlueprintJson` wrapping one blueprint node type, wired so the node's
|
|
200
|
+
* registered default input pin (see {@link registerBlueprintNode}) is reachable as `"in"` - what
|
|
201
|
+
* powers the `EntityEventBinding` shorthand that skips declaring a `blueprints` entry entirely.
|
|
202
|
+
* @param eventName - Name of the observable property being bound, for warning messages
|
|
203
|
+
* @param nodeType - The blueprint node type alias
|
|
204
|
+
* @param settings - Settings to bake into the node, if any
|
|
205
|
+
* @returns The single-node graph, or `undefined` (logged via `console.warn`) if `nodeType` isn't
|
|
206
|
+
* registered, or was registered without a default input pin
|
|
207
|
+
*/
|
|
208
|
+
private inlineNodeBlueprint;
|
|
209
|
+
/**
|
|
210
|
+
* Fetch a level JSON document hosted at `url` and load it, so a whole level/scene can be
|
|
211
|
+
* shipped and consumed as a single static JSON file.
|
|
212
|
+
* @param url - URL (or path) of the level JSON document
|
|
213
|
+
* @param levelName - Optional name for the returned group entity, see {@link loadLevel}
|
|
214
|
+
* @returns The level's root group entity
|
|
215
|
+
*/
|
|
216
|
+
loadLevelFromUrl(url: string, levelName?: string): Promise<GroupEntity<D, R, TypeDoc>>;
|
|
217
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { GroupEntity } from './entities/group.entity';
|
|
11
|
+
import { IEntity, TickOrder } from './entities/i-entity';
|
|
12
|
+
import { Blueprint } from './blueprint/blueprint';
|
|
13
|
+
import { RemoveEntityBlueprintNode } from './blueprint/nodes/remove-entity.node';
|
|
14
|
+
/**
|
|
15
|
+
* Base class for level loaders: parses a {@link LevelJson} document into world entities by
|
|
16
|
+
* dispatching each `EntityJson.class` to a generator function registered with {@link registerClass}.
|
|
17
|
+
*
|
|
18
|
+
* A generator is required to return an `IEntity`. Every `IEntity` a generator produces is parented
|
|
19
|
+
* under one {@link GroupEntity} per `loadLevel`/`loadLevelFromUrl` call (added to the world
|
|
20
|
+
* immediately, and handed back once loading completes) - so a whole level can be torn down in one
|
|
21
|
+
* shot with `world.removeEntity(level, true)`, which cascades removal/disposal to every child, and
|
|
22
|
+
* any named entity can be found afterwards with `level.getChildEntityByName(name)`. If a generator
|
|
23
|
+
* returns anything other than an `IEntity` (including `null`/`undefined`), `loadLevel` logs a
|
|
24
|
+
* `console.warn` and skips that entity - it's never parented, named, or tracked.
|
|
25
|
+
* @template D - The position type
|
|
26
|
+
* @template R - The rotation type
|
|
27
|
+
* @template TypeDoc - The type document repository
|
|
28
|
+
*/
|
|
29
|
+
export class LevelLoader {
|
|
30
|
+
/**
|
|
31
|
+
* Constructor
|
|
32
|
+
* @param world - The world instance
|
|
33
|
+
*/
|
|
34
|
+
constructor(world) {
|
|
35
|
+
this.world = world;
|
|
36
|
+
/**
|
|
37
|
+
* Map of class aliases to generator functions
|
|
38
|
+
*/
|
|
39
|
+
this.generators = new Map();
|
|
40
|
+
/**
|
|
41
|
+
* Map of blueprint node type aliases to node factory functions - see {@link registerBlueprintNode}.
|
|
42
|
+
*/
|
|
43
|
+
this.blueprintNodes = new Map();
|
|
44
|
+
/**
|
|
45
|
+
* Map of blueprint node type aliases to their default input pin name, for node types registered
|
|
46
|
+
* with one - see {@link registerBlueprintNode}.
|
|
47
|
+
*/
|
|
48
|
+
this.blueprintNodeDefaultInputs = new Map();
|
|
49
|
+
this.registerBlueprintNode('RemoveEntity', (w, settings) => new RemoveEntityBlueprintNode(w, settings), 'entity');
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Register a generator function for a class alias
|
|
53
|
+
* @param classAlias - The class alias
|
|
54
|
+
* @param generator - The generator function
|
|
55
|
+
*/
|
|
56
|
+
registerClass(classAlias, generator) {
|
|
57
|
+
this.generators.set(classAlias, generator);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Register a {@link BlueprintNode} factory for a node type alias, so a `BlueprintJson`'s
|
|
61
|
+
* `nodes` can reference it by `type` (e.g. the built-in `"RemoveEntity"`, registered by every
|
|
62
|
+
* `LevelLoader` out of the box). Same pattern as {@link registerClass}, one level down (node
|
|
63
|
+
* types within a blueprint graph, rather than entity classes within a level).
|
|
64
|
+
* @param typeAlias - The node type alias
|
|
65
|
+
* @param factory - Builds a node instance from its baked-in settings
|
|
66
|
+
* @param defaultInputPin - This node type's sole "trigger me" input pin name, if it has one
|
|
67
|
+
* canonical one (e.g. `"RemoveEntity"`'s `"entity"`). Enables the node type to be used directly
|
|
68
|
+
* as an `EntityJson.events` binding (`{ "eventName": "TypeAlias" }` or
|
|
69
|
+
* `{ "eventName": { "type": "TypeAlias", "settings": {...} } }`) without declaring a full
|
|
70
|
+
* `BlueprintJson` graph in `blueprints` - see {@link EntityEventBinding}. Omit for a node type
|
|
71
|
+
* with zero or multiple input pins, or one with no single obviously-correct default; it remains
|
|
72
|
+
* usable from a full graph either way.
|
|
73
|
+
*/
|
|
74
|
+
registerBlueprintNode(typeAlias, factory, defaultInputPin) {
|
|
75
|
+
this.blueprintNodes.set(typeAlias, factory);
|
|
76
|
+
if (defaultInputPin !== undefined) {
|
|
77
|
+
this.blueprintNodeDefaultInputs.set(typeAlias, defaultInputPin);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
this.blueprintNodeDefaultInputs.delete(typeAlias);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Load a level from an already-parsed JSON document. Every `IEntity` the level's entities
|
|
85
|
+
* produce is parented under - and, on failure, torn down along with - the returned
|
|
86
|
+
* {@link GroupEntity}, already added to the world.
|
|
87
|
+
* @param levelJson - The level JSON
|
|
88
|
+
* @param levelName - Optional name for the returned group entity (e.g. so a debugger/console
|
|
89
|
+
* listing entities by name shows something more meaningful than the default auto-generated one)
|
|
90
|
+
* @returns The level's root group entity
|
|
91
|
+
*/
|
|
92
|
+
loadLevel(levelJson, levelName) {
|
|
93
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
94
|
+
const level = new GroupEntity();
|
|
95
|
+
if (levelName !== undefined) {
|
|
96
|
+
level.name = levelName;
|
|
97
|
+
}
|
|
98
|
+
this.world.addEntity(level);
|
|
99
|
+
try {
|
|
100
|
+
for (const entityJson of levelJson.entities) {
|
|
101
|
+
const { class: classAlias, shape, position, rotation, name, config, events } = entityJson;
|
|
102
|
+
const generator = this.generators.get(classAlias);
|
|
103
|
+
if (!generator) {
|
|
104
|
+
console.warn(`No generator registered for class alias "${classAlias}"`);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
const settings = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (config !== null && config !== void 0 ? config : {})), (shape !== undefined ? { shape } : {})), (position !== undefined ? { position } : {})), (rotation !== undefined ? { rotation } : {})), (name !== undefined ? { name } : {}));
|
|
108
|
+
const entity = yield generator(this.world, settings);
|
|
109
|
+
if (!(entity instanceof IEntity)) {
|
|
110
|
+
console.warn(`Generator for class alias "${classAlias}" did not return an IEntity - skipping`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (name !== undefined) {
|
|
114
|
+
entity.name = name;
|
|
115
|
+
}
|
|
116
|
+
// addChildren reparents the entity under level regardless of whether a generator already
|
|
117
|
+
// self-added it to the world (e.g. addPrimitiveRigidBody does) - safe either way.
|
|
118
|
+
level.addChildren(entity);
|
|
119
|
+
if (events) {
|
|
120
|
+
for (const [eventName, eventBinding] of Object.entries(events)) {
|
|
121
|
+
const bindingEntity = this.bindEvent(entity, eventName, eventBinding, levelJson.blueprints);
|
|
122
|
+
if (bindingEntity) {
|
|
123
|
+
level.addChildren(bindingEntity);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (e) {
|
|
130
|
+
// Don't leave a partially-loaded level (and its already-spawned entities) behind if a
|
|
131
|
+
// generator throws partway through - the caller never gets `level` back to clean it up itself.
|
|
132
|
+
this.world.removeEntity(level, true);
|
|
133
|
+
throw e;
|
|
134
|
+
}
|
|
135
|
+
return level;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Resolve `eventBinding` (see {@link EntityEventBinding}) to a `BlueprintJson`, instantiate a
|
|
140
|
+
* fresh `Blueprint` from it, and subscribe it to `entity[eventName]` so every value that
|
|
141
|
+
* observable emits triggers the blueprint's `"in"` entry point. Wrapped in a
|
|
142
|
+
* `BlueprintBindingEntity` so the subscription (and the blueprint's own node state) is torn down
|
|
143
|
+
* automatically once that entity is disposed - the caller parents the returned entity under the
|
|
144
|
+
* level's group for that reason.
|
|
145
|
+
* @param entity - The entity carrying the observable property
|
|
146
|
+
* @param eventName - Name of the observable property on `entity`
|
|
147
|
+
* @param eventBinding - What to run - a `blueprints` name, a bare node type alias, or `{ type,
|
|
148
|
+
* settings? }`
|
|
149
|
+
* @param blueprints - The level's top-level blueprint map, if any
|
|
150
|
+
* @returns The binding entity to parent under the level, or `undefined` if `eventBinding`
|
|
151
|
+
* couldn't be resolved or the named property isn't an `Observable` (both logged via
|
|
152
|
+
* `console.warn`)
|
|
153
|
+
*/
|
|
154
|
+
bindEvent(entity, eventName, eventBinding, blueprints) {
|
|
155
|
+
const blueprintJson = this.resolveEventBlueprint(eventName, eventBinding, blueprints);
|
|
156
|
+
if (!blueprintJson) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
const observable = entity[eventName];
|
|
160
|
+
if (!observable || typeof observable.subscribe !== 'function') {
|
|
161
|
+
console.warn(`Entity has no observable property "${eventName}" to bind a blueprint to`);
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const blueprint = new Blueprint(this.world, blueprintJson, this.blueprintNodes);
|
|
165
|
+
return new BlueprintBindingEntity(blueprint, observable);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Turn an `EntityEventBinding` into a `BlueprintJson` to run. An object form (`{ type,
|
|
169
|
+
* settings? }`) always builds a single-node inline graph via {@link inlineNodeBlueprint}. A
|
|
170
|
+
* string form is tried first as a key into `blueprints` (a named, possibly multi-node graph),
|
|
171
|
+
* then - if not found there - as a bare node type alias, same as the object form with no
|
|
172
|
+
* settings.
|
|
173
|
+
* @param eventName - Name of the observable property being bound, for warning messages
|
|
174
|
+
* @param eventBinding - The binding to resolve
|
|
175
|
+
* @param blueprints - The level's top-level blueprint map, if any
|
|
176
|
+
* @returns The resolved graph, or `undefined` (logged via `console.warn`) if it couldn't be
|
|
177
|
+
*/
|
|
178
|
+
resolveEventBlueprint(eventName, eventBinding, blueprints) {
|
|
179
|
+
if (typeof eventBinding === 'object') {
|
|
180
|
+
return this.inlineNodeBlueprint(eventName, eventBinding.type, eventBinding.settings);
|
|
181
|
+
}
|
|
182
|
+
const named = blueprints === null || blueprints === void 0 ? void 0 : blueprints[eventBinding];
|
|
183
|
+
if (named) {
|
|
184
|
+
return named;
|
|
185
|
+
}
|
|
186
|
+
if (this.blueprintNodes.has(eventBinding)) {
|
|
187
|
+
return this.inlineNodeBlueprint(eventName, eventBinding, undefined);
|
|
188
|
+
}
|
|
189
|
+
console.warn(`No blueprint or blueprint node type named "${eventBinding}" found for event "${eventName}" - skipping`);
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Build a single-node `BlueprintJson` wrapping one blueprint node type, wired so the node's
|
|
194
|
+
* registered default input pin (see {@link registerBlueprintNode}) is reachable as `"in"` - what
|
|
195
|
+
* powers the `EntityEventBinding` shorthand that skips declaring a `blueprints` entry entirely.
|
|
196
|
+
* @param eventName - Name of the observable property being bound, for warning messages
|
|
197
|
+
* @param nodeType - The blueprint node type alias
|
|
198
|
+
* @param settings - Settings to bake into the node, if any
|
|
199
|
+
* @returns The single-node graph, or `undefined` (logged via `console.warn`) if `nodeType` isn't
|
|
200
|
+
* registered, or was registered without a default input pin
|
|
201
|
+
*/
|
|
202
|
+
inlineNodeBlueprint(eventName, nodeType, settings) {
|
|
203
|
+
if (!this.blueprintNodes.has(nodeType)) {
|
|
204
|
+
console.warn(`No blueprint node type registered for "${nodeType}" (event "${eventName}") - skipping`);
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
const inputPin = this.blueprintNodeDefaultInputs.get(nodeType);
|
|
208
|
+
if (!inputPin) {
|
|
209
|
+
console.warn(`Blueprint node type "${nodeType}" has no default input pin registered - event "${eventName}" must ` +
|
|
210
|
+
`reference a full graph declared in "blueprints" instead, addressing the desired pin explicitly`);
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
nodes: [{ id: 'n1', type: nodeType, settings }],
|
|
215
|
+
inputs: { in: { node: 'n1', pin: inputPin } },
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Fetch a level JSON document hosted at `url` and load it, so a whole level/scene can be
|
|
220
|
+
* shipped and consumed as a single static JSON file.
|
|
221
|
+
* @param url - URL (or path) of the level JSON document
|
|
222
|
+
* @param levelName - Optional name for the returned group entity, see {@link loadLevel}
|
|
223
|
+
* @returns The level's root group entity
|
|
224
|
+
*/
|
|
225
|
+
loadLevelFromUrl(url, levelName) {
|
|
226
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
227
|
+
const response = yield fetch(url);
|
|
228
|
+
if (!response.ok) {
|
|
229
|
+
throw new Error(`Failed to load level JSON from "${url}": ${response.status} ${response.statusText}`);
|
|
230
|
+
}
|
|
231
|
+
const levelJson = yield response.json();
|
|
232
|
+
return this.loadLevel(levelJson, levelName);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Plain do-nothing `IEntity` that owns one event-to-blueprint binding created by
|
|
238
|
+
* `LevelLoader.bindEvent`: subscribes to the bound observable on construction, and unsubscribes
|
|
239
|
+
* plus disposes the `Blueprint` on `dispose()`. Parented under the level's group entity like any
|
|
240
|
+
* other level-produced entity, so `world.removeEntity(level, true)` tears the binding down along
|
|
241
|
+
* with the rest of the level - there is nothing else app code needs to do to clean it up.
|
|
242
|
+
* @template D - The position type
|
|
243
|
+
* @template R - The rotation type
|
|
244
|
+
* @template TypeDoc - The type document repository
|
|
245
|
+
*/
|
|
246
|
+
class BlueprintBindingEntity extends IEntity {
|
|
247
|
+
constructor(blueprint, observable) {
|
|
248
|
+
super();
|
|
249
|
+
this.blueprint = blueprint;
|
|
250
|
+
this.tickOrder = TickOrder.CONTROLLERS;
|
|
251
|
+
this.subscription = observable.subscribe(value => this.blueprint.trigger('in', value));
|
|
252
|
+
}
|
|
253
|
+
dispose() {
|
|
254
|
+
this.subscription.unsubscribe();
|
|
255
|
+
this.blueprint.dispose();
|
|
256
|
+
super.dispose();
|
|
257
|
+
}
|
|
258
|
+
}
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.0.
|
|
1
|
+
export declare const VERSION = "0.0.59";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.0.
|
|
1
|
+
export const VERSION = '0.0.59';
|
package/package.json
CHANGED
package/tsconfig.json
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
"extends": "./../../tsconfig.base.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"baseUrl": "./src/",
|
|
5
|
-
"outDir": "./dist/"
|
|
5
|
+
"outDir": "./dist/",
|
|
6
|
+
"rootDir": "./src/",
|
|
7
|
+
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
|
|
6
8
|
},
|
|
7
9
|
"include": ["*.ts", "**/*.ts"],
|
|
8
10
|
"exclude": ["**/*.spec.ts", "node_modules", "dist/**/*", "test/**/*"]
|
|
File without changes
|