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.
Files changed (57) hide show
  1. package/dist/components/Animation.d.ts +12 -0
  2. package/dist/components/Animation.js +32 -0
  3. package/dist/components/BoxCollider.d.ts +10 -0
  4. package/dist/components/BoxCollider.js +21 -0
  5. package/dist/components/Camera2D.d.ts +20 -0
  6. package/dist/components/Camera2D.js +33 -0
  7. package/dist/components/Checkpoint.d.ts +18 -0
  8. package/dist/components/Checkpoint.js +36 -0
  9. package/dist/components/Entity.d.ts +10 -0
  10. package/dist/components/Entity.js +29 -0
  11. package/dist/components/Game.d.ts +28 -0
  12. package/dist/components/Game.js +93 -0
  13. package/dist/components/MovingPlatform.d.ts +22 -0
  14. package/dist/components/MovingPlatform.js +28 -0
  15. package/dist/components/ParallaxLayer.d.ts +28 -0
  16. package/dist/components/ParallaxLayer.js +51 -0
  17. package/dist/components/ParticleEmitter.d.ts +26 -0
  18. package/dist/components/ParticleEmitter.js +44 -0
  19. package/dist/components/RigidBody.d.ts +11 -0
  20. package/dist/components/RigidBody.js +13 -0
  21. package/dist/components/ScreenFlash.d.ts +4 -0
  22. package/dist/components/ScreenFlash.js +34 -0
  23. package/dist/components/Script.d.ts +11 -0
  24. package/dist/components/Script.js +20 -0
  25. package/dist/components/Sprite.d.ts +22 -0
  26. package/dist/components/Sprite.js +49 -0
  27. package/dist/components/SquashStretch.d.ts +8 -0
  28. package/dist/components/SquashStretch.js +18 -0
  29. package/dist/components/Tilemap.d.ts +58 -0
  30. package/dist/components/Tilemap.js +245 -0
  31. package/dist/components/Transform.d.ts +9 -0
  32. package/dist/components/Transform.js +24 -0
  33. package/dist/components/World.d.ts +10 -0
  34. package/dist/components/World.js +28 -0
  35. package/dist/components/particlePresets.d.ts +13 -0
  36. package/dist/components/particlePresets.js +27 -0
  37. package/dist/components/spriteAtlas.d.ts +8 -0
  38. package/dist/components/spriteAtlas.js +10 -0
  39. package/dist/context.d.ts +19 -0
  40. package/dist/context.js +3 -0
  41. package/dist/hooks/useEntity.d.ts +2 -0
  42. package/dist/hooks/useEntity.js +8 -0
  43. package/dist/hooks/useEvents.d.ts +3 -0
  44. package/dist/hooks/useEvents.js +15 -0
  45. package/dist/hooks/useGame.d.ts +2 -0
  46. package/dist/hooks/useGame.js +8 -0
  47. package/dist/hooks/useInput.d.ts +2 -0
  48. package/dist/hooks/useInput.js +8 -0
  49. package/dist/hooks/usePlatformerController.d.ts +32 -0
  50. package/dist/hooks/usePlatformerController.js +82 -0
  51. package/dist/hooks/useTopDownMovement.d.ts +22 -0
  52. package/dist/hooks/useTopDownMovement.js +46 -0
  53. package/dist/index.d.ts +44 -0
  54. package/dist/index.js +27 -0
  55. package/dist/systems/debugSystem.d.ts +10 -0
  56. package/dist/systems/debugSystem.js +104 -0
  57. 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
+ }
@@ -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
+ }