Skip to content

Components

A component is a small, plain data struct attached to an entity. Declare one with defineComponent, giving it a name and its default values:

import { defineComponent } from 'esengine';
const Speed = defineComponent('Speed', { value: 200 });
const Health = defineComponent('Health', { current: 100, max: 100 });

The defaults define both the shape (fields and types) and the initial values a new instance gets. Components are pure data — no methods, no behavior. Behavior lives in systems.

A field’s type is inferred from its default. Numbers, booleans, and strings work as you’d expect, and structured shapes get matching editor controls:

const Missile = defineComponent('Missile', {
speed: 400, // number
homing: true, // boolean
targetTag: '', // string
velocity: { x: 0, y: 0 }, // vector — edited as an X/Y pair
tint: { r: 1, g: 1, b: 1, a: 1 }, // {r,g,b,a} fields render a color picker
});

A tag is a zero-field marker used purely for filtering — Player, Enemy, Frozen. Declare it with defineTag and use it in queries with .with() / .without():

import { defineTag, defineSystem, Query, Mut, Transform } from 'esengine';
const Player = defineTag('Player');
const Frozen = defineTag('Frozen');
defineSystem([Query(Mut(Transform)).with(Player).without(Frozen)], (q) => { /* … */ });

A third argument controls how fields present in the editor’s Details panel — ranges, sliders, units, enums, and asset pickers:

const Turret = defineComponent('Turret', {
range: 300,
mode: 0,
}, {
fields: {
range: { min: 0, max: 1000, slider: true, unit: 'px' },
mode: { enum: [{ label: 'Idle', value: 0 }, { label: 'Attack', value: 1 }] },
},
});

Useful fields options: min / max / step, slider, unit, enum (dropdown storing the option’s int), flags (bitmask multi-select), tooltip. Other component-level metadata: assetFields (marks a field as an asset reference, so Details renders a picker and the cook ships the dependency), animatableFields (which fields the Sequencer can key — defaults to all numeric fields), transient (never saved into the scene — for per-frame runtime state), and replicatedFields (synced by networking’s state replication).

The SDK ships ~30 engine components you query alongside your own — Transform, Sprite, ShapeRenderer, Camera, Light2D, ParticleEmitter, SpineAnimation, TilemapLayer, the UI set, the physics colliders, and more. Each is documented in its subsystem guide: Sprites & Rendering, Camera, 2D Lighting, Physics, Tilemaps, UI.

Systems receive component data through a query. Wrap a component in Mut(...) when a system writes to it, so change detection and the renderer know the data was touched:

Query(Mut(Transform), Speed)
// Transform is written; Speed is read-only

Because writes are tracked, systems can also react to lifecycle: Added(Comp) / Changed(Comp) narrow a query to entities whose component just appeared / just changed — see Systems for the full query vocabulary.


Under the hood a component is a C++ struct annotated with ES_COMPONENT / ES_PROPERTY; the cross-boundary layout is generated, so the TypeScript component and the C++ struct are always in sync.