@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.
Files changed (52) hide show
  1. package/dist/2d/components/rendering/i-camera-2d.component.d.ts +5 -0
  2. package/dist/2d/components/rendering/i-visual-scene-2d.component.d.ts +1 -2
  3. package/dist/2d/entities/renderer-2d.entity.d.ts +2 -1
  4. package/dist/2d/gg-2d-world.d.ts +5 -1
  5. package/dist/2d/gg-2d-world.js +4 -2
  6. package/dist/2d/index.d.ts +3 -0
  7. package/dist/2d/index.js +3 -0
  8. package/dist/2d/level-loader.d.ts +101 -0
  9. package/dist/2d/level-loader.js +88 -0
  10. package/dist/2d/loader.d.ts +11 -0
  11. package/dist/2d/loader.js +10 -0
  12. package/dist/3d/components/rendering/{i-camera.component.d.ts → i-camera-3d.component.d.ts} +1 -1
  13. package/dist/3d/components/rendering/i-camera-3d.component.js +1 -0
  14. package/dist/3d/components/rendering/i-renderer-3d.component.d.ts +0 -1
  15. package/dist/3d/components/rendering/i-visual-scene-3d.component.d.ts +1 -2
  16. package/dist/3d/entities/camera-3d.entity.d.ts +24 -0
  17. package/dist/3d/entities/camera-3d.entity.js +35 -0
  18. package/dist/3d/entities/renderer-3d.entity.d.ts +0 -5
  19. package/dist/3d/entities/renderer-3d.entity.js +0 -15
  20. package/dist/3d/gg-3d-world.d.ts +2 -2
  21. package/dist/3d/index.d.ts +3 -1
  22. package/dist/3d/index.js +3 -1
  23. package/dist/3d/level-loader.d.ts +300 -0
  24. package/dist/3d/level-loader.js +267 -0
  25. package/dist/3d/loader.d.ts +44 -3
  26. package/dist/3d/loader.js +31 -3
  27. package/dist/base/blueprint/blueprint-node.d.ts +75 -0
  28. package/dist/base/blueprint/blueprint-node.js +51 -0
  29. package/dist/base/blueprint/blueprint.d.ts +123 -0
  30. package/dist/base/blueprint/blueprint.js +86 -0
  31. package/dist/base/blueprint/nodes/remove-entity.node.d.ts +26 -0
  32. package/dist/base/blueprint/nodes/remove-entity.node.js +29 -0
  33. package/dist/base/components/rendering/i-renderer.component.d.ts +1 -0
  34. package/dist/base/components/rendering/i-visual-scene.component.d.ts +2 -0
  35. package/dist/base/entities/group.entity.d.ts +17 -0
  36. package/dist/base/entities/group.entity.js +19 -0
  37. package/dist/base/entities/i-entity.d.ts +11 -0
  38. package/dist/base/entities/i-entity.js +28 -0
  39. package/dist/base/entities/i-renderer.entity.d.ts +7 -1
  40. package/dist/base/entities/i-renderer.entity.js +15 -0
  41. package/dist/base/gg-world.d.ts +13 -0
  42. package/dist/base/gg-world.js +23 -1
  43. package/dist/base/index.d.ts +5 -0
  44. package/dist/base/index.js +5 -0
  45. package/dist/base/inputs/mouse.input.js +1 -0
  46. package/dist/base/level-loader.d.ts +217 -0
  47. package/dist/base/level-loader.js +258 -0
  48. package/dist/version.d.ts +1 -1
  49. package/dist/version.js +1 -1
  50. package/package.json +1 -1
  51. package/tsconfig.json +3 -1
  52. /package/dist/{3d/components/rendering/i-camera.component.js → 2d/components/rendering/i-camera-2d.component.js} +0 -0
@@ -0,0 +1,75 @@
1
+ import { Observable } from 'rxjs';
2
+ import { GgWorld, GgWorldTypeDocRepo } from '../gg-world';
3
+ /**
4
+ * Whether a {@link BlueprintNode} pin carries an execution pulse ("do this now", no meaningful
5
+ * payload - e.g. Unreal's white exec pins) or a data value ("here's a value", not itself a
6
+ * trigger - e.g. Unreal's colored data pins). Purely descriptive for now (introspection/future
7
+ * editor tooling) - {@link BlueprintNode.trigger} treats every input pin the same way at runtime,
8
+ * since a node is free to both react to and read a value from the same pin (see
9
+ * {@link RemoveEntityBlueprintNode}, whose single `"entity"` pin is a data pin that also triggers
10
+ * the node when fed a value - the same way an Unreal event node's payload pins double as the
11
+ * thing that fires the node).
12
+ */
13
+ export type BlueprintPinKind = 'exec' | 'data';
14
+ /**
15
+ * Declares one named pin a {@link BlueprintNode} exposes - either an input (fed a value via
16
+ * {@link BlueprintNode.trigger}) or an output (fired via {@link BlueprintNode.output}).
17
+ */
18
+ export interface BlueprintPinDefinition {
19
+ name: string;
20
+ kind: BlueprintPinKind;
21
+ }
22
+ /**
23
+ * One node in a {@link Blueprint} graph - the engine's analogue of a single Unreal Blueprint graph
24
+ * node: a small unit of behavior with named input pins that trigger it, named output pins it can
25
+ * fire in response, and a `settings` bag of static (non-pin) configuration baked in from its
26
+ * {@link BlueprintNodeJson} (e.g. `RemoveEntity`'s `dispose` flag). Register a concrete subclass's
27
+ * factory against a type alias via `LevelLoader.registerBlueprintNode` so `Blueprint` can
28
+ * instantiate it from JSON, the same way `LevelLoader.registerClass` works for entity classes.
29
+ * @template D - The position type
30
+ * @template R - The rotation type
31
+ * @template TypeDoc - The type document repository
32
+ */
33
+ export declare abstract class BlueprintNode<D = any, R = any, TypeDoc extends GgWorldTypeDocRepo<D, R> = GgWorldTypeDocRepo<D, R>> {
34
+ protected readonly world: GgWorld<D, R, TypeDoc>;
35
+ protected readonly settings: Record<string, any>;
36
+ /**
37
+ * This node's input pins - see {@link trigger} for how they're fed.
38
+ */
39
+ abstract readonly inputs: readonly BlueprintPinDefinition[];
40
+ /**
41
+ * This node's output pins - see {@link output} for how to observe them.
42
+ */
43
+ abstract readonly outputs: readonly BlueprintPinDefinition[];
44
+ private readonly outputSubjects;
45
+ constructor(world: GgWorld<D, R, TypeDoc>, settings: Record<string, any>);
46
+ private subjectFor;
47
+ /**
48
+ * Observe one of this node's output pins firing - what a {@link BlueprintLinkJson} subscribes
49
+ * to in order to feed a downstream node's input, or what an embedding `Blueprint`'s own
50
+ * `outputs` entry bubbles up to whatever triggered the blueprint.
51
+ * @param name - The output pin's name, per {@link outputs}
52
+ */
53
+ output(name: string): Observable<unknown>;
54
+ /**
55
+ * Fire one of this node's output pins - call from within {@link trigger} once the node has done
56
+ * whatever that output represents. A no-op if nothing is subscribed.
57
+ * @param name - The output pin's name, per {@link outputs}
58
+ * @param value - Payload for a data output pin; omit for a bare exec pulse
59
+ */
60
+ protected emit(name: string, value?: unknown): void;
61
+ /**
62
+ * Feed a value/pulse into one of this node's input pins (per {@link inputs}), running whatever
63
+ * behavior that pin represents. Called by `Blueprint` both for external triggers (via its own
64
+ * `inputs` aliasing) and for links from another node's output pin.
65
+ * @param inputName - The input pin's name, per {@link inputs}
66
+ * @param value - Payload carried in on a data pin, if any
67
+ */
68
+ abstract trigger(inputName: string, value?: unknown): void;
69
+ /**
70
+ * Release anything this node set up outside of its output subjects (timers, subscriptions,
71
+ * etc). Default no-op - override when a node needs it. `Blueprint.dispose` calls this on every
72
+ * node in its graph.
73
+ */
74
+ dispose(): void;
75
+ }
@@ -0,0 +1,51 @@
1
+ import { Subject } from 'rxjs';
2
+ /**
3
+ * One node in a {@link Blueprint} graph - the engine's analogue of a single Unreal Blueprint graph
4
+ * node: a small unit of behavior with named input pins that trigger it, named output pins it can
5
+ * fire in response, and a `settings` bag of static (non-pin) configuration baked in from its
6
+ * {@link BlueprintNodeJson} (e.g. `RemoveEntity`'s `dispose` flag). Register a concrete subclass's
7
+ * factory against a type alias via `LevelLoader.registerBlueprintNode` so `Blueprint` can
8
+ * instantiate it from JSON, the same way `LevelLoader.registerClass` works for entity classes.
9
+ * @template D - The position type
10
+ * @template R - The rotation type
11
+ * @template TypeDoc - The type document repository
12
+ */
13
+ export class BlueprintNode {
14
+ constructor(world, settings) {
15
+ this.world = world;
16
+ this.settings = settings;
17
+ this.outputSubjects = new Map();
18
+ }
19
+ subjectFor(name) {
20
+ let subject = this.outputSubjects.get(name);
21
+ if (!subject) {
22
+ subject = new Subject();
23
+ this.outputSubjects.set(name, subject);
24
+ }
25
+ return subject;
26
+ }
27
+ /**
28
+ * Observe one of this node's output pins firing - what a {@link BlueprintLinkJson} subscribes
29
+ * to in order to feed a downstream node's input, or what an embedding `Blueprint`'s own
30
+ * `outputs` entry bubbles up to whatever triggered the blueprint.
31
+ * @param name - The output pin's name, per {@link outputs}
32
+ */
33
+ output(name) {
34
+ return this.subjectFor(name).asObservable();
35
+ }
36
+ /**
37
+ * Fire one of this node's output pins - call from within {@link trigger} once the node has done
38
+ * whatever that output represents. A no-op if nothing is subscribed.
39
+ * @param name - The output pin's name, per {@link outputs}
40
+ * @param value - Payload for a data output pin; omit for a bare exec pulse
41
+ */
42
+ emit(name, value) {
43
+ this.subjectFor(name).next(value);
44
+ }
45
+ /**
46
+ * Release anything this node set up outside of its output subjects (timers, subscriptions,
47
+ * etc). Default no-op - override when a node needs it. `Blueprint.dispose` calls this on every
48
+ * node in its graph.
49
+ */
50
+ dispose() { }
51
+ }
@@ -0,0 +1,123 @@
1
+ import { Observable } from 'rxjs';
2
+ import { GgWorld, GgWorldTypeDocRepo } from '../gg-world';
3
+ import { BlueprintNode } from './blueprint-node';
4
+ /**
5
+ * A function that builds a {@link BlueprintNode} instance from its baked-in settings. Registered
6
+ * against a node type alias via `LevelLoader.registerBlueprintNode`, the same way
7
+ * {@link EntityGenerator} is registered against an entity class alias via `registerClass`.
8
+ * @template D - The position type
9
+ * @template R - The rotation type
10
+ * @template TypeDoc - The type document repository
11
+ */
12
+ export type BlueprintNodeFactory<D, R, TypeDoc extends GgWorldTypeDocRepo<D, R>> = (world: GgWorld<D, R, TypeDoc>, settings: Record<string, any>) => BlueprintNode<D, R, TypeDoc>;
13
+ /**
14
+ * A reference to one named pin on one node within a {@link BlueprintJson} graph - either end of a
15
+ * {@link BlueprintLinkJson}, or what a graph's own `inputs`/`outputs` entry aliases.
16
+ */
17
+ export interface BlueprintPinRef {
18
+ /**
19
+ * `id` of the node within the same `BlueprintJson.nodes` array
20
+ */
21
+ node: string;
22
+ /**
23
+ * Pin name on that node, per its `BlueprintNode.inputs`/`outputs`
24
+ */
25
+ pin: string;
26
+ }
27
+ /**
28
+ * JSON description of a single node instance within a {@link BlueprintJson} graph.
29
+ */
30
+ export interface BlueprintNodeJson {
31
+ /**
32
+ * Identifier for this node instance, unique within the same graph - referenced by
33
+ * `BlueprintLinkJson`/`BlueprintJson.inputs`/`BlueprintJson.outputs`.
34
+ */
35
+ id: string;
36
+ /**
37
+ * Node type alias, matching a type registered via `LevelLoader.registerBlueprintNode` (e.g. the
38
+ * built-in `"RemoveEntity"`).
39
+ */
40
+ type: string;
41
+ /**
42
+ * Static settings baked into the node (e.g. `RemoveEntity`'s `dispose` flag) - not wired at
43
+ * runtime, unlike a pin.
44
+ */
45
+ settings?: Record<string, any>;
46
+ }
47
+ /**
48
+ * JSON description of one wire connecting one node's output pin to another node's input pin
49
+ * within the same {@link BlueprintJson} graph. Whenever `from` fires, `to` is triggered with
50
+ * whatever value (if any) `from` fired with.
51
+ */
52
+ export interface BlueprintLinkJson {
53
+ from: BlueprintPinRef;
54
+ to: BlueprintPinRef;
55
+ }
56
+ /**
57
+ * A blueprint graph, serializable as a single JSON document - the engine's analogue of an Unreal
58
+ * Blueprint event graph. `nodes` are node instances (see {@link BlueprintNodeJson}), `links` wire
59
+ * one node's output pin to another's input pin, and `inputs`/`outputs` expose named entry/exit
60
+ * points at the graph's own boundary - each aliasing one node's pin - so embedding code (or, in
61
+ * the future, another blueprint nesting this one) doesn't need to know internal node ids. A level
62
+ * JSON references a `BlueprintJson` by name via its top-level `blueprints` map and an entity's
63
+ * `events` mapping - see `gg-engine-level-json`.
64
+ */
65
+ export interface BlueprintJson {
66
+ /**
67
+ * Node instances in this graph
68
+ */
69
+ nodes: BlueprintNodeJson[];
70
+ /**
71
+ * Wires wiring one node's output pin to another node's input pin
72
+ */
73
+ links?: BlueprintLinkJson[];
74
+ /**
75
+ * Named entry points into this graph, each aliasing one node's input pin - e.g.
76
+ * `{ "in": { "node": "n1", "pin": "entity" } }` lets external code trigger `"n1"`'s `"entity"`
77
+ * pin by calling `blueprint.trigger("in", value)` without knowing the internal node id. A
78
+ * blueprint bound to a level JSON entity event is always triggered through the entry named
79
+ * `"in"` - see `gg-engine-level-json`.
80
+ */
81
+ inputs?: Record<string, BlueprintPinRef>;
82
+ /**
83
+ * Named exit points out of this graph, each aliasing one node's output pin - for a future
84
+ * blueprint nested inside a larger graph to bubble one of its own nodes' outputs back out.
85
+ */
86
+ outputs?: Record<string, BlueprintPinRef>;
87
+ }
88
+ /**
89
+ * Runtime instance of a {@link BlueprintJson} graph: builds one {@link BlueprintNode} per
90
+ * `nodes` entry (via the node type registry passed in), wires every `links` entry as a live
91
+ * subscription from the source node's output pin to the target node's input pin, and exposes the
92
+ * graph's own `inputs`/`outputs` boundary. Each binding of a blueprint (e.g. one level JSON entity
93
+ * event) gets its own `Blueprint` instance - and therefore its own node instances - even when
94
+ * multiple bindings reference the same `BlueprintJson` by name, so per-node state (a future timer
95
+ * node's countdown, etc) is never accidentally shared between unrelated bindings.
96
+ * @template D - The position type
97
+ * @template R - The rotation type
98
+ * @template TypeDoc - The type document repository
99
+ */
100
+ export declare class Blueprint<D = any, R = any, TypeDoc extends GgWorldTypeDocRepo<D, R> = GgWorldTypeDocRepo<D, R>> {
101
+ private readonly json;
102
+ private readonly nodes;
103
+ private readonly linkSubscriptions;
104
+ constructor(world: GgWorld<D, R, TypeDoc>, json: BlueprintJson, registry: ReadonlyMap<string, BlueprintNodeFactory<D, R, TypeDoc>>);
105
+ /**
106
+ * Feed a value/pulse into one of this graph's named entry points (per `BlueprintJson.inputs`).
107
+ * A no-op (with a console warning) if no such input, or the node it aliases failed to build.
108
+ * @param inputName - Entry point name, per `BlueprintJson.inputs`
109
+ * @param value - Payload to hand to the aliased node's input pin, if any
110
+ */
111
+ trigger(inputName: string, value?: unknown): void;
112
+ /**
113
+ * Observe one of this graph's named exit points (per `BlueprintJson.outputs`) firing.
114
+ * @param outputName - Exit point name, per `BlueprintJson.outputs`
115
+ * @throws if no such output is declared, or the node it aliases failed to build
116
+ */
117
+ output(outputName: string): Observable<unknown>;
118
+ /**
119
+ * Unwire every link and dispose every node in this graph. Idempotent-ish - safe to call once
120
+ * per `Blueprint` instance, same lifetime contract as `IEntity.dispose`.
121
+ */
122
+ dispose(): void;
123
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Runtime instance of a {@link BlueprintJson} graph: builds one {@link BlueprintNode} per
3
+ * `nodes` entry (via the node type registry passed in), wires every `links` entry as a live
4
+ * subscription from the source node's output pin to the target node's input pin, and exposes the
5
+ * graph's own `inputs`/`outputs` boundary. Each binding of a blueprint (e.g. one level JSON entity
6
+ * event) gets its own `Blueprint` instance - and therefore its own node instances - even when
7
+ * multiple bindings reference the same `BlueprintJson` by name, so per-node state (a future timer
8
+ * node's countdown, etc) is never accidentally shared between unrelated bindings.
9
+ * @template D - The position type
10
+ * @template R - The rotation type
11
+ * @template TypeDoc - The type document repository
12
+ */
13
+ export class Blueprint {
14
+ constructor(world, json, registry) {
15
+ var _a, _b;
16
+ this.json = json;
17
+ this.nodes = new Map();
18
+ this.linkSubscriptions = [];
19
+ for (const nodeJson of json.nodes) {
20
+ const factory = registry.get(nodeJson.type);
21
+ if (!factory) {
22
+ console.warn(`No blueprint node type registered for "${nodeJson.type}" - skipping node "${nodeJson.id}"`);
23
+ continue;
24
+ }
25
+ this.nodes.set(nodeJson.id, factory(world, (_a = nodeJson.settings) !== null && _a !== void 0 ? _a : {}));
26
+ }
27
+ for (const link of (_b = json.links) !== null && _b !== void 0 ? _b : []) {
28
+ const source = this.nodes.get(link.from.node);
29
+ const target = this.nodes.get(link.to.node);
30
+ if (!source || !target) {
31
+ console.warn(`Blueprint link references an unknown node ("${link.from.node}" -> "${link.to.node}") - skipping`);
32
+ continue;
33
+ }
34
+ this.linkSubscriptions.push(source.output(link.from.pin).subscribe(value => target.trigger(link.to.pin, value)));
35
+ }
36
+ }
37
+ /**
38
+ * Feed a value/pulse into one of this graph's named entry points (per `BlueprintJson.inputs`).
39
+ * A no-op (with a console warning) if no such input, or the node it aliases failed to build.
40
+ * @param inputName - Entry point name, per `BlueprintJson.inputs`
41
+ * @param value - Payload to hand to the aliased node's input pin, if any
42
+ */
43
+ trigger(inputName, value) {
44
+ var _a;
45
+ const ref = (_a = this.json.inputs) === null || _a === void 0 ? void 0 : _a[inputName];
46
+ if (!ref) {
47
+ console.warn(`Blueprint has no declared input named "${inputName}" - ignoring trigger`);
48
+ return;
49
+ }
50
+ const node = this.nodes.get(ref.node);
51
+ if (!node) {
52
+ return;
53
+ }
54
+ node.trigger(ref.pin, value);
55
+ }
56
+ /**
57
+ * Observe one of this graph's named exit points (per `BlueprintJson.outputs`) firing.
58
+ * @param outputName - Exit point name, per `BlueprintJson.outputs`
59
+ * @throws if no such output is declared, or the node it aliases failed to build
60
+ */
61
+ output(outputName) {
62
+ var _a;
63
+ const ref = (_a = this.json.outputs) === null || _a === void 0 ? void 0 : _a[outputName];
64
+ if (!ref) {
65
+ throw new Error(`Blueprint has no declared output named "${outputName}"`);
66
+ }
67
+ const node = this.nodes.get(ref.node);
68
+ if (!node) {
69
+ throw new Error(`Blueprint output "${outputName}" references unknown node "${ref.node}"`);
70
+ }
71
+ return node.output(ref.pin);
72
+ }
73
+ /**
74
+ * Unwire every link and dispose every node in this graph. Idempotent-ish - safe to call once
75
+ * per `Blueprint` instance, same lifetime contract as `IEntity.dispose`.
76
+ */
77
+ dispose() {
78
+ for (const sub of this.linkSubscriptions) {
79
+ sub.unsubscribe();
80
+ }
81
+ this.linkSubscriptions.splice(0, this.linkSubscriptions.length);
82
+ for (const node of this.nodes.values()) {
83
+ node.dispose();
84
+ }
85
+ }
86
+ }
@@ -0,0 +1,26 @@
1
+ import { GgWorldTypeDocRepo } from '../../gg-world';
2
+ import { BlueprintNode, BlueprintPinDefinition } from '../blueprint-node';
3
+ /**
4
+ * Settings for the built-in `"RemoveEntity"` blueprint node - baked in from its
5
+ * {@link BlueprintNodeJson.settings}, not wired at runtime.
6
+ */
7
+ export interface RemoveEntityNodeSettings {
8
+ /**
9
+ * Whether to dispose the entity (release its components/children) as well as remove it from
10
+ * the world, same as the `dispose` argument of `GgWorld.removeEntity`. Defaults to `false`.
11
+ */
12
+ dispose?: boolean;
13
+ }
14
+ /**
15
+ * Built-in blueprint node: removes an entity from the world - the blueprint analogue of calling
16
+ * `world.removeEntity(entity, dispose)` directly. Has one input pin, `"entity"` (a data pin that
17
+ * also acts as this node's trigger - feeding it a value runs the node, same as an Unreal event
18
+ * node's payload pin doubling as its exec pulse) and no output pins. Whether the removal also
19
+ * disposes the entity is controlled by the static `dispose` setting, not a pin - see
20
+ * {@link RemoveEntityNodeSettings}.
21
+ */
22
+ export declare class RemoveEntityBlueprintNode<D = any, R = any, TypeDoc extends GgWorldTypeDocRepo<D, R> = GgWorldTypeDocRepo<D, R>> extends BlueprintNode<D, R, TypeDoc> {
23
+ readonly inputs: readonly BlueprintPinDefinition[];
24
+ readonly outputs: readonly BlueprintPinDefinition[];
25
+ trigger(inputName: string, value?: unknown): void;
26
+ }
@@ -0,0 +1,29 @@
1
+ import { IEntity } from '../../entities/i-entity';
2
+ import { BlueprintNode } from '../blueprint-node';
3
+ /**
4
+ * Built-in blueprint node: removes an entity from the world - the blueprint analogue of calling
5
+ * `world.removeEntity(entity, dispose)` directly. Has one input pin, `"entity"` (a data pin that
6
+ * also acts as this node's trigger - feeding it a value runs the node, same as an Unreal event
7
+ * node's payload pin doubling as its exec pulse) and no output pins. Whether the removal also
8
+ * disposes the entity is controlled by the static `dispose` setting, not a pin - see
9
+ * {@link RemoveEntityNodeSettings}.
10
+ */
11
+ export class RemoveEntityBlueprintNode extends BlueprintNode {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.inputs = [{ name: 'entity', kind: 'data' }];
15
+ this.outputs = [];
16
+ }
17
+ trigger(inputName, value) {
18
+ var _a;
19
+ if (inputName !== 'entity') {
20
+ return;
21
+ }
22
+ if (!(value instanceof IEntity)) {
23
+ console.warn('RemoveEntity blueprint node triggered without a valid entity reference - ignoring');
24
+ return;
25
+ }
26
+ const settings = this.settings;
27
+ this.world.removeEntity(value, (_a = settings.dispose) !== null && _a !== void 0 ? _a : false);
28
+ }
29
+ }
@@ -24,6 +24,7 @@ export declare abstract class IRendererComponent<D, R, VTypeDoc extends VisualTy
24
24
  readonly scene: IVisualSceneComponent<D, R, VTypeDoc>;
25
25
  readonly canvas?: HTMLCanvasElement | undefined;
26
26
  entity: IEntity | null;
27
+ abstract camera: VTypeDoc['camera'];
27
28
  /** Specifies the options for the renderer. */
28
29
  readonly rendererOptions: RendererOptions & Partial<VTypeDoc['rendererExtraOpts']>;
29
30
  /** get flag whether renderer shows physics debugger view */
@@ -1,6 +1,8 @@
1
1
  import { IComponent } from '../i-component';
2
2
  import { VisualTypeDocRepo } from '../../gg-world';
3
+ import { RendererOptions } from './i-renderer.component';
3
4
  export interface IVisualSceneComponent<D, R, VTypeDoc extends VisualTypeDocRepo<D, R> = VisualTypeDocRepo<D, R>> extends IComponent {
4
5
  readonly factory: VTypeDoc['factory'];
5
6
  init(): Promise<void>;
7
+ createRenderer(camera: VTypeDoc['camera'], canvas?: HTMLCanvasElement, rendererOptions?: Partial<RendererOptions & VTypeDoc['rendererExtraOpts']>): VTypeDoc['renderer'];
6
8
  }
@@ -0,0 +1,17 @@
1
+ import { GgWorldTypeDocRepo } from '../gg-world';
2
+ import { IEntity, TickOrder } from './i-entity';
3
+ /**
4
+ * A trivial entity with no rendering/physics of its own: it exists purely as a parent/grouping
5
+ * node. `LevelLoader.loadLevel`/`loadLevelFromUrl` hand one back for every loaded level - every
6
+ * entity the level's JSON produced is added as one of its children (see `IEntity.addChildren`),
7
+ * so the whole level can be torn down in a single call:
8
+ * `world.removeEntity(level, true)` cascades removal + disposal to every child
9
+ * (see `IEntity.onRemoved`/`dispose`). Also used internally to group the several entities a single
10
+ * multi-piece GLB load can produce under one name (see the built-in `"Glb"` level entity class).
11
+ * @template D - The position type
12
+ * @template R - The rotation type
13
+ * @template TypeDoc - The type document repository
14
+ */
15
+ export declare class GroupEntity<D = any, R = any, TypeDoc extends GgWorldTypeDocRepo<D, R> = GgWorldTypeDocRepo<D, R>> extends IEntity<D, R, TypeDoc> {
16
+ readonly tickOrder = TickOrder.OBJECTS_BINDING;
17
+ }
@@ -0,0 +1,19 @@
1
+ import { IEntity, TickOrder } from './i-entity';
2
+ /**
3
+ * A trivial entity with no rendering/physics of its own: it exists purely as a parent/grouping
4
+ * node. `LevelLoader.loadLevel`/`loadLevelFromUrl` hand one back for every loaded level - every
5
+ * entity the level's JSON produced is added as one of its children (see `IEntity.addChildren`),
6
+ * so the whole level can be torn down in a single call:
7
+ * `world.removeEntity(level, true)` cascades removal + disposal to every child
8
+ * (see `IEntity.onRemoved`/`dispose`). Also used internally to group the several entities a single
9
+ * multi-piece GLB load can produce under one name (see the built-in `"Glb"` level entity class).
10
+ * @template D - The position type
11
+ * @template R - The rotation type
12
+ * @template TypeDoc - The type document repository
13
+ */
14
+ export class GroupEntity extends IEntity {
15
+ constructor() {
16
+ super(...arguments);
17
+ this.tickOrder = TickOrder.OBJECTS_BINDING;
18
+ }
19
+ }
@@ -42,6 +42,17 @@ export declare abstract class IEntity<D = any, R = any, TypeDoc extends GgWorldT
42
42
  get children(): IEntity[];
43
43
  addChildren(...entities: IEntity[]): void;
44
44
  removeChildren(entities: IEntity[], dispose?: boolean): void;
45
+ /**
46
+ * Find a descendant entity by name, searching this entity's own children and their children
47
+ * recursively (depth-first) - not the whole world, just this entity's subtree. Useful e.g. to
48
+ * pull a specific entity back out of a `GroupEntity` a `LevelLoader` handed back:
49
+ * `level.getChildEntityByName('KillFloor')`.
50
+ * @param name - The descendant entity's `name`
51
+ * @returns The matching descendant
52
+ * @throws if no descendant has that name
53
+ */
54
+ getChildEntityByName<T extends IEntity = IEntity>(name: string): T;
55
+ private findChildEntityByName;
45
56
  private _components;
46
57
  get components(): IWorldComponent<D, R, TypeDoc>[];
47
58
  addComponents(...components: IWorldComponent<D, R, TypeDoc>[]): void;
@@ -74,6 +74,34 @@ export class IEntity {
74
74
  }
75
75
  }
76
76
  }
77
+ /**
78
+ * Find a descendant entity by name, searching this entity's own children and their children
79
+ * recursively (depth-first) - not the whole world, just this entity's subtree. Useful e.g. to
80
+ * pull a specific entity back out of a `GroupEntity` a `LevelLoader` handed back:
81
+ * `level.getChildEntityByName('KillFloor')`.
82
+ * @param name - The descendant entity's `name`
83
+ * @returns The matching descendant
84
+ * @throws if no descendant has that name
85
+ */
86
+ getChildEntityByName(name) {
87
+ const found = this.findChildEntityByName(name);
88
+ if (!found) {
89
+ throw new Error(`No child entity named "${name}" found under "${this.name}"`);
90
+ }
91
+ return found;
92
+ }
93
+ findChildEntityByName(name) {
94
+ for (const child of this._children) {
95
+ if (child.name === name) {
96
+ return child;
97
+ }
98
+ const found = child.findChildEntityByName(name);
99
+ if (found) {
100
+ return found;
101
+ }
102
+ }
103
+ return undefined;
104
+ }
77
105
  get components() {
78
106
  return [...this._components];
79
107
  }
@@ -3,11 +3,12 @@ import { GgWorld, GgWorldTypeDocVPatch, VisualTypeDocRepo } from '../gg-world';
3
3
  import { BehaviorSubject, Observable } from 'rxjs';
4
4
  import { Point2 } from '../models/points';
5
5
  import { RendererOptions } from '../components/rendering/i-renderer.component';
6
+ import { IPositionable } from '../interfaces/i-positionable';
6
7
  /**
7
8
  * Represents a base class for a renderer entity.
8
9
  * @class
9
10
  */
10
- export declare abstract class IRendererEntity<D, R, VTypeDoc extends VisualTypeDocRepo<D, R> = VisualTypeDocRepo<D, R>> extends IEntity<D, R, GgWorldTypeDocVPatch<D, R, VTypeDoc>> {
11
+ export declare abstract class IRendererEntity<D, R, VTypeDoc extends VisualTypeDocRepo<D, R> = VisualTypeDocRepo<D, R>> extends IEntity<D, R, GgWorldTypeDocVPatch<D, R, VTypeDoc>> implements IPositionable<D, R> {
11
12
  readonly renderer: VTypeDoc['renderer'];
12
13
  readonly tickOrder = TickOrder.RENDERING;
13
14
  /** Represents the current size of the renderer. */
@@ -23,6 +24,11 @@ export declare abstract class IRendererEntity<D, R, VTypeDoc extends VisualTypeD
23
24
  */
24
25
  get rendererSize(): Point2 | null;
25
26
  get rendererOptions(): RendererOptions;
27
+ get camera(): VTypeDoc['camera'];
28
+ get position(): D;
29
+ set position(value: D);
30
+ get rotation(): R;
31
+ set rotation(value: R);
26
32
  /** get flag whether renderer shows physics debugger view */
27
33
  get physicsDebugViewActive(): boolean;
28
34
  /** turns on/off physics debugger view for this renderer */
@@ -23,6 +23,21 @@ export class IRendererEntity extends IEntity {
23
23
  get rendererOptions() {
24
24
  return this.renderer.rendererOptions;
25
25
  }
26
+ get camera() {
27
+ return this.renderer.camera;
28
+ }
29
+ get position() {
30
+ return this.camera.position;
31
+ }
32
+ set position(value) {
33
+ this.renderer.camera.position = value;
34
+ }
35
+ get rotation() {
36
+ return this.renderer.camera.rotation;
37
+ }
38
+ set rotation(value) {
39
+ this.renderer.camera.rotation = value;
40
+ }
26
41
  /** get flag whether renderer shows physics debugger view */
27
42
  get physicsDebugViewActive() {
28
43
  return this.renderer.physicsDebugViewActive;
@@ -6,6 +6,7 @@ export type VisualTypeDocRepo<D, R> = {
6
6
  displayObject: IDisplayObjectComponent<D, R>;
7
7
  renderer: IRendererComponent<D, R>;
8
8
  rendererExtraOpts: {};
9
+ camera: IPositionable<D, R>;
9
10
  };
10
11
  export type PhysicsTypeDocRepo<D, R> = {
11
12
  factory: unknown;
@@ -32,6 +33,8 @@ export type GgWorldSceneTypeDocVPatch<D, R, VTypeDoc extends VisualTypeDocRepo2D
32
33
  export type GgWorldSceneTypeDocPPatch<D, R, PTypeDoc extends PhysicsTypeDocRepo<D, R>, PW extends IPhysicsWorldComponent<D, R, PTypeDoc> | null> = Omit<GgWorldSceneTypeRepo<D, R>, 'physicsWorld'> & {
33
34
  physicsWorld: PW;
34
35
  };
36
+ export type TypeDocOf<W extends GgWorld<any, any>> = W extends GgWorld<infer D, infer R, infer TypeDoc> ? TypeDoc : never;
37
+ export type SceneTypeDocOf<W extends GgWorld<any, any>> = W extends GgWorld<infer D, infer R, infer TypeDoc, infer SceneTypeDoc> ? SceneTypeDoc : never;
35
38
  export declare abstract class GgWorld<D, R, TypeDoc extends GgWorldTypeDocRepo<D, R> = GgWorldTypeDocRepo<D, R>, SceneTypeDoc extends GgWorldSceneTypeRepo<D, R, TypeDoc> = GgWorldSceneTypeRepo<D, R, TypeDoc>> {
36
39
  private static default_name_counter;
37
40
  private static _documentWorlds;
@@ -67,6 +70,16 @@ export declare abstract class GgWorld<D, R, TypeDoc extends GgWorldTypeDocRepo<D
67
70
  position?: D, rotation?: R, material?: unknown): IPositionable<D, R> & IRenderableEntity<D, R, TypeDoc>;
68
71
  addEntity(entity: IEntity): void;
69
72
  removeEntity(entity: IEntity, dispose?: boolean): void;
73
+ /**
74
+ * Find an entity anywhere in the world by name. `children` is a flat list of every entity ever
75
+ * added via `addEntity` (nested entities included - `addChildren`/`onSpawned` cascade into it
76
+ * too), so this is a plain linear scan, not a tree walk; to search inside one particular
77
+ * entity's own subtree instead, use `IEntity.getChildEntityByName`.
78
+ * @param name - The entity's `name`
79
+ * @returns The first entity found with that name (insertion order), if more than one shares it
80
+ * @throws if no entity in the world has that name
81
+ */
82
+ getEntityByName<T extends IEntity = IEntity>(name: string): T;
70
83
  private onGgStaticInitialized;
71
84
  protected registerConsoleCommands(ggstatic: {
72
85
  registerConsoleCommand: (world: GgWorld<any, any> | null, command: string, handler: (...args: string[]) => Promise<string>, doc?: string) => void;
@@ -134,7 +134,13 @@ export class GgWorld {
134
134
  this.disposed$.complete();
135
135
  }
136
136
  addEntity(entity) {
137
- if (!!entity.world) {
137
+ if (entity.world === this) {
138
+ // Already a member of this world - e.g. reparented (via addChildren) after having been
139
+ // added directly, as level-loaded entities are. Not an error: just a no-op, since
140
+ // addChildren already updated the parent/children bookkeeping before calling back in here.
141
+ return;
142
+ }
143
+ if (entity.world) {
138
144
  console.warn('Trying to spawn entity, which is already spawned');
139
145
  return;
140
146
  }
@@ -156,6 +162,22 @@ export class GgWorld {
156
162
  entity.dispose();
157
163
  }
158
164
  }
165
+ /**
166
+ * Find an entity anywhere in the world by name. `children` is a flat list of every entity ever
167
+ * added via `addEntity` (nested entities included - `addChildren`/`onSpawned` cascade into it
168
+ * too), so this is a plain linear scan, not a tree walk; to search inside one particular
169
+ * entity's own subtree instead, use `IEntity.getChildEntityByName`.
170
+ * @param name - The entity's `name`
171
+ * @returns The first entity found with that name (insertion order), if more than one shares it
172
+ * @throws if no entity in the world has that name
173
+ */
174
+ getEntityByName(name) {
175
+ const found = this.children.find(e => e.name === name);
176
+ if (!found) {
177
+ throw new Error(`No entity named "${name}" found in the world`);
178
+ }
179
+ return found;
180
+ }
159
181
  onGgStaticInitialized() {
160
182
  window.removeEventListener('ggstatic_added', this.onGgStaticInitialized);
161
183
  this.registerConsoleCommands(window.ggstatic);
@@ -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';