Skip to content

@carrot/engine-framework โ€‹

ts

Entity-component game framework with scene management, pipeline execution, and decorator-driven configuration.

Installation โ€‹

bash
npm install @carrot/engine-framework

Architecture / How It Works โ€‹

The framework follows an entity-component pattern where Entity objects live in Scene containers and Behaviour subclasses provide all functionality.

Core Loop โ€‹

Game owns the main loop. Each frame:

  1. Game systems tick (GameSystem._update)
  2. Pipeline stages dispatch in order: Earliest -> PreUpdate -> Update -> PostUpdate -> PreRender -> Render -> PostRender -> Final
  3. The active scene's entities tick between PreUpdate and Update

Entity-Behaviour Model โ€‹

  • Entity has a transform (Transform2D or Transform3D), a list of behaviours, child entities, and tags.
  • Behaviour is the component. Lifecycle: initialize(engine) (async, load assets) -> start() (first frame) -> update(delta) (every frame) -> dispose() (cleanup).
  • RenderBehaviour extends Behaviour for anything that submits draw calls (must expose mesh, materialInstance, visible).

Scenes โ€‹

Scene manages entity lifecycle. SceneManager handles switching - deactivating the old scene and activating the new one. Scenes support a preload() hook for asset loading before entity initialization.

Pipeline โ€‹

GamePipeline provides ordered execution across named stages. Entries declare before/after dependencies for topological sorting within each stage.

Templates โ€‹

EntityTemplate, BehaviourTemplate, and SceneTemplate are serializable descriptions for prefabs, save/load, and runtime spawning. BehaviourRegistry maps type-name strings to factory functions to instantiate behaviours from templates.

Decorators โ€‹

  • @Requires(...types) - declares sibling behaviour dependencies
  • @ExecutionOrder({ priority, before, after }) - ordering constraints
  • @Pipeline({ stage, before, after }) - pipeline stage registration

Decorator metadata is stored in a WeakMap-based store (setMeta/getMeta).

Dependencies โ€‹

PackageUsed For
@carrot/engineCore engine, canvas, render loop
@carrot/signalsEvent signals on Game, Entity, Scene, SceneManager, GameViewport
@carrot/collections(transitive)
@carrot/maths-geometryTransform2D, Transform3D, AspectRatio, Size2
@carrot/assetsAssetImage, AssetRegistry for asset resolution
@carrot/loggingOptional Log interface
@carrot/engine-renderer(transitive - renderer access via Engine)
@carrot/engine-renderer-graph(transitive)
@carrot/engine-materialsMaterialInstance used in RenderBehaviour
@carrot/engine-meshesMesh used in RenderBehaviour

Build โ€‹

bash
npm run build    # runs tsc

Output goes to dist/. Package is ESM ("type": "module").


Using @carrot/engine-framework โ€‹

Entity-component game framework with scene management, pipeline execution, and decorator-driven configuration.

Import โ€‹

ts
import {
  Game, GameViewport, Entity, Behaviour, Scene, SceneManager,
  GameSystem, GamePipeline, PipelineStage, RenderBehaviour,
  BehaviourRegistry, Requires, ExecutionOrder, Pipeline,
} from '@carrot/engine-framework';

Common Patterns โ€‹

1. Bootstrap a game โ€‹

ts
const game = new Game({
  canvas: document.getElementById('game') as HTMLCanvasElement,
  assetBasePath: '/assets/',
});

const scene = new Scene('Menu');
await game.start(scene);

2. Create entities with behaviours โ€‹

ts
class PlayerController extends Behaviour {
  protected override update(delta: number): void {
    const input = this.engine!.input;
    if (input.keyboard.getButton('KeyW')) {
      this.entity.transform.localPosition.y += 100 * delta;
    }
  }
}

const player = Entity.create2d('Player');
player.addBehaviour(new PlayerController());
scene.addEntity(player);

3. Scene switching โ€‹

ts
class MenuScene extends Scene {
  constructor() { super('Menu'); }

  protected override onEnter(): void {
    // set up menu entities
  }

  protected override onExit(): void {
    // clean up
  }
}

class GameScene extends Scene {
  constructor() { super('Game'); }

  protected override async preload(): Promise<void> {
    // preload level assets
  }
}

// Switch scenes
await game.scenes.switchTo(new GameScene());

// Listen for switches
game.scenes.onSwitch.add(({ previous, current }) => {
  console.log(`Switched from ${previous?.name} to ${current.name}`);
});

4. Game systems (scene-independent) โ€‹

ts
class AudioManager extends GameSystem {
  protected override initialize(engine: Engine): void {
    // set up audio context
  }

  protected override update(delta: number): void {
    // tick audio
  }
}

game.addSystem(new AudioManager());

// Retrieve later
const audio = game.getSystem(AudioManager);

5. Pipeline registration โ€‹

ts
game.pipeline.register({
  fn: (delta) => physics.update(delta),
  stage: PipelineStage.Update,
  name: 'physics',
  before: ['rendering'],
});

game.pipeline.register({
  fn: (delta) => renderer.draw(delta),
  stage: PipelineStage.Render,
  name: 'rendering',
  after: ['physics'],
});

6. Decorators โ€‹

ts
@Requires(SpriteRenderer)
@ExecutionOrder({ after: [InputSystem] })
@Pipeline({ stage: PipelineStage.Update })
class PlayerController extends Behaviour {
  protected override start(): void {
    const renderer = this.requireSibling(SpriteRenderer); // guaranteed by @Requires
  }
}

7. Entity hierarchy and tags โ€‹

ts
const parent = Entity.create2d('Ship');
const turret = Entity.create2d('Turret');
parent.addChild(turret);

parent.addTag('player');
const players = scene.findEntitiesByTag('player');

8. Behaviour registry and templates โ€‹

ts
game.behaviours.register('playerController', () => new PlayerController());

const prefab: EntityTemplate = {
  name: 'Bullet',
  transform: { type: '2d', x: 0, y: 0 },
  behaviours: [
    { type: 'sprite', properties: { image: 'bullet.png' } },
    { type: 'playerController', properties: {} },
  ],
};

9. Viewport, aspect ratio, and display info โ€‹

ts
import { AspectRatio } from '@carrot/maths-geometry';

const game = new Game({
  canvas,
  aspectRatio: new AspectRatio(16, 9),
});

// The viewport produces a DisplayInfo (not just a Size2)
const display = game.viewport.display;
console.log(`Logical: ${display.logicalSize.width}x${display.logicalSize.height}`);
console.log(`Physical: ${display.physicalSize.width}x${display.physicalSize.height}`);

// viewport.size still works - delegates to display.logicalSize
const size = game.viewport.size;

// Resize events carry previous and current DisplayInfo
game.viewport.onResize.add(({ previous, current }) => {
  console.log(`Resized: ${current.logicalSize.width}x${current.logicalSize.height}`);
});

Package Contents โ€‹

@carrot/engine-framework-sprites โ€‹

Import: import { ... } from '@carrot/engine-framework-sprites';

Classes โ€‹

ClassExtendsDescription
SpriteBehaviourLoads image, creates texture + material + quad mesh
SpriteAnimatedSpriteFrame-based animation support (stub)
SpritePackedSpriteAnimatedTexture atlas/packing support (stub)
SpriteRendererRenderBehaviourReads mesh/material from a Sprite sibling, exposes to render pipeline

Functions โ€‹

FunctionDescription
registerSpriteBehaviours(registry)Registers all sprite behaviour factories on a BehaviourRegistry

Sprite Members โ€‹

MemberTypeDescription
imageAssetImage | undefinedLoaded image asset
textureTexture2D | undefinedGPU texture
materialMaterial2DSprite | undefinedSprite material
meshMesh | undefinedQuad mesh sized to image (or overridden size)
tintColorRgbLikeTint colour (get/set) - delegates to material instance
mainTextureTexture2DMain texture (get/set) - delegates to material instance
spriteSizeSize2Current sprite dimensions (getter)
materialInstanceMaterialInstance<Material2DSprite>Typed material instance (getter)
onImageLoadedSignal<AssetImage>Fires when image loads successfully
onImageFailedSignal<Error>Fires on image load failure

Constructor: new Sprite(source: string | AssetImage, size?: Size2Like) - optional size overrides the mesh dimensions instead of using the image's natural size.

SpriteRenderer Members โ€‹

MemberTypeDescription
meshMesh | undefinedMesh from sibling Sprite
materialInstanceMaterialInstance | undefinedMaterial instance from sibling Sprite
visiblebooleanTrue when both mesh and material are available
submit()methodSubmit a DrawCall to the renderer's draw list
draw(camera)methodImmediate-mode rendering - binds material, submits, and draws in one call

Registered Behaviour Types โ€‹

'sprite' ยท 'spriteRenderer' ยท 'spriteAnimated' ยท 'spritePacked'


@carrot/engine-framework-physics โ€‹

Import: import { ... } from '@carrot/engine-framework-physics';

Classes โ€‹

ClassDescription
NewtonSystemPooled particle physics GameSystem with gravity and air friction
NewtonParticleIndividual particle - position, velocity, rotation, lifetime. Implements PoolItem

NewtonParticle Properties โ€‹

position (Vector2) ยท velocity (Vector2) ยท rotation (number) ยท angularVelocity (number) ยท lifetime (number) ยท age (number) ยท maxAge (number) ยท isAlive (boolean)

NewtonSystem Members โ€‹

MemberTypeDescription
gravitynumberY-axis gravity (default: -0.981)
airFrictionnumberVelocity damping per frame (default: 0.98)
particlesNewtonParticle[]Current particle list (snapshot)
emit(count)methodSpawn particles from the pool

@carrot/engine-framework-flexui โ€‹

Import: import { ... } from '@carrot/engine-framework-flexui';

This package is currently a stub. No exports are defined yet.

Planned Exports โ€‹

The following are planned but not yet implemented:

  • FlexNode tree (layout nodes with flex properties)
  • Layout engine (measure, arrange, render)
  • Text rendering (SDF-based)
  • UI event system (hit testing, focus, input routing)
  • Style system (CSS-like properties, cascading)
  • UIOverlayPass integration via RenderFeature

@carrot/engine-framework-cameras โ€‹

Import: import { ... } from '@carrot/engine-framework-cameras';

Classes โ€‹

ClassExtendsDescription
CameraBehaviourBehaviourWraps a low-level Camera with projection factories, entity transform sync, and auto-resize

Types โ€‹

TypeDescription
CameraProjectionMode'ortho2d' ยท 'orthographic' ยท 'perspective' ยท 'custom'
OrthographicBounds{ left, right, bottom, top, near, far }
PerspectiveConfig{ fov, aspect, near, far }

Functions โ€‹

FunctionDescription
registerCameraBehaviours(registry)Registers camera behaviour factories on a BehaviourRegistry

CameraBehaviour Static Factories โ€‹

FactoryDescription
CameraBehaviour.ortho2d()2D orthographic camera - auto-rebuilds projection on engine resize
CameraBehaviour.perspective(config)Perspective camera from a PerspectiveConfig
CameraBehaviour.orthographic(bounds)Orthographic camera from an OrthographicBounds
CameraBehaviour.custom(matrix)Camera with a manually provided projection matrix

CameraBehaviour Members โ€‹

MemberTypeDescription
cameraCameraThe underlying low-level Camera (getter)
projectionModeCameraProjectionModeCurrent projection mode (getter)
clearFlagsClearFlagsWhat to clear before rendering (get/set)
clearColorColorRgbLikeClear colour (get/set)
renderOrdernumberRender order - lower renders first (get/set)
layerMasknumberLayer bitmask filter (get/set)
targetRenderTexture | nullRender target - null = default framebuffer (get/set)

CameraBehaviour Runtime Setters โ€‹

MethodDescription
setOrtho2d()Switch to auto-resizing 2D orthographic projection
setOrthographic(bounds)Switch to orthographic with explicit bounds
setPerspective(config)Switch to perspective projection
setCustomProjection(matrix)Switch to a manually provided projection matrix

Carrot