cubeforge 0.0.1
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/Animation.d.ts +12 -0
- package/dist/components/Animation.js +32 -0
- package/dist/components/BoxCollider.d.ts +10 -0
- package/dist/components/BoxCollider.js +21 -0
- package/dist/components/Camera2D.d.ts +20 -0
- package/dist/components/Camera2D.js +33 -0
- package/dist/components/Checkpoint.d.ts +18 -0
- package/dist/components/Checkpoint.js +36 -0
- package/dist/components/Entity.d.ts +10 -0
- package/dist/components/Entity.js +29 -0
- package/dist/components/Game.d.ts +28 -0
- package/dist/components/Game.js +93 -0
- package/dist/components/MovingPlatform.d.ts +22 -0
- package/dist/components/MovingPlatform.js +28 -0
- package/dist/components/ParallaxLayer.d.ts +28 -0
- package/dist/components/ParallaxLayer.js +51 -0
- package/dist/components/ParticleEmitter.d.ts +26 -0
- package/dist/components/ParticleEmitter.js +44 -0
- package/dist/components/RigidBody.d.ts +11 -0
- package/dist/components/RigidBody.js +13 -0
- package/dist/components/ScreenFlash.d.ts +4 -0
- package/dist/components/ScreenFlash.js +34 -0
- package/dist/components/Script.d.ts +11 -0
- package/dist/components/Script.js +20 -0
- package/dist/components/Sprite.d.ts +22 -0
- package/dist/components/Sprite.js +49 -0
- package/dist/components/SquashStretch.d.ts +8 -0
- package/dist/components/SquashStretch.js +18 -0
- package/dist/components/Tilemap.d.ts +58 -0
- package/dist/components/Tilemap.js +245 -0
- package/dist/components/Transform.d.ts +9 -0
- package/dist/components/Transform.js +24 -0
- package/dist/components/World.d.ts +10 -0
- package/dist/components/World.js +28 -0
- package/dist/components/particlePresets.d.ts +13 -0
- package/dist/components/particlePresets.js +27 -0
- package/dist/components/spriteAtlas.d.ts +8 -0
- package/dist/components/spriteAtlas.js +10 -0
- package/dist/context.d.ts +19 -0
- package/dist/context.js +3 -0
- package/dist/hooks/useEntity.d.ts +2 -0
- package/dist/hooks/useEntity.js +8 -0
- package/dist/hooks/useEvents.d.ts +3 -0
- package/dist/hooks/useEvents.js +15 -0
- package/dist/hooks/useGame.d.ts +2 -0
- package/dist/hooks/useGame.js +8 -0
- package/dist/hooks/useInput.d.ts +2 -0
- package/dist/hooks/useInput.js +8 -0
- package/dist/hooks/usePlatformerController.d.ts +32 -0
- package/dist/hooks/usePlatformerController.js +82 -0
- package/dist/hooks/useTopDownMovement.d.ts +22 -0
- package/dist/hooks/useTopDownMovement.js +46 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +27 -0
- package/dist/systems/debugSystem.d.ts +10 -0
- package/dist/systems/debugSystem.js +104 -0
- package/package.json +37 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { EntityId } from '@cubeforge/core';
|
|
2
|
+
export interface PlatformerControllerOptions {
|
|
3
|
+
/** Horizontal move speed in px/s (default 200) */
|
|
4
|
+
speed?: number;
|
|
5
|
+
/** Upward impulse on jump (default -500) */
|
|
6
|
+
jumpForce?: number;
|
|
7
|
+
/** Max consecutive jumps, e.g. 2 for double jump (default 1) */
|
|
8
|
+
maxJumps?: number;
|
|
9
|
+
/** Seconds after leaving ground the player can still jump (default 0.08) */
|
|
10
|
+
coyoteTime?: number;
|
|
11
|
+
/** Seconds to buffer a jump input before landing (default 0.08) */
|
|
12
|
+
jumpBuffer?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Attaches platformer movement (WASD/Arrows + Space/Up to jump) to an entity.
|
|
16
|
+
* The entity must already have a RigidBody component.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* function Player() {
|
|
20
|
+
* const id = useEntity()
|
|
21
|
+
* usePlatformerController(id, { speed: 220, maxJumps: 2 })
|
|
22
|
+
* return (
|
|
23
|
+
* <Entity id="player">
|
|
24
|
+
* <Transform x={100} y={300} />
|
|
25
|
+
* <Sprite width={28} height={40} color="#4fc3f7" />
|
|
26
|
+
* <RigidBody />
|
|
27
|
+
* <BoxCollider width={26} height={40} />
|
|
28
|
+
* </Entity>
|
|
29
|
+
* )
|
|
30
|
+
* }
|
|
31
|
+
*/
|
|
32
|
+
export declare function usePlatformerController(entityId: EntityId, opts?: PlatformerControllerOptions): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useContext, useEffect } from 'react';
|
|
2
|
+
import { createScript } from '@cubeforge/core';
|
|
3
|
+
import { EngineContext } from '../context';
|
|
4
|
+
/**
|
|
5
|
+
* Attaches platformer movement (WASD/Arrows + Space/Up to jump) to an entity.
|
|
6
|
+
* The entity must already have a RigidBody component.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* function Player() {
|
|
10
|
+
* const id = useEntity()
|
|
11
|
+
* usePlatformerController(id, { speed: 220, maxJumps: 2 })
|
|
12
|
+
* return (
|
|
13
|
+
* <Entity id="player">
|
|
14
|
+
* <Transform x={100} y={300} />
|
|
15
|
+
* <Sprite width={28} height={40} color="#4fc3f7" />
|
|
16
|
+
* <RigidBody />
|
|
17
|
+
* <BoxCollider width={26} height={40} />
|
|
18
|
+
* </Entity>
|
|
19
|
+
* )
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
export function usePlatformerController(entityId, opts = {}) {
|
|
23
|
+
const engine = useContext(EngineContext);
|
|
24
|
+
const { speed = 200, jumpForce = -500, maxJumps = 1, coyoteTime = 0.08, jumpBuffer = 0.08, } = opts;
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const state = { coyoteTimer: 0, jumpBuffer: 0, jumpsLeft: maxJumps };
|
|
27
|
+
const updateFn = (id, world, input, dt) => {
|
|
28
|
+
if (!world.hasEntity(id))
|
|
29
|
+
return;
|
|
30
|
+
const rb = world.getComponent(id, 'RigidBody');
|
|
31
|
+
if (!rb)
|
|
32
|
+
return;
|
|
33
|
+
// Ground tracking
|
|
34
|
+
if (rb.onGround) {
|
|
35
|
+
state.coyoteTimer = coyoteTime;
|
|
36
|
+
state.jumpsLeft = maxJumps;
|
|
37
|
+
}
|
|
38
|
+
else
|
|
39
|
+
state.coyoteTimer = Math.max(0, state.coyoteTimer - dt);
|
|
40
|
+
// Jump buffer
|
|
41
|
+
const jumpPressed = input.isPressed('Space') || input.isPressed('ArrowUp') ||
|
|
42
|
+
input.isPressed('KeyW') || input.isPressed('w');
|
|
43
|
+
if (jumpPressed)
|
|
44
|
+
state.jumpBuffer = jumpBuffer;
|
|
45
|
+
else
|
|
46
|
+
state.jumpBuffer = Math.max(0, state.jumpBuffer - dt);
|
|
47
|
+
// Horizontal movement
|
|
48
|
+
const left = input.isDown('ArrowLeft') || input.isDown('KeyA') || input.isDown('a');
|
|
49
|
+
const right = input.isDown('ArrowRight') || input.isDown('KeyD') || input.isDown('d');
|
|
50
|
+
if (left)
|
|
51
|
+
rb.vx = -speed;
|
|
52
|
+
else if (right)
|
|
53
|
+
rb.vx = speed;
|
|
54
|
+
else
|
|
55
|
+
rb.vx *= rb.onGround ? 0.6 : 0.92;
|
|
56
|
+
// Sprite flip
|
|
57
|
+
const sprite = world.getComponent(id, 'Sprite');
|
|
58
|
+
if (sprite) {
|
|
59
|
+
if (left)
|
|
60
|
+
sprite.flipX = true;
|
|
61
|
+
if (right)
|
|
62
|
+
sprite.flipX = false;
|
|
63
|
+
}
|
|
64
|
+
// Jump
|
|
65
|
+
const canJump = state.coyoteTimer > 0 || state.jumpsLeft > 0;
|
|
66
|
+
if (state.jumpBuffer > 0 && canJump) {
|
|
67
|
+
rb.vy = jumpForce;
|
|
68
|
+
state.jumpsLeft = Math.max(0, state.jumpsLeft - 1);
|
|
69
|
+
state.coyoteTimer = 0;
|
|
70
|
+
state.jumpBuffer = 0;
|
|
71
|
+
}
|
|
72
|
+
// Variable jump height — release early to cut arc
|
|
73
|
+
const jumpHeld = input.isDown('Space') || input.isDown('ArrowUp') ||
|
|
74
|
+
input.isDown('KeyW') || input.isDown('w');
|
|
75
|
+
if (!jumpHeld && rb.vy < -120)
|
|
76
|
+
rb.vy += 800 * dt;
|
|
77
|
+
};
|
|
78
|
+
engine.ecs.addComponent(entityId, createScript(updateFn));
|
|
79
|
+
return () => engine.ecs.removeComponent(entityId, 'Script');
|
|
80
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
81
|
+
}, []);
|
|
82
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { EntityId } from '@cubeforge/core';
|
|
2
|
+
export interface TopDownMovementOptions {
|
|
3
|
+
/** Movement speed in px/s (default 200) */
|
|
4
|
+
speed?: number;
|
|
5
|
+
/** Normalize diagonal movement to avoid faster diagonal movement (default true) */
|
|
6
|
+
normalizeDiagonal?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Attaches 4-directional top-down movement (WASD/Arrows) to an entity.
|
|
10
|
+
* The entity must have a RigidBody with gravityScale=0 for top-down games.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* <Entity id="player">
|
|
14
|
+
* <Transform x={100} y={100} />
|
|
15
|
+
* <Sprite width={24} height={24} color="#4fc3f7" />
|
|
16
|
+
* <RigidBody gravityScale={0} />
|
|
17
|
+
* <BoxCollider width={24} height={24} />
|
|
18
|
+
* </Entity>
|
|
19
|
+
* // In a Script or parent component:
|
|
20
|
+
* useTopDownMovement(playerId, { speed: 180 })
|
|
21
|
+
*/
|
|
22
|
+
export declare function useTopDownMovement(entityId: EntityId, opts?: TopDownMovementOptions): void;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useContext, useEffect } from 'react';
|
|
2
|
+
import { createScript } from '@cubeforge/core';
|
|
3
|
+
import { EngineContext } from '../context';
|
|
4
|
+
/**
|
|
5
|
+
* Attaches 4-directional top-down movement (WASD/Arrows) to an entity.
|
|
6
|
+
* The entity must have a RigidBody with gravityScale=0 for top-down games.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* <Entity id="player">
|
|
10
|
+
* <Transform x={100} y={100} />
|
|
11
|
+
* <Sprite width={24} height={24} color="#4fc3f7" />
|
|
12
|
+
* <RigidBody gravityScale={0} />
|
|
13
|
+
* <BoxCollider width={24} height={24} />
|
|
14
|
+
* </Entity>
|
|
15
|
+
* // In a Script or parent component:
|
|
16
|
+
* useTopDownMovement(playerId, { speed: 180 })
|
|
17
|
+
*/
|
|
18
|
+
export function useTopDownMovement(entityId, opts = {}) {
|
|
19
|
+
const engine = useContext(EngineContext);
|
|
20
|
+
const { speed = 200, normalizeDiagonal = true } = opts;
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
const updateFn = (id, world, input) => {
|
|
23
|
+
if (!world.hasEntity(id))
|
|
24
|
+
return;
|
|
25
|
+
const rb = world.getComponent(id, 'RigidBody');
|
|
26
|
+
if (!rb)
|
|
27
|
+
return;
|
|
28
|
+
const left = (input.isDown('ArrowLeft') || input.isDown('KeyA') || input.isDown('a')) ? -1 : 0;
|
|
29
|
+
const right = (input.isDown('ArrowRight') || input.isDown('KeyD') || input.isDown('d')) ? 1 : 0;
|
|
30
|
+
const up = (input.isDown('ArrowUp') || input.isDown('KeyW') || input.isDown('w')) ? -1 : 0;
|
|
31
|
+
const down = (input.isDown('ArrowDown') || input.isDown('KeyS') || input.isDown('s')) ? 1 : 0;
|
|
32
|
+
let dx = left + right;
|
|
33
|
+
let dy = up + down;
|
|
34
|
+
if (normalizeDiagonal && dx !== 0 && dy !== 0) {
|
|
35
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
36
|
+
dx /= len;
|
|
37
|
+
dy /= len;
|
|
38
|
+
}
|
|
39
|
+
rb.vx = dx * speed;
|
|
40
|
+
rb.vy = dy * speed;
|
|
41
|
+
};
|
|
42
|
+
engine.ecs.addComponent(entityId, createScript(updateFn));
|
|
43
|
+
return () => engine.ecs.removeComponent(entityId, 'Script');
|
|
44
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
45
|
+
}, []);
|
|
46
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export { Game } from './components/Game';
|
|
2
|
+
export { World } from './components/World';
|
|
3
|
+
export { Entity } from './components/Entity';
|
|
4
|
+
export { Transform } from './components/Transform';
|
|
5
|
+
export { Sprite } from './components/Sprite';
|
|
6
|
+
export { RigidBody } from './components/RigidBody';
|
|
7
|
+
export { BoxCollider } from './components/BoxCollider';
|
|
8
|
+
export { Script } from './components/Script';
|
|
9
|
+
export { Camera2D } from './components/Camera2D';
|
|
10
|
+
export { Animation } from './components/Animation';
|
|
11
|
+
export { SquashStretch } from './components/SquashStretch';
|
|
12
|
+
export { ParticleEmitter } from './components/ParticleEmitter';
|
|
13
|
+
export { MovingPlatform } from './components/MovingPlatform';
|
|
14
|
+
export { Checkpoint } from './components/Checkpoint';
|
|
15
|
+
export { Tilemap } from './components/Tilemap';
|
|
16
|
+
export { ParallaxLayer } from './components/ParallaxLayer';
|
|
17
|
+
export { ScreenFlash } from './components/ScreenFlash';
|
|
18
|
+
export type { ScreenFlashHandle } from './components/ScreenFlash';
|
|
19
|
+
export type { TiledObject, TiledLayer } from './components/Tilemap';
|
|
20
|
+
export { useGame } from './hooks/useGame';
|
|
21
|
+
export { useEntity } from './hooks/useEntity';
|
|
22
|
+
export { useInput } from './hooks/useInput';
|
|
23
|
+
export { useEvents, useEvent } from './hooks/useEvents';
|
|
24
|
+
export { usePlatformerController } from './hooks/usePlatformerController';
|
|
25
|
+
export { useTopDownMovement } from './hooks/useTopDownMovement';
|
|
26
|
+
export type { SpriteAtlas } from './components/spriteAtlas';
|
|
27
|
+
export { createAtlas } from './components/spriteAtlas';
|
|
28
|
+
export type { EngineState } from './context';
|
|
29
|
+
export type { GameControls } from './components/Game';
|
|
30
|
+
export type { PlatformerControllerOptions } from './hooks/usePlatformerController';
|
|
31
|
+
export type { TopDownMovementOptions } from './hooks/useTopDownMovement';
|
|
32
|
+
export type { EntityId, ECSWorld, ScriptUpdateFn } from '@cubeforge/core';
|
|
33
|
+
export type { InputManager } from '@cubeforge/input';
|
|
34
|
+
export type { TransformComponent } from '@cubeforge/core';
|
|
35
|
+
export type { RigidBodyComponent } from '@cubeforge/physics';
|
|
36
|
+
export type { BoxColliderComponent } from '@cubeforge/physics';
|
|
37
|
+
export type { SpriteComponent } from '@cubeforge/renderer';
|
|
38
|
+
export type { AnimationStateComponent } from '@cubeforge/renderer';
|
|
39
|
+
export type { SquashStretchComponent } from '@cubeforge/renderer';
|
|
40
|
+
export type { ParticlePoolComponent, Particle } from '@cubeforge/renderer';
|
|
41
|
+
export type { ParallaxLayerComponent } from '@cubeforge/renderer';
|
|
42
|
+
export type { TweenHandle } from '@cubeforge/core';
|
|
43
|
+
export { Ease, tween } from '@cubeforge/core';
|
|
44
|
+
export type { ParticlePreset } from './components/particlePresets';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export { Game } from './components/Game';
|
|
3
|
+
export { World } from './components/World';
|
|
4
|
+
export { Entity } from './components/Entity';
|
|
5
|
+
export { Transform } from './components/Transform';
|
|
6
|
+
export { Sprite } from './components/Sprite';
|
|
7
|
+
export { RigidBody } from './components/RigidBody';
|
|
8
|
+
export { BoxCollider } from './components/BoxCollider';
|
|
9
|
+
export { Script } from './components/Script';
|
|
10
|
+
export { Camera2D } from './components/Camera2D';
|
|
11
|
+
export { Animation } from './components/Animation';
|
|
12
|
+
export { SquashStretch } from './components/SquashStretch';
|
|
13
|
+
export { ParticleEmitter } from './components/ParticleEmitter';
|
|
14
|
+
export { MovingPlatform } from './components/MovingPlatform';
|
|
15
|
+
export { Checkpoint } from './components/Checkpoint';
|
|
16
|
+
export { Tilemap } from './components/Tilemap';
|
|
17
|
+
export { ParallaxLayer } from './components/ParallaxLayer';
|
|
18
|
+
export { ScreenFlash } from './components/ScreenFlash';
|
|
19
|
+
// Hooks
|
|
20
|
+
export { useGame } from './hooks/useGame';
|
|
21
|
+
export { useEntity } from './hooks/useEntity';
|
|
22
|
+
export { useInput } from './hooks/useInput';
|
|
23
|
+
export { useEvents, useEvent } from './hooks/useEvents';
|
|
24
|
+
export { usePlatformerController } from './hooks/usePlatformerController';
|
|
25
|
+
export { useTopDownMovement } from './hooks/useTopDownMovement';
|
|
26
|
+
export { createAtlas } from './components/spriteAtlas';
|
|
27
|
+
export { Ease, tween } from '@cubeforge/core';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { System, ECSWorld } from '@cubeforge/core';
|
|
2
|
+
import type { Canvas2DRenderer } from '@cubeforge/renderer';
|
|
3
|
+
export declare class DebugSystem implements System {
|
|
4
|
+
private readonly renderer;
|
|
5
|
+
private frameCount;
|
|
6
|
+
private lastFpsTime;
|
|
7
|
+
private fps;
|
|
8
|
+
constructor(renderer: Canvas2DRenderer);
|
|
9
|
+
update(world: ECSWorld, dt: number): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export class DebugSystem {
|
|
2
|
+
renderer;
|
|
3
|
+
frameCount = 0;
|
|
4
|
+
lastFpsTime = 0;
|
|
5
|
+
fps = 0;
|
|
6
|
+
constructor(renderer) {
|
|
7
|
+
this.renderer = renderer;
|
|
8
|
+
}
|
|
9
|
+
update(world, dt) {
|
|
10
|
+
const { ctx, canvas } = this.renderer;
|
|
11
|
+
// FPS tracking
|
|
12
|
+
this.frameCount++;
|
|
13
|
+
this.lastFpsTime += dt;
|
|
14
|
+
if (this.lastFpsTime >= 0.5) {
|
|
15
|
+
this.fps = Math.round(this.frameCount / this.lastFpsTime);
|
|
16
|
+
this.frameCount = 0;
|
|
17
|
+
this.lastFpsTime = 0;
|
|
18
|
+
}
|
|
19
|
+
// Get camera for world-space drawing
|
|
20
|
+
const camId = world.queryOne('Camera2D');
|
|
21
|
+
let camX = 0, camY = 0, zoom = 1;
|
|
22
|
+
if (camId !== undefined) {
|
|
23
|
+
const cam = world.getComponent(camId, 'Camera2D');
|
|
24
|
+
camX = cam.x;
|
|
25
|
+
camY = cam.y;
|
|
26
|
+
zoom = cam.zoom;
|
|
27
|
+
}
|
|
28
|
+
// Draw collider wireframes in world space
|
|
29
|
+
ctx.save();
|
|
30
|
+
ctx.translate(canvas.width / 2 - camX * zoom, canvas.height / 2 - camY * zoom);
|
|
31
|
+
ctx.scale(zoom, zoom);
|
|
32
|
+
const lw = 1 / zoom;
|
|
33
|
+
for (const id of world.query('Transform', 'BoxCollider')) {
|
|
34
|
+
const t = world.getComponent(id, 'Transform');
|
|
35
|
+
const c = world.getComponent(id, 'BoxCollider');
|
|
36
|
+
ctx.strokeStyle = c.isTrigger ? 'rgba(255,200,0,0.85)' : 'rgba(0,255,120,0.85)';
|
|
37
|
+
ctx.lineWidth = lw;
|
|
38
|
+
ctx.strokeRect(t.x + c.offsetX - c.width / 2, t.y + c.offsetY - c.height / 2, c.width, c.height);
|
|
39
|
+
// Entity ID label
|
|
40
|
+
ctx.fillStyle = 'rgba(255,255,255,0.5)';
|
|
41
|
+
ctx.font = `${10 / zoom}px monospace`;
|
|
42
|
+
ctx.fillText(String(id), t.x + c.offsetX - c.width / 2 + lw, t.y + c.offsetY - c.height / 2 - lw * 2);
|
|
43
|
+
}
|
|
44
|
+
// Camera bounds visualization (drawn in same world-space context)
|
|
45
|
+
if (camId !== undefined) {
|
|
46
|
+
const camFull = world.getComponent(camId, 'Camera2D');
|
|
47
|
+
if (camFull.bounds) {
|
|
48
|
+
const b = camFull.bounds;
|
|
49
|
+
ctx.strokeStyle = 'rgba(0, 255, 255, 0.4)';
|
|
50
|
+
ctx.lineWidth = 1 / zoom;
|
|
51
|
+
ctx.setLineDash([8 / zoom, 4 / zoom]);
|
|
52
|
+
ctx.strokeRect(b.x, b.y, b.width, b.height);
|
|
53
|
+
ctx.setLineDash([]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
ctx.restore();
|
|
57
|
+
// Physics grid visualization (128px spatial broadphase grid)
|
|
58
|
+
const GRID_SIZE = 128;
|
|
59
|
+
ctx.save();
|
|
60
|
+
ctx.strokeStyle = 'rgba(255, 255, 255, 0.04)';
|
|
61
|
+
ctx.lineWidth = 1;
|
|
62
|
+
ctx.setLineDash([]);
|
|
63
|
+
// Compute visible world-space range
|
|
64
|
+
const offsetX = camX - canvas.width / (2 * zoom);
|
|
65
|
+
const offsetY = camY - canvas.height / (2 * zoom);
|
|
66
|
+
const visibleW = canvas.width / zoom;
|
|
67
|
+
const visibleH = canvas.height / zoom;
|
|
68
|
+
const startCol = Math.floor(offsetX / GRID_SIZE);
|
|
69
|
+
const endCol = Math.ceil((offsetX + visibleW) / GRID_SIZE);
|
|
70
|
+
const startRow = Math.floor(offsetY / GRID_SIZE);
|
|
71
|
+
const endRow = Math.ceil((offsetY + visibleH) / GRID_SIZE);
|
|
72
|
+
ctx.translate(canvas.width / 2 - camX * zoom, canvas.height / 2 - camY * zoom);
|
|
73
|
+
ctx.scale(zoom, zoom);
|
|
74
|
+
for (let col = startCol; col <= endCol; col++) {
|
|
75
|
+
const wx = col * GRID_SIZE;
|
|
76
|
+
ctx.beginPath();
|
|
77
|
+
ctx.moveTo(wx, startRow * GRID_SIZE);
|
|
78
|
+
ctx.lineTo(wx, endRow * GRID_SIZE);
|
|
79
|
+
ctx.stroke();
|
|
80
|
+
}
|
|
81
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
82
|
+
const wy = row * GRID_SIZE;
|
|
83
|
+
ctx.beginPath();
|
|
84
|
+
ctx.moveTo(startCol * GRID_SIZE, wy);
|
|
85
|
+
ctx.lineTo(endCol * GRID_SIZE, wy);
|
|
86
|
+
ctx.stroke();
|
|
87
|
+
}
|
|
88
|
+
ctx.restore();
|
|
89
|
+
// Screen-space HUD
|
|
90
|
+
const entityCount = world.entityCount;
|
|
91
|
+
const physicsCount = world.query('RigidBody', 'BoxCollider').length;
|
|
92
|
+
const renderCount = world.query('Transform', 'Sprite').length;
|
|
93
|
+
ctx.save();
|
|
94
|
+
ctx.fillStyle = 'rgba(0,0,0,0.65)';
|
|
95
|
+
ctx.fillRect(8, 8, 184, 84);
|
|
96
|
+
ctx.fillStyle = '#00ff88';
|
|
97
|
+
ctx.font = '11px monospace';
|
|
98
|
+
ctx.fillText(`FPS ${this.fps}`, 16, 26);
|
|
99
|
+
ctx.fillText(`Entities ${entityCount}`, 16, 42);
|
|
100
|
+
ctx.fillText(`Physics ${physicsCount}`, 16, 58);
|
|
101
|
+
ctx.fillText(`Renderables ${renderCount}`, 16, 74);
|
|
102
|
+
ctx.restore();
|
|
103
|
+
}
|
|
104
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cubeforge",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "React-first 2D browser game engine",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"typecheck": "tsc --noEmit",
|
|
8
|
+
"build": "tsc"
|
|
9
|
+
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"import": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": ["dist"],
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"react": "^18.0.0",
|
|
23
|
+
"react-dom": "^18.0.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@cubeforge/core": "workspace:*",
|
|
27
|
+
"@cubeforge/input": "workspace:*",
|
|
28
|
+
"@cubeforge/renderer": "workspace:*",
|
|
29
|
+
"@cubeforge/physics": "workspace:*"
|
|
30
|
+
},
|
|
31
|
+
"keywords": ["game-engine", "react", "2d", "browser-game", "cubeforge"],
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/1homsi/cubeforge"
|
|
36
|
+
}
|
|
37
|
+
}
|