@simpludify/web 0.1.1 → 0.1.3
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/README.md +3 -3
- package/dist/canvas-visual.d.ts +4 -0
- package/dist/game.d.ts +7 -0
- package/dist/index.js +130 -14
- package/dist/index.js.map +1 -1
- package/dist/renderer.d.ts +1 -0
- package/dist/style.css +10 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# @simpludify/web
|
|
2
2
|
|
|
3
|
-
Experimental v0.1.
|
|
3
|
+
Experimental v0.1.3 browser SDK for Simpludify, a code-first TypeScript 2D engine. APIs may change between 0.x releases. Includes Game, PixiJS rendering, browser input and Canvas/DOM UI adapters; re-exports the host-independent Core API. Core and PixiJS are installed as runtime dependencies, not bundled into this package.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Registry installation (check availability with `npm view @simpludify/web@0.1.3 version`):
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npm install @simpludify/web@0.1.
|
|
8
|
+
npm install @simpludify/web@0.1.3
|
|
9
9
|
npm install --save-dev vite typescript
|
|
10
10
|
```
|
|
11
11
|
|
package/dist/canvas-visual.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare class CanvasVisual implements CanvasPresentation {
|
|
|
18
18
|
private dirty;
|
|
19
19
|
private dead;
|
|
20
20
|
private active;
|
|
21
|
+
private cacheResolution;
|
|
21
22
|
private mirroredFocus;
|
|
22
23
|
private readonly subscriptions;
|
|
23
24
|
private syncs;
|
|
@@ -25,14 +26,17 @@ export declare class CanvasVisual implements CanvasPresentation {
|
|
|
25
26
|
graphics(graphics: Graphics, context: number | undefined): void;
|
|
26
27
|
text(text: Text, key: string | undefined): void;
|
|
27
28
|
changedText(key: string): void;
|
|
29
|
+
cacheResolution(): number | undefined;
|
|
28
30
|
});
|
|
29
31
|
get diagnostics(): {
|
|
30
32
|
nodes: number;
|
|
33
|
+
visibleNodes: number;
|
|
31
34
|
buttons: number;
|
|
32
35
|
syncs: number;
|
|
33
36
|
textMetrics: number;
|
|
34
37
|
};
|
|
35
38
|
private measure;
|
|
39
|
+
private clearCache;
|
|
36
40
|
beforeResolutionChange(): void;
|
|
37
41
|
focus(node: CanvasNode): void;
|
|
38
42
|
private native;
|
package/dist/game.d.ts
CHANGED
|
@@ -44,6 +44,8 @@ export declare class Game<SceneId extends string = string> {
|
|
|
44
44
|
private canvas;
|
|
45
45
|
private canvasPresentation;
|
|
46
46
|
private defaultCanvas;
|
|
47
|
+
private readonly ownedCanvas;
|
|
48
|
+
private readonly viewportBindings;
|
|
47
49
|
get pointer(): ScenePointer;
|
|
48
50
|
get pointerDiagnostics(): {
|
|
49
51
|
captures: number;
|
|
@@ -119,6 +121,11 @@ export declare class Game<SceneId extends string = string> {
|
|
|
119
121
|
private present;
|
|
120
122
|
get viewport(): Viewport;
|
|
121
123
|
get view(): ViewTransform;
|
|
124
|
+
/** Bind the mounted UI's resolved region to the world viewport. UI units become CSS pixels.
|
|
125
|
+
* One binding per UI; replacement/unbind restores the manual viewport. UI/node disposal unbinds.
|
|
126
|
+
* A collapsed/hidden region clips all world rendering and pointer input. No extra frame/observer.
|
|
127
|
+
*/
|
|
128
|
+
bindViewport(ui: CanvasUI, node: CanvasNode): () => void;
|
|
122
129
|
setViewport(viewport?: Viewport): void;
|
|
123
130
|
worldToScreen(point: WorldPoint): ScreenPoint;
|
|
124
131
|
screenToWorld(point: ScreenPoint): WorldPoint;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "./style.css";
|
|
2
2
|
import { ActionController, Aspect, Camera, CanvasButton as CanvasButton$1, CanvasUI, Engine, EngineError, FixedStepRunner, GeometryTilemapLayer, PointerRouting, Renderable, ReplayPlayer, Scene, ScenePointer, SceneState, Shape, TilemapLayer, Transform, canvasControlText, canvasHoverHighlight, canvasOpacity, canvasScrollbars, canvasSliderGeometry, cleanupAll, containsScreenPoint, createViewTransform, imageSize, intersectUI, isKeyCode, positive, screenPoint, screenToWorld, spriteFrameBounds, worldToScreen, wrapCanvasText } from "@simpludify/core";
|
|
3
|
-
import { Application, CanvasTextMetrics, Container, Graphics, Rectangle, Sprite, Text, TextStyle, Texture } from "pixi.js";
|
|
3
|
+
import { Application, CanvasTextMetrics, Container, Graphics, Rectangle, RendererType, Sprite, Text, TextStyle, Texture } from "pixi.js";
|
|
4
4
|
export * from "@simpludify/core";
|
|
5
5
|
// packages/web/src/canvas-node.ts
|
|
6
6
|
/** Bridge owner cancellation to browser APIs such as fetch. */
|
|
@@ -372,6 +372,11 @@ var CanvasNativeText = class extends NativeControl {
|
|
|
372
372
|
this.source = "pointer";
|
|
373
373
|
this.begin();
|
|
374
374
|
}), options);
|
|
375
|
+
el.addEventListener("blur", () => {
|
|
376
|
+
this.composing = false;
|
|
377
|
+
this.session = void 0;
|
|
378
|
+
this.source = "keyboard";
|
|
379
|
+
}, options);
|
|
375
380
|
el.addEventListener("focus", () => this.run(() => this.begin()), options);
|
|
376
381
|
el.addEventListener("compositionstart", () => this.run(() => {
|
|
377
382
|
this.composing = true;
|
|
@@ -522,6 +527,7 @@ var CanvasVisual = class {
|
|
|
522
527
|
dirty = true;
|
|
523
528
|
dead = false;
|
|
524
529
|
active = false;
|
|
530
|
+
cacheResolution;
|
|
525
531
|
mirroredFocus;
|
|
526
532
|
subscriptions = [];
|
|
527
533
|
syncs = 0;
|
|
@@ -576,6 +582,7 @@ var CanvasVisual = class {
|
|
|
576
582
|
get diagnostics() {
|
|
577
583
|
return {
|
|
578
584
|
nodes: this.entries.size,
|
|
585
|
+
visibleNodes: [...this.entries.values()].filter((e) => e.display.visible).length,
|
|
579
586
|
buttons: [...this.entries.values()].filter((e) => e.button).length,
|
|
580
587
|
syncs: this.syncs,
|
|
581
588
|
textMetrics: this.metrics.size
|
|
@@ -619,7 +626,14 @@ var CanvasVisual = class {
|
|
|
619
626
|
cache.values.set(width, result);
|
|
620
627
|
return result;
|
|
621
628
|
};
|
|
629
|
+
clearCache() {
|
|
630
|
+
if (this.cacheResolution === void 0) return;
|
|
631
|
+
this.display.cacheAsTexture(false);
|
|
632
|
+
this.cacheResolution = void 0;
|
|
633
|
+
}
|
|
622
634
|
beforeResolutionChange() {
|
|
635
|
+
this.clearCache();
|
|
636
|
+
this.dirty = true;
|
|
623
637
|
for (const entry of this.entries.values()) if (entry.text && !entry.text.destroyed) this.resources.changedText(entry.text.styleKey);
|
|
624
638
|
}
|
|
625
639
|
focus(node) {
|
|
@@ -748,6 +762,7 @@ var CanvasVisual = class {
|
|
|
748
762
|
const visited = /* @__PURE__ */ new Set(), buttons = [];
|
|
749
763
|
const paint = [];
|
|
750
764
|
let order = 0;
|
|
765
|
+
let scrollVisible = false;
|
|
751
766
|
let restoreNativeFocus;
|
|
752
767
|
this.ui.walk((node, parentClip) => {
|
|
753
768
|
const measuredText = node.kind === "panel" ? void 0 : this.measure(node, Math.max(0, node.bounds.width - node.style.padding * 2));
|
|
@@ -772,6 +787,19 @@ var CanvasVisual = class {
|
|
|
772
787
|
entry.button = entry.native.element;
|
|
773
788
|
} else entry.button = this.button(node);
|
|
774
789
|
this.element.append(entry.button);
|
|
790
|
+
} else if (node.kind === "label") {
|
|
791
|
+
entry.label = document.createElement("span");
|
|
792
|
+
entry.label.className = "s-canvas-label";
|
|
793
|
+
const semantics = node.labelSemantics;
|
|
794
|
+
if (semantics?.role) {
|
|
795
|
+
entry.label.setAttribute("role", semantics.role);
|
|
796
|
+
if (semantics.role === "heading") entry.label.setAttribute("aria-level", String(semantics.level));
|
|
797
|
+
else {
|
|
798
|
+
entry.label.setAttribute("aria-live", semantics.role === "alert" ? "assertive" : "polite");
|
|
799
|
+
entry.label.setAttribute("aria-atomic", "true");
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
this.element.append(entry.label);
|
|
775
803
|
}
|
|
776
804
|
if (node.kind !== "panel") {
|
|
777
805
|
entry.text = new Text({ text: node.text });
|
|
@@ -797,7 +825,14 @@ var CanvasVisual = class {
|
|
|
797
825
|
} : {}
|
|
798
826
|
});
|
|
799
827
|
if (entry.button) buttons.push(entry.button);
|
|
828
|
+
if (entry.label) {
|
|
829
|
+
buttons.push(entry.label);
|
|
830
|
+
if (entry.label.textContent !== node.text) entry.label.textContent = node.text;
|
|
831
|
+
entry.label.setAttribute("aria-hidden", String(canvasOpacity(node) === 0));
|
|
832
|
+
}
|
|
800
833
|
const b = node.bounds, s = node.style;
|
|
834
|
+
const scrollBox = intersectUI(b, clip);
|
|
835
|
+
if (node.style.scroll && scrollBox.width > 0 && scrollBox.height > 0 && canvasOpacity(node) > 0) scrollVisible = true;
|
|
801
836
|
let enabled = !node.busy;
|
|
802
837
|
for (let p = node; p; p = p.parent) enabled &&= p.enabled;
|
|
803
838
|
const signature = JSON.stringify([
|
|
@@ -818,9 +853,12 @@ var CanvasVisual = class {
|
|
|
818
853
|
if (entry.signature === signature) return;
|
|
819
854
|
entry.signature = signature;
|
|
820
855
|
entry.display.position.set(b.x, b.y);
|
|
856
|
+
const paintBox = intersectUI(b, clip);
|
|
857
|
+
const visible = paintBox.width > 0 && paintBox.height > 0 && canvasOpacity(node) > 0;
|
|
858
|
+
entry.display.visible = visible;
|
|
821
859
|
const clipped = clip.x !== 0 || clip.y !== 0 || clip.width !== width / this.ui.scale || clip.height !== height / this.ui.scale;
|
|
822
860
|
entry.display.mask = clipped ? entry.mask : null;
|
|
823
|
-
entry.mask.renderable = clipped;
|
|
861
|
+
entry.mask.renderable = clipped && visible;
|
|
824
862
|
const maskSignature = JSON.stringify([
|
|
825
863
|
clipped,
|
|
826
864
|
clip.x,
|
|
@@ -922,7 +960,8 @@ var CanvasVisual = class {
|
|
|
922
960
|
const display = entry.scrollbar;
|
|
923
961
|
if (!display) return;
|
|
924
962
|
this.display.setChildIndex(display, order++);
|
|
925
|
-
|
|
963
|
+
const viewport = intersectUI(node.bounds, clip);
|
|
964
|
+
display.visible = bars.length > 0 && viewport.width > 0 && viewport.height > 0 && canvasOpacity(node) > 0;
|
|
926
965
|
display.mask = entry.display.mask;
|
|
927
966
|
const signature = JSON.stringify([
|
|
928
967
|
node.style.color,
|
|
@@ -977,6 +1016,14 @@ var CanvasVisual = class {
|
|
|
977
1016
|
const button = focused ? this.entries.get(focused)?.button : void 0;
|
|
978
1017
|
if ((focused !== this.mirroredFocus || focused === restoreNativeFocus) && button && !button.disabled && document.activeElement !== button) button.focus({ preventScroll: true });
|
|
979
1018
|
this.mirroredFocus = this.ui.focusedNode;
|
|
1019
|
+
const ratio = this.resources.cacheResolution();
|
|
1020
|
+
const cache = scrollVisible && ratio !== void 0 && Math.max(width, height) * ratio <= 4096 ? ratio * this.ui.scale : void 0;
|
|
1021
|
+
if (cache !== this.cacheResolution) {
|
|
1022
|
+
this.clearCache();
|
|
1023
|
+
this.cacheResolution = cache;
|
|
1024
|
+
if (cache !== void 0) this.display.cacheAsTexture({ resolution: cache });
|
|
1025
|
+
}
|
|
1026
|
+
if (cache !== void 0) this.display.updateCacheTexture();
|
|
980
1027
|
}
|
|
981
1028
|
releaseEntry(entry) {
|
|
982
1029
|
const backgroundContext = entry.background.context?.uid, maskContext = entry.mask.context?.uid;
|
|
@@ -986,6 +1033,7 @@ var CanvasVisual = class {
|
|
|
986
1033
|
() => entry.abort.abort(),
|
|
987
1034
|
() => entry.native?.dispose(),
|
|
988
1035
|
() => entry.button?.remove(),
|
|
1036
|
+
() => entry.label?.remove(),
|
|
989
1037
|
() => entry.background.destroy({ context: true }),
|
|
990
1038
|
() => entry.scrollbar?.destroy({ context: true }),
|
|
991
1039
|
() => entry.text?.destroy({ style: true }),
|
|
@@ -1010,6 +1058,7 @@ var CanvasVisual = class {
|
|
|
1010
1058
|
cleanupAll([
|
|
1011
1059
|
...this.subscriptions.splice(0),
|
|
1012
1060
|
...entries.map((e) => () => this.releaseEntry(e)),
|
|
1061
|
+
() => this.clearCache(),
|
|
1013
1062
|
() => this.display.destroy({ children: true }),
|
|
1014
1063
|
() => this.element.remove()
|
|
1015
1064
|
], "Canvas visual cleanup failed");
|
|
@@ -1457,10 +1506,11 @@ var PixiRenderer = class {
|
|
|
1457
1506
|
pendingUITextKeys = /* @__PURE__ */ new Set();
|
|
1458
1507
|
canvasPresentations = /* @__PURE__ */ new Set();
|
|
1459
1508
|
get canvasDiagnostics() {
|
|
1460
|
-
let nodes = 0, buttons = 0, syncs = 0, textMetrics = 0;
|
|
1509
|
+
let nodes = 0, visibleNodes = 0, buttons = 0, syncs = 0, textMetrics = 0;
|
|
1461
1510
|
for (const p of this.canvasPresentations) {
|
|
1462
1511
|
const d = p.diagnostics;
|
|
1463
1512
|
nodes += d.nodes;
|
|
1513
|
+
visibleNodes += d.visibleNodes;
|
|
1464
1514
|
buttons += d.buttons;
|
|
1465
1515
|
syncs += d.syncs;
|
|
1466
1516
|
textMetrics += d.textMetrics;
|
|
@@ -1468,6 +1518,7 @@ var PixiRenderer = class {
|
|
|
1468
1518
|
return {
|
|
1469
1519
|
presentations: this.canvasPresentations.size,
|
|
1470
1520
|
nodes,
|
|
1521
|
+
visibleNodes,
|
|
1471
1522
|
buttons,
|
|
1472
1523
|
syncs,
|
|
1473
1524
|
textMetrics
|
|
@@ -1478,7 +1529,8 @@ var PixiRenderer = class {
|
|
|
1478
1529
|
const visual = new CanvasVisual(ui, this.host, routing, {
|
|
1479
1530
|
graphics: (graphics, context) => releaseUIGraphics(this.app.renderer, graphics, context),
|
|
1480
1531
|
text: (text, key) => releaseUIText(this.app.renderer, text, key),
|
|
1481
|
-
changedText: (key) => this.pendingUITextKeys.add(key)
|
|
1532
|
+
changedText: (key) => this.pendingUITextKeys.add(key),
|
|
1533
|
+
cacheResolution: () => this.app.renderer.type === RendererType.WEBGL ? this.app.renderer.resolution : void 0
|
|
1482
1534
|
});
|
|
1483
1535
|
this.canvasPresentations.add(visual);
|
|
1484
1536
|
let disposed = false;
|
|
@@ -2297,6 +2349,8 @@ var Game = class {
|
|
|
2297
2349
|
canvas;
|
|
2298
2350
|
canvasPresentation;
|
|
2299
2351
|
defaultCanvas;
|
|
2352
|
+
ownedCanvas = /* @__PURE__ */ new WeakSet();
|
|
2353
|
+
viewportBindings = /* @__PURE__ */ new WeakMap();
|
|
2300
2354
|
get pointer() {
|
|
2301
2355
|
this.assertAlive();
|
|
2302
2356
|
return this.channelFor(this.engine.scene);
|
|
@@ -2498,6 +2552,7 @@ var Game = class {
|
|
|
2498
2552
|
this.assertAlive();
|
|
2499
2553
|
if (this.startPromise || this.defaultCanvas) throw new EngineError("UI_ALREADY_STARTED", "Define one default Canvas UI before run");
|
|
2500
2554
|
this.defaultCanvas = new CanvasUI(this.engine.scene, root, options);
|
|
2555
|
+
this.ownedCanvas.add(this.defaultCanvas);
|
|
2501
2556
|
return this.defaultCanvas;
|
|
2502
2557
|
}
|
|
2503
2558
|
uiOverlay(...nodes) {
|
|
@@ -2581,6 +2636,7 @@ var Game = class {
|
|
|
2581
2636
|
canvasUI: (root, options) => {
|
|
2582
2637
|
if (canvas) throw new EngineError("UI_OWNERSHIP", "A Scene has one Canvas UI root");
|
|
2583
2638
|
canvas = new CanvasUI(scene, root, options);
|
|
2639
|
+
this.ownedCanvas.add(canvas);
|
|
2584
2640
|
return canvas;
|
|
2585
2641
|
},
|
|
2586
2642
|
actions: (actions, bindings) => new ActionController(actions, {
|
|
@@ -2690,6 +2746,31 @@ var Game = class {
|
|
|
2690
2746
|
this.updateProjection();
|
|
2691
2747
|
return this.projection;
|
|
2692
2748
|
}
|
|
2749
|
+
/** Bind the mounted UI's resolved region to the world viewport. UI units become CSS pixels.
|
|
2750
|
+
* One binding per UI; replacement/unbind restores the manual viewport. UI/node disposal unbinds.
|
|
2751
|
+
* A collapsed/hidden region clips all world rendering and pointer input. No extra frame/observer.
|
|
2752
|
+
*/
|
|
2753
|
+
bindViewport(ui, node) {
|
|
2754
|
+
this.assertAlive();
|
|
2755
|
+
if (!this.ownedCanvas.has(ui) || ui.disposed || node.disposed || node.ui !== ui) throw new EngineError("UI_OWNERSHIP", "Viewport region must belong to this Game's Canvas UI");
|
|
2756
|
+
this.viewportBindings.get(ui)?.dispose();
|
|
2757
|
+
let removed = false;
|
|
2758
|
+
const off = [];
|
|
2759
|
+
const binding = {
|
|
2760
|
+
node,
|
|
2761
|
+
dispose: () => {
|
|
2762
|
+
if (removed) return;
|
|
2763
|
+
removed = true;
|
|
2764
|
+
if (this.viewportBindings.get(ui) === binding) this.viewportBindings.delete(ui);
|
|
2765
|
+
for (const unregister of off) unregister();
|
|
2766
|
+
this.projectionDirty = true;
|
|
2767
|
+
}
|
|
2768
|
+
};
|
|
2769
|
+
this.viewportBindings.set(ui, binding);
|
|
2770
|
+
off.push(ui.onDispose(binding.dispose), node.onDispose(binding.dispose));
|
|
2771
|
+
this.projectionDirty = true;
|
|
2772
|
+
return binding.dispose;
|
|
2773
|
+
}
|
|
2693
2774
|
setViewport(viewport) {
|
|
2694
2775
|
this.assertAlive();
|
|
2695
2776
|
const next = viewport ? { ...viewport } : void 0;
|
|
@@ -2732,23 +2813,59 @@ var Game = class {
|
|
|
2732
2813
|
this.render();
|
|
2733
2814
|
}
|
|
2734
2815
|
updateProjection() {
|
|
2735
|
-
|
|
2736
|
-
|
|
2816
|
+
this.canvasPresentation?.update(this.width, this.height);
|
|
2817
|
+
const ui = this.canvas;
|
|
2818
|
+
const region = ui ? this.viewportBindings.get(ui)?.node : void 0;
|
|
2819
|
+
let v = this.requestedViewport;
|
|
2820
|
+
let empty = false;
|
|
2821
|
+
if (ui && region) {
|
|
2822
|
+
const b = region.bounds, scale = ui.scale;
|
|
2823
|
+
const x = Math.max(0, Math.min(this.width, b.x * scale));
|
|
2824
|
+
const y = Math.max(0, Math.min(this.height, b.y * scale));
|
|
2825
|
+
const right = Math.max(x, Math.min(this.width, (b.x + b.width) * scale));
|
|
2826
|
+
const bottom = Math.max(y, Math.min(this.height, (b.y + b.height) * scale));
|
|
2827
|
+
let visible = region.ui === ui;
|
|
2828
|
+
for (let node = region; node; node = node.parent) if (node.disposed || !node.visible || !node.enabled) visible = false;
|
|
2829
|
+
empty = !visible || right <= x || bottom <= y;
|
|
2830
|
+
v = {
|
|
2831
|
+
x,
|
|
2832
|
+
y,
|
|
2833
|
+
width: empty ? 0 : right - x,
|
|
2834
|
+
height: empty ? 0 : bottom - y
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
const viewport = v ?? {
|
|
2838
|
+
x: 0,
|
|
2839
|
+
y: 0,
|
|
2840
|
+
width: this.width,
|
|
2841
|
+
height: this.height
|
|
2842
|
+
};
|
|
2843
|
+
const previous = this.projection.viewport;
|
|
2844
|
+
const c = this.camera;
|
|
2845
|
+
if (!this.projectionDirty && previous.x === viewport.x && previous.y === viewport.y && previous.width === viewport.width && previous.height === viewport.height && this.cameraX === c.x && this.cameraY === c.y && this.cameraZoom === c.zoom) return;
|
|
2737
2846
|
this.projectionDirty = false;
|
|
2738
2847
|
this.cameraX = c.x;
|
|
2739
2848
|
this.cameraY = c.y;
|
|
2740
2849
|
this.cameraZoom = c.zoom;
|
|
2741
2850
|
this.projection = createViewTransform({
|
|
2742
|
-
viewport:
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
},
|
|
2851
|
+
viewport: empty ? {
|
|
2852
|
+
...viewport,
|
|
2853
|
+
width: 1,
|
|
2854
|
+
height: 1
|
|
2855
|
+
} : viewport,
|
|
2748
2856
|
resolution: this.resolution,
|
|
2749
2857
|
aspect: this.aspect,
|
|
2750
2858
|
camera: c
|
|
2751
2859
|
});
|
|
2860
|
+
if (empty) this.projection = {
|
|
2861
|
+
...this.projection,
|
|
2862
|
+
viewport,
|
|
2863
|
+
clip: {
|
|
2864
|
+
...viewport,
|
|
2865
|
+
width: 0,
|
|
2866
|
+
height: 0
|
|
2867
|
+
}
|
|
2868
|
+
};
|
|
2752
2869
|
}
|
|
2753
2870
|
resize() {
|
|
2754
2871
|
if (this.dead || !this.rendererReady) return;
|
|
@@ -2767,7 +2884,6 @@ var Game = class {
|
|
|
2767
2884
|
}
|
|
2768
2885
|
render() {
|
|
2769
2886
|
this.updateProjection();
|
|
2770
|
-
this.canvasPresentation?.update(this.width, this.height);
|
|
2771
2887
|
this.prepared?.render(this.projection);
|
|
2772
2888
|
}
|
|
2773
2889
|
tick = (time) => {
|