@waica/engine 0.3.0
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/LICENSE +21 -0
- package/dist/aabb.d.ts +2 -0
- package/dist/aabb.js +4 -0
- package/dist/animation/clip-player.d.ts +20 -0
- package/dist/animation/clip-player.js +24 -0
- package/dist/animation/contract.d.ts +21 -0
- package/dist/animation/contract.js +23 -0
- package/dist/animation/sheet.d.ts +65 -0
- package/dist/animation/sheet.js +44 -0
- package/dist/archetype.d.ts +37 -0
- package/dist/archetype.js +1 -0
- package/dist/camera.d.ts +71 -0
- package/dist/camera.js +59 -0
- package/dist/collision-shape.d.ts +26 -0
- package/dist/collision-shape.js +136 -0
- package/dist/component-registry.d.ts +16 -0
- package/dist/component-registry.js +44 -0
- package/dist/component.d.ts +58 -0
- package/dist/component.js +11 -0
- package/dist/components/animated-sprite.d.ts +80 -0
- package/dist/components/animated-sprite.js +210 -0
- package/dist/components/dynamic-body.d.ts +68 -0
- package/dist/components/dynamic-body.js +189 -0
- package/dist/components/hitbox.d.ts +25 -0
- package/dist/components/hitbox.js +21 -0
- package/dist/components/solid.d.ts +32 -0
- package/dist/components/solid.js +45 -0
- package/dist/components/sprite.d.ts +51 -0
- package/dist/components/sprite.js +112 -0
- package/dist/entity.d.ts +22 -0
- package/dist/entity.js +52 -0
- package/dist/events.d.ts +8 -0
- package/dist/events.js +20 -0
- package/dist/game.d.ts +95 -0
- package/dist/game.js +263 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +26 -0
- package/dist/input.d.ts +40 -0
- package/dist/input.js +84 -0
- package/dist/scene.d.ts +80 -0
- package/dist/scene.js +93 -0
- package/dist/solid-axis.d.ts +21 -0
- package/dist/solid-axis.js +77 -0
- package/dist/state/hooks.d.ts +90 -0
- package/dist/state/hooks.js +81 -0
- package/dist/state/state-machine.d.ts +83 -0
- package/dist/state/state-machine.js +173 -0
- package/dist/stats.d.ts +27 -0
- package/dist/stats.js +54 -0
- package/dist/ui.d.ts +47 -0
- package/dist/ui.js +175 -0
- package/package.json +32 -0
package/dist/ui.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTML UI layer. Each piece is a self-contained HTML fragment
|
|
3
|
+
* (markup + <style>) that only DRAWS: it declares which stats it shows
|
|
4
|
+
* with {{stat}} placeholders and positions itself with its own CSS.
|
|
5
|
+
* Behaviour always comes from outside — code toggles pieces with
|
|
6
|
+
* show/hide and wires interactivity through element().
|
|
7
|
+
*
|
|
8
|
+
* Pieces mount inside a transparent overlay that covers the game canvas
|
|
9
|
+
* (each in its own shadow root, so styles never leak between pieces or
|
|
10
|
+
* into the hosting page). The whole overlay hides while the game is not
|
|
11
|
+
* simulating (pause / editor edit mode).
|
|
12
|
+
*/
|
|
13
|
+
export class GameUi {
|
|
14
|
+
stats;
|
|
15
|
+
host;
|
|
16
|
+
sources = new Map();
|
|
17
|
+
pieces = new Map();
|
|
18
|
+
overlay;
|
|
19
|
+
active = true;
|
|
20
|
+
constructor(stats,
|
|
21
|
+
/** Resolved lazily: the canvas may not be in the DOM at construction. */
|
|
22
|
+
host) {
|
|
23
|
+
this.stats = stats;
|
|
24
|
+
this.host = host;
|
|
25
|
+
}
|
|
26
|
+
/** Registers a piece's HTML source. Re-defining an unmounted name wins. */
|
|
27
|
+
define(name, html) {
|
|
28
|
+
this.sources.set(name, html);
|
|
29
|
+
}
|
|
30
|
+
defineAll(pieces) {
|
|
31
|
+
for (const [name, html] of Object.entries(pieces))
|
|
32
|
+
this.define(name, html);
|
|
33
|
+
}
|
|
34
|
+
/** Piece names available to show (defined via the registry or define()). */
|
|
35
|
+
names() {
|
|
36
|
+
return [...this.sources.keys()];
|
|
37
|
+
}
|
|
38
|
+
show(name) {
|
|
39
|
+
const piece = this.mount(name);
|
|
40
|
+
if (piece)
|
|
41
|
+
piece.visible = true;
|
|
42
|
+
this.sync();
|
|
43
|
+
}
|
|
44
|
+
hide(name) {
|
|
45
|
+
const piece = this.pieces.get(name);
|
|
46
|
+
if (piece)
|
|
47
|
+
piece.visible = false;
|
|
48
|
+
this.sync();
|
|
49
|
+
}
|
|
50
|
+
toggle(name) {
|
|
51
|
+
if (this.isVisible(name))
|
|
52
|
+
this.hide(name);
|
|
53
|
+
else
|
|
54
|
+
this.show(name);
|
|
55
|
+
}
|
|
56
|
+
isVisible(name) {
|
|
57
|
+
return this.pieces.get(name)?.visible ?? false;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The piece's DOM root — the escape hatch that keeps pieces logic-free:
|
|
61
|
+
* behaviour is wired from code (element(...).querySelector + listeners).
|
|
62
|
+
* Mounts the piece hidden if it wasn't mounted yet.
|
|
63
|
+
*/
|
|
64
|
+
element(name) {
|
|
65
|
+
return this.mount(name)?.root ?? null;
|
|
66
|
+
}
|
|
67
|
+
/** Called by the game loop: the overlay only draws while simulating. */
|
|
68
|
+
setActive(active) {
|
|
69
|
+
if (this.active === active)
|
|
70
|
+
return;
|
|
71
|
+
this.active = active;
|
|
72
|
+
this.sync();
|
|
73
|
+
}
|
|
74
|
+
/** Unmounts every piece and removes the overlay (Game.dispose). */
|
|
75
|
+
dispose() {
|
|
76
|
+
for (const piece of this.pieces.values()) {
|
|
77
|
+
for (const off of piece.unsubs)
|
|
78
|
+
off();
|
|
79
|
+
}
|
|
80
|
+
this.pieces.clear();
|
|
81
|
+
this.overlay?.remove();
|
|
82
|
+
this.overlay = undefined;
|
|
83
|
+
}
|
|
84
|
+
mount(name) {
|
|
85
|
+
const existing = this.pieces.get(name);
|
|
86
|
+
if (existing)
|
|
87
|
+
return existing;
|
|
88
|
+
const html = this.sources.get(name);
|
|
89
|
+
if (html == null) {
|
|
90
|
+
console.warn(`[waica] unknown ui piece: "${name}"`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
// Same geometry as the overlay, so the piece's own CSS positions
|
|
94
|
+
// against the full canvas. pointer-events stays off unless the piece's
|
|
95
|
+
// CSS opts in (e.g. a button with pointer-events:auto).
|
|
96
|
+
const shell = document.createElement('div');
|
|
97
|
+
shell.style.cssText = 'position:absolute;inset:0;pointer-events:none';
|
|
98
|
+
const shadow = shell.attachShadow({ mode: 'open' });
|
|
99
|
+
const root = document.createElement('div');
|
|
100
|
+
root.style.display = 'contents';
|
|
101
|
+
root.innerHTML = html;
|
|
102
|
+
shadow.append(root);
|
|
103
|
+
const piece = { shell, root, visible: false, unsubs: bindStats(root, this.stats) };
|
|
104
|
+
this.mountOverlay().append(shell);
|
|
105
|
+
this.pieces.set(name, piece);
|
|
106
|
+
return piece;
|
|
107
|
+
}
|
|
108
|
+
mountOverlay() {
|
|
109
|
+
if (this.overlay)
|
|
110
|
+
return this.overlay;
|
|
111
|
+
const overlay = document.createElement('div');
|
|
112
|
+
overlay.style.cssText = 'position:absolute;inset:0;z-index:9000;pointer-events:none';
|
|
113
|
+
const host = this.host();
|
|
114
|
+
if (getComputedStyle(host).position === 'static')
|
|
115
|
+
host.style.position = 'relative';
|
|
116
|
+
host.append(overlay);
|
|
117
|
+
this.overlay = overlay;
|
|
118
|
+
this.sync();
|
|
119
|
+
return overlay;
|
|
120
|
+
}
|
|
121
|
+
sync() {
|
|
122
|
+
if (this.overlay)
|
|
123
|
+
this.overlay.style.display = this.active ? '' : 'none';
|
|
124
|
+
for (const piece of this.pieces.values()) {
|
|
125
|
+
piece.shell.style.display = piece.visible ? '' : 'none';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const BINDING = /\{\{\s*([\w-]+)\s*\}\}/g;
|
|
130
|
+
function renderStat(value) {
|
|
131
|
+
if (value === undefined)
|
|
132
|
+
return '';
|
|
133
|
+
if (typeof value === 'boolean')
|
|
134
|
+
return value ? '✓' : '✕';
|
|
135
|
+
return String(value);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Replaces {{stat}} placeholders in the fragment's text with reactive text
|
|
139
|
+
* nodes kept in sync with the stats. Text-only by design: the binding
|
|
140
|
+
* language has no expressions — presentation, never logic.
|
|
141
|
+
*/
|
|
142
|
+
function bindStats(root, stats) {
|
|
143
|
+
const unsubs = [];
|
|
144
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
145
|
+
const targets = [];
|
|
146
|
+
for (let node = walker.nextNode(); node; node = walker.nextNode()) {
|
|
147
|
+
// Braces inside <style>/<script> are CSS/code, not bindings.
|
|
148
|
+
if (node.parentElement?.closest('style, script'))
|
|
149
|
+
continue;
|
|
150
|
+
if ((node.nodeValue ?? '').includes('{{'))
|
|
151
|
+
targets.push(node);
|
|
152
|
+
}
|
|
153
|
+
for (const text of targets) {
|
|
154
|
+
const source = text.nodeValue ?? '';
|
|
155
|
+
const parts = [];
|
|
156
|
+
let last = 0;
|
|
157
|
+
for (const match of source.matchAll(BINDING)) {
|
|
158
|
+
const stat = match[1];
|
|
159
|
+
if (stat === undefined)
|
|
160
|
+
continue;
|
|
161
|
+
if (match.index > last)
|
|
162
|
+
parts.push(document.createTextNode(source.slice(last, match.index)));
|
|
163
|
+
const bound = document.createTextNode(renderStat(stats.get(stat)));
|
|
164
|
+
unsubs.push(stats.onChange(stat, (value) => (bound.nodeValue = renderStat(value))));
|
|
165
|
+
parts.push(bound);
|
|
166
|
+
last = match.index + match[0].length;
|
|
167
|
+
}
|
|
168
|
+
if (parts.length === 0)
|
|
169
|
+
continue;
|
|
170
|
+
if (last < source.length)
|
|
171
|
+
parts.push(document.createTextNode(source.slice(last)));
|
|
172
|
+
text.replaceWith(...parts);
|
|
173
|
+
}
|
|
174
|
+
return unsubs;
|
|
175
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@waica/engine",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Waica game engine core — archetype-driven, web-first, 2D & 3D",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/chichex/waica",
|
|
10
|
+
"directory": "packages/engine"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@types/three": "^0.185.1",
|
|
23
|
+
"three": "^0.185.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"vitest": "^4.1.10"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "node ../../scripts/clean-dist.mjs && tsc -p tsconfig.build.json",
|
|
30
|
+
"typecheck": "tsc --noEmit"
|
|
31
|
+
}
|
|
32
|
+
}
|