@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
|
@@ -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;
|
package/dist/base/gg-world.d.ts
CHANGED
|
@@ -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;
|
package/dist/base/gg-world.js
CHANGED
|
@@ -134,7 +134,13 @@ export class GgWorld {
|
|
|
134
134
|
this.disposed$.complete();
|
|
135
135
|
}
|
|
136
136
|
addEntity(entity) {
|
|
137
|
-
if (
|
|
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);
|
package/dist/base/index.d.ts
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';
|