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), replicatedFields (synced by networking’s state replication), and renderableField.

renderableField names the boolean field that gates your component’s drawing — declare it if the component draws something of its own (say through a custom draw callback), and hiding the entity reaches it: the Outliner’s eye, setEntityVisible, and a sleeping scene all switch that field off and restore it afterwards. Leave it out for a component that only carries data or behaviour; plenty of those have an enabled field that has nothing to do with drawing, and switching it off would stop the wrong thing.

The engine registers 77 components you query alongside your own — Transform, Sprite, ShapeRenderer, Camera, Light, ParticleEmitter, SpineAnimation, TilemapLayer, the UI set, the physics colliders, and more. Each subsystem guide teaches the ones it owns; the component reference lists every one of them with its fields, types and authoring defaults, derived from the same registry the editor’s Details panel reads.

The ones the engine reads about your entities

Section titled “The ones the engine reads about your entities”

Four of them carry no subsystem of their own — they are how the engine and the editor describe an entity to themselves, and you will meet them in the Outliner and in scene files before you ever query them.

Component What it is
Name The entity’s display name — what the World Outliner shows and what name lookups resolve against.
Parent / Children The hierarchy. The engine keeps the pair in step; see Transforms.
Disabled A tag. setEntityActive(entity, false) adds it and isEntityActive is simply its absence, so “active” is a question about one component rather than a flag to keep in sync.
RuntimeOnly A tag for an entity a system derives rather than one you authored — the layer entities a Tilemap projects, runtime tile colliders. Scene saving skips them, because persisting a derived entity would duplicate it against the next derivation.

Both tags are ordinary components: query them with .with(Disabled) / .without(Disabled) like any other, and add or remove them directly if you prefer that to setEntityActive. Note that the Outliner’s eye is a different thing — an edit-time visibility fold that never touches Disabled, so hiding an entity while you work does not change what the running game does with it.

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.