cubeforge 0.0.7 → 0.0.8
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/components/Camera2D.d.ts +5 -1
- package/dist/components/Game.d.ts +8 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +135 -42
- package/package.json +1 -1
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
interface Camera2DProps {
|
|
2
2
|
/** String ID of entity to follow */
|
|
3
3
|
followEntity?: string;
|
|
4
|
+
/** Initial camera X position in world space (default 0 = world origin at screen center) */
|
|
5
|
+
x?: number;
|
|
6
|
+
/** Initial camera Y position in world space (default 0 = world origin at screen center) */
|
|
7
|
+
y?: number;
|
|
4
8
|
zoom?: number;
|
|
5
9
|
/** Lerp smoothing factor (0 = instant snap, 0.85 = smooth) */
|
|
6
10
|
smoothing?: number;
|
|
@@ -19,5 +23,5 @@ interface Camera2DProps {
|
|
|
19
23
|
followOffsetX?: number;
|
|
20
24
|
followOffsetY?: number;
|
|
21
25
|
}
|
|
22
|
-
export declare function Camera2D({ followEntity, zoom, smoothing, background, bounds, deadZone, followOffsetX, followOffsetY, }: Camera2DProps): null;
|
|
26
|
+
export declare function Camera2D({ followEntity, x, y, zoom, smoothing, background, bounds, deadZone, followOffsetX, followOffsetY, }: Camera2DProps): null;
|
|
23
27
|
export {};
|
|
@@ -27,6 +27,13 @@ interface GameProps {
|
|
|
27
27
|
deterministic?: boolean;
|
|
28
28
|
/** Seed for the deterministic RNG (default 0). Only used when deterministic=true. */
|
|
29
29
|
seed?: number;
|
|
30
|
+
/**
|
|
31
|
+
* When true, the game loop starts immediately and sprites swap from color → image as
|
|
32
|
+
* they load in the background. When false (default) the loop is held until every
|
|
33
|
+
* sprite that is part of the initial scene has finished loading, so the first frame
|
|
34
|
+
* shown is fully rendered with real assets.
|
|
35
|
+
*/
|
|
36
|
+
asyncAssets?: boolean;
|
|
30
37
|
/** Custom plugins to register after core systems. Each plugin's systems run after Render. */
|
|
31
38
|
plugins?: Plugin[];
|
|
32
39
|
/**
|
|
@@ -41,5 +48,5 @@ interface GameProps {
|
|
|
41
48
|
className?: string;
|
|
42
49
|
children?: React.ReactNode;
|
|
43
50
|
}
|
|
44
|
-
export declare function Game({ width, height, gravity, debug, devtools, scale, deterministic, seed, onReady, plugins, renderer: CustomRenderer, style, className, children, }: GameProps): import("react/jsx-runtime").JSX.Element;
|
|
51
|
+
export declare function Game({ width, height, gravity, debug, devtools, scale, deterministic, seed, asyncAssets, onReady, plugins, renderer: CustomRenderer, style, className, children, }: GameProps): import("react/jsx-runtime").JSX.Element;
|
|
45
52
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -44,7 +44,9 @@ export type { RaycastHit } from '@cubeforge/physics';
|
|
|
44
44
|
export type { InputManager, ActionBindings, InputMap } from '@cubeforge/input';
|
|
45
45
|
export { createInputMap } from '@cubeforge/input';
|
|
46
46
|
export type { BoundInputMap } from './hooks/useInputMap';
|
|
47
|
-
export type { TransformComponent } from '@cubeforge/core';
|
|
47
|
+
export type { TransformComponent, Component } from '@cubeforge/core';
|
|
48
|
+
export { createTransform, createTag } from '@cubeforge/core';
|
|
49
|
+
export { createSprite } from '@cubeforge/renderer';
|
|
48
50
|
export type { RigidBodyComponent } from '@cubeforge/physics';
|
|
49
51
|
export type { BoxColliderComponent } from '@cubeforge/physics';
|
|
50
52
|
export type { CircleColliderComponent } from '@cubeforge/physics';
|
package/dist/index.js
CHANGED
|
@@ -90,7 +90,27 @@ class ECSWorld {
|
|
|
90
90
|
hasComponent(id, type) {
|
|
91
91
|
return this.componentIndex.get(id)?.has(type) ?? false;
|
|
92
92
|
}
|
|
93
|
+
flushDirty() {
|
|
94
|
+
if (this.dirtyAll) {
|
|
95
|
+
this.queryCache.clear();
|
|
96
|
+
this.dirtyAll = false;
|
|
97
|
+
this.dirtyTypes.clear();
|
|
98
|
+
} else if (this.dirtyTypes.size > 0) {
|
|
99
|
+
for (const key of this.queryCache.keys()) {
|
|
100
|
+
if (key === "") {
|
|
101
|
+
this.queryCache.delete(key);
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
const keyTypes = key.split("\x00");
|
|
105
|
+
if (keyTypes.some((t) => this.dirtyTypes.has(t))) {
|
|
106
|
+
this.queryCache.delete(key);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.dirtyTypes.clear();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
93
112
|
query(...types) {
|
|
113
|
+
this.flushDirty();
|
|
94
114
|
const key = types.slice().sort().join("\x00");
|
|
95
115
|
const cached = this.queryCache.get(key);
|
|
96
116
|
if (cached)
|
|
@@ -106,6 +126,7 @@ class ECSWorld {
|
|
|
106
126
|
return result;
|
|
107
127
|
}
|
|
108
128
|
queryOne(...types) {
|
|
129
|
+
this.flushDirty();
|
|
109
130
|
for (const arch of this.archetypes.values()) {
|
|
110
131
|
if (types.every((t) => arch.types.has(t))) {
|
|
111
132
|
if (arch.entities.length > 0)
|
|
@@ -176,22 +197,6 @@ class ECSWorld {
|
|
|
176
197
|
this.systems.splice(idx, 1);
|
|
177
198
|
}
|
|
178
199
|
update(dt) {
|
|
179
|
-
if (this.dirtyAll) {
|
|
180
|
-
this.queryCache.clear();
|
|
181
|
-
} else if (this.dirtyTypes.size > 0) {
|
|
182
|
-
for (const key of this.queryCache.keys()) {
|
|
183
|
-
if (key === "") {
|
|
184
|
-
this.queryCache.delete(key);
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const keyTypes = key.split("\x00");
|
|
188
|
-
if (keyTypes.some((t) => this.dirtyTypes.has(t))) {
|
|
189
|
-
this.queryCache.delete(key);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
this.dirtyAll = false;
|
|
194
|
-
this.dirtyTypes.clear();
|
|
195
200
|
for (const system of this.systems) {
|
|
196
201
|
system.update(this, dt);
|
|
197
202
|
}
|
|
@@ -315,6 +320,7 @@ class EventBus {
|
|
|
315
320
|
// ../core/src/assets/assetManager.ts
|
|
316
321
|
class AssetManager {
|
|
317
322
|
images = new Map;
|
|
323
|
+
imagePromises = new Map;
|
|
318
324
|
audio = new Map;
|
|
319
325
|
audioCtx = null;
|
|
320
326
|
activeSources = new Map;
|
|
@@ -325,21 +331,28 @@ class AssetManager {
|
|
|
325
331
|
return this.audioCtx;
|
|
326
332
|
}
|
|
327
333
|
async loadImage(src) {
|
|
328
|
-
if (this.
|
|
329
|
-
return this.
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
334
|
+
if (this.imagePromises.has(src))
|
|
335
|
+
return this.imagePromises.get(src);
|
|
336
|
+
const promise = (async () => {
|
|
337
|
+
const img = new Image;
|
|
338
|
+
img.src = src;
|
|
339
|
+
try {
|
|
340
|
+
await new Promise((resolve, reject) => {
|
|
341
|
+
img.onload = () => resolve();
|
|
342
|
+
img.onerror = () => reject(new Error(`Failed to load image: ${src}`));
|
|
343
|
+
});
|
|
344
|
+
} catch (err) {
|
|
345
|
+
console.warn(`[Cubeforge] Failed to load image: ${src}`);
|
|
346
|
+
throw err;
|
|
347
|
+
}
|
|
348
|
+
this.images.set(src, img);
|
|
349
|
+
return img;
|
|
350
|
+
})();
|
|
351
|
+
this.imagePromises.set(src, promise);
|
|
352
|
+
return promise;
|
|
353
|
+
}
|
|
354
|
+
async waitForImages() {
|
|
355
|
+
await Promise.allSettled([...this.imagePromises.values()]);
|
|
343
356
|
}
|
|
344
357
|
getImage(src) {
|
|
345
358
|
return this.images.get(src);
|
|
@@ -1995,6 +2008,7 @@ function Game({
|
|
|
1995
2008
|
scale = "none",
|
|
1996
2009
|
deterministic = false,
|
|
1997
2010
|
seed = 0,
|
|
2011
|
+
asyncAssets = false,
|
|
1998
2012
|
onReady,
|
|
1999
2013
|
plugins,
|
|
2000
2014
|
renderer: CustomRenderer,
|
|
@@ -2005,6 +2019,7 @@ function Game({
|
|
|
2005
2019
|
const canvasRef = useRef(null);
|
|
2006
2020
|
const wrapperRef = useRef(null);
|
|
2007
2021
|
const [engine, setEngine] = useState2(null);
|
|
2022
|
+
const [assetsReady, setAssetsReady] = useState2(asyncAssets);
|
|
2008
2023
|
const devtoolsHandle = useRef({ buffer: [] });
|
|
2009
2024
|
useEffect2(() => {
|
|
2010
2025
|
const canvas = canvasRef.current;
|
|
@@ -2058,7 +2073,6 @@ function Game({
|
|
|
2058
2073
|
plugin2.onInit?.(state);
|
|
2059
2074
|
}
|
|
2060
2075
|
}
|
|
2061
|
-
loop.start();
|
|
2062
2076
|
onReady?.({
|
|
2063
2077
|
pause: () => loop.pause(),
|
|
2064
2078
|
resume: () => loop.resume(),
|
|
@@ -2092,6 +2106,25 @@ function Game({
|
|
|
2092
2106
|
resizeObserver?.disconnect();
|
|
2093
2107
|
};
|
|
2094
2108
|
}, []);
|
|
2109
|
+
useEffect2(() => {
|
|
2110
|
+
if (!engine)
|
|
2111
|
+
return;
|
|
2112
|
+
let cancelled = false;
|
|
2113
|
+
if (asyncAssets) {
|
|
2114
|
+
engine.loop.start();
|
|
2115
|
+
setAssetsReady(true);
|
|
2116
|
+
return;
|
|
2117
|
+
}
|
|
2118
|
+
engine.assets.waitForImages().then(() => {
|
|
2119
|
+
if (!cancelled) {
|
|
2120
|
+
engine.loop.start();
|
|
2121
|
+
setAssetsReady(true);
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
return () => {
|
|
2125
|
+
cancelled = true;
|
|
2126
|
+
};
|
|
2127
|
+
}, [engine]);
|
|
2095
2128
|
useEffect2(() => {
|
|
2096
2129
|
engine?.physics.setGravity(gravity);
|
|
2097
2130
|
}, [gravity, engine]);
|
|
@@ -2101,21 +2134,71 @@ function Game({
|
|
|
2101
2134
|
imageRendering: scale === "pixel" ? "pixelated" : undefined,
|
|
2102
2135
|
...style
|
|
2103
2136
|
};
|
|
2104
|
-
const wrapperStyle =
|
|
2137
|
+
const wrapperStyle = {
|
|
2138
|
+
position: "relative",
|
|
2139
|
+
display: "inline-block",
|
|
2140
|
+
...scale === "contain" ? { width, height, overflow: "visible" } : {}
|
|
2141
|
+
};
|
|
2105
2142
|
return /* @__PURE__ */ jsxDEV2(EngineContext.Provider, {
|
|
2106
2143
|
value: engine,
|
|
2107
2144
|
children: [
|
|
2108
2145
|
/* @__PURE__ */ jsxDEV2("div", {
|
|
2109
2146
|
ref: wrapperRef,
|
|
2110
2147
|
style: wrapperStyle,
|
|
2111
|
-
children:
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2148
|
+
children: [
|
|
2149
|
+
/* @__PURE__ */ jsxDEV2("canvas", {
|
|
2150
|
+
ref: canvasRef,
|
|
2151
|
+
width,
|
|
2152
|
+
height,
|
|
2153
|
+
style: canvasStyle,
|
|
2154
|
+
className
|
|
2155
|
+
}, undefined, false, undefined, this),
|
|
2156
|
+
!assetsReady && /* @__PURE__ */ jsxDEV2("div", {
|
|
2157
|
+
style: {
|
|
2158
|
+
position: "absolute",
|
|
2159
|
+
inset: 0,
|
|
2160
|
+
display: "flex",
|
|
2161
|
+
flexDirection: "column",
|
|
2162
|
+
alignItems: "center",
|
|
2163
|
+
justifyContent: "center",
|
|
2164
|
+
background: "#0a0a0f",
|
|
2165
|
+
pointerEvents: "none"
|
|
2166
|
+
},
|
|
2167
|
+
children: [
|
|
2168
|
+
/* @__PURE__ */ jsxDEV2("div", {
|
|
2169
|
+
style: { display: "flex", gap: 6, marginBottom: 12 },
|
|
2170
|
+
children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxDEV2("div", {
|
|
2171
|
+
style: {
|
|
2172
|
+
width: 8,
|
|
2173
|
+
height: 8,
|
|
2174
|
+
borderRadius: "50%",
|
|
2175
|
+
background: "#4fc3f7",
|
|
2176
|
+
animation: "cubeforge-loading-dot 1.2s ease-in-out infinite",
|
|
2177
|
+
animationDelay: `${i * 0.2}s`
|
|
2178
|
+
}
|
|
2179
|
+
}, i, false, undefined, this))
|
|
2180
|
+
}, undefined, false, undefined, this),
|
|
2181
|
+
/* @__PURE__ */ jsxDEV2("span", {
|
|
2182
|
+
style: {
|
|
2183
|
+
fontFamily: "monospace",
|
|
2184
|
+
fontSize: 11,
|
|
2185
|
+
letterSpacing: 3,
|
|
2186
|
+
color: "#37474f"
|
|
2187
|
+
},
|
|
2188
|
+
children: "LOADING"
|
|
2189
|
+
}, undefined, false, undefined, this),
|
|
2190
|
+
/* @__PURE__ */ jsxDEV2("style", {
|
|
2191
|
+
children: `
|
|
2192
|
+
@keyframes cubeforge-loading-dot {
|
|
2193
|
+
0%, 80%, 100% { transform: scale(0.6); opacity: 0.3; }
|
|
2194
|
+
40% { transform: scale(1); opacity: 1; }
|
|
2195
|
+
}
|
|
2196
|
+
`
|
|
2197
|
+
}, undefined, false, undefined, this)
|
|
2198
|
+
]
|
|
2199
|
+
}, undefined, true, undefined, this)
|
|
2200
|
+
]
|
|
2201
|
+
}, undefined, true, undefined, this),
|
|
2119
2202
|
engine && children,
|
|
2120
2203
|
engine && devtools && /* @__PURE__ */ jsxDEV2(DevToolsOverlay, {
|
|
2121
2204
|
handle: devtoolsHandle.current,
|
|
@@ -2247,7 +2330,10 @@ function Sprite({
|
|
|
2247
2330
|
});
|
|
2248
2331
|
engine.ecs.addComponent(entityId, comp);
|
|
2249
2332
|
if (src) {
|
|
2250
|
-
|
|
2333
|
+
const viteEnv = import.meta.env;
|
|
2334
|
+
const base = (viteEnv?.BASE_URL ?? "/").replace(/\/$/, "");
|
|
2335
|
+
const resolvedSrc = base && src.startsWith("/") ? base + src : src;
|
|
2336
|
+
engine.assets.loadImage(resolvedSrc).then((img) => {
|
|
2251
2337
|
const c = engine.ecs.getComponent(entityId, "Sprite");
|
|
2252
2338
|
if (c)
|
|
2253
2339
|
c.image = img;
|
|
@@ -2356,6 +2442,8 @@ function Script({ init, update }) {
|
|
|
2356
2442
|
import { useEffect as useEffect11, useContext as useContext9 } from "react";
|
|
2357
2443
|
function Camera2D({
|
|
2358
2444
|
followEntity,
|
|
2445
|
+
x = 0,
|
|
2446
|
+
y = 0,
|
|
2359
2447
|
zoom = 1,
|
|
2360
2448
|
smoothing = 0,
|
|
2361
2449
|
background = "#1a1a2e",
|
|
@@ -2369,6 +2457,8 @@ function Camera2D({
|
|
|
2369
2457
|
const entityId = engine.ecs.createEntity();
|
|
2370
2458
|
engine.ecs.addComponent(entityId, createCamera2D({
|
|
2371
2459
|
followEntityId: followEntity,
|
|
2460
|
+
x,
|
|
2461
|
+
y,
|
|
2372
2462
|
zoom,
|
|
2373
2463
|
smoothing,
|
|
2374
2464
|
background,
|
|
@@ -3267,6 +3357,9 @@ export {
|
|
|
3267
3357
|
overlapBox,
|
|
3268
3358
|
findByTag,
|
|
3269
3359
|
definePlugin,
|
|
3360
|
+
createTransform,
|
|
3361
|
+
createTag,
|
|
3362
|
+
createSprite,
|
|
3270
3363
|
createInputMap,
|
|
3271
3364
|
createAtlas,
|
|
3272
3365
|
World,
|