Skip to content

Plugins

The plugin system organizes engine functionality into reusable modules. Each plugin provides a set of components, systems, and resources. Estella ships with several built-in plugins that are installed automatically — you do not need to configure them.

Built-in Plugins

When you create a project in the editor, all built-in plugins are active by default. They provide the components you see in the Inspector and the systems that drive engine behavior each frame.

Core Plugins

PluginWhat It Provides
Asset loadingAssetServer for loading textures, Spine data, prefabs, and other assets at runtime
PrefabsPrefabs resource for instantiating prefabs
InputInput resource with keyboard, mouse, and touch input — cleared each frame
TimelineTimelinePlayer component for playing timeline animations, TimelineControl runtime API

UI Plugins

PluginComponents
TextText — renders text content to sprite textures each frame
ImageImage — displays images with slicing, tiling, and fill modes
UI MaskUIMask — scissor/stencil-based masking for UI elements
UI LayoutUIRect — anchored UI layout relative to camera
UI Render OrderAutomatic render order assignment for UI hierarchies
UI InteractionInteractable, Button, UIInteraction, UIEvents — pointer hit-testing and button states
Text InputTextInput — editable text fields with platform input
ToggleToggle, ToggleGroup — checkbox and radio-button behavior
SliderSlider — interactive value slider with fill and handle
Progress BarProgressBar — non-interactive progress display
DropdownDropdown — dropdown list selector
Scroll ViewScrollView — scrollable container with momentum and elastic bounce
List ViewListView — virtual scrolling for large lists
DragDraggable — drag-to-move on UI elements
FocusFocusable — keyboard focus and Tab navigation
Safe AreaSafeArea — adapts layout to device safe areas (notches, rounded corners)
Layout GroupLayoutGroup — grid-like layout with direction, spacing, and alignment

All of these are active by default in every project. You can use their components directly from the editor’s component picker — no setup required.

User Scripts

The primary way to extend your game is by writing TypeScript scripts in your project’s src/ directory. Scripts are automatically compiled and included in builds.

Custom Components

Use defineComponent() to create components that appear in the editor’s Inspector:

import { defineComponent } from 'esengine';
const Health = defineComponent('Health', {
hp: 100,
maxHp: 100,
});

Once defined, Health appears in the editor’s Add Component menu. You can add it to entities and edit its properties in the Inspector just like built-in components.

Use defineTag() for marker components with no data:

import { defineTag } from 'esengine';
const Enemy = defineTag('Enemy');

Systems

Use defineSystem() and addSystem() to add game logic that runs every frame:

import { defineSystem, addSystem, Schedule } from 'esengine';
addSystem(defineSystem([], () => {
// runs every frame during the Update phase
}));

You can also target a specific schedule phase:

import { addSystemToSchedule, defineSystem, Schedule } from 'esengine';
addSystemToSchedule(Schedule.FixedUpdate, defineSystem([], () => {
// runs at fixed timestep, ideal for physics logic
}));

For one-time initialization, use addStartupSystem():

import { addStartupSystem, defineSystem } from 'esengine';
addStartupSystem(defineSystem([], () => {
// runs once when the game starts
}));

Resources

Resources are global singleton data that can be shared across systems:

import { defineResource, addStartupSystem, defineSystem, Commands } from 'esengine';
const GameState = defineResource<{ score: number; level: number }>(
{ score: 0, level: 1 },
'GameState'
);
addStartupSystem(defineSystem([Commands()], (cmds) => {
cmds.insertResource(GameState, { score: 0, level: 1 });
}));

Events

Events enable decoupled communication between systems:

import { defineEvent, EventWriter, EventReader, defineSystem, addSystem } from 'esengine';
const ScoreEvent = defineEvent<{ points: number }>('Score');
addSystem(defineSystem([EventWriter(ScoreEvent)], (writer) => {
writer.send({ points: 10 });
}));
addSystem(defineSystem([EventReader(ScoreEvent)], (reader) => {
for (const e of reader) {
console.log('Scored:', e.points);
}
}));

System Schedule

Systems run in a defined order each frame:

PhaseWhen
StartupOnce, when the game starts
FirstBeginning of each frame
FixedPreUpdateBefore fixed-timestep update
FixedUpdateFixed-timestep update (physics)
FixedPostUpdateAfter fixed-timestep update
PreUpdateBefore main update
UpdateMain update (default for addSystem)
PostUpdateAfter main update
LastEnd of each frame

Advanced: Plugin Interface

A plugin is an object implementing the Plugin interface:

import { type Plugin, type App, defineResource, defineComponent, defineSystem, Schedule } from 'esengine';
const ScoreData = defineResource<{ value: number }>({ value: 0 }, 'Score');
const Health = defineComponent('Health', { hp: 100, maxHp: 100 });
const myPlugin: Plugin = {
name: 'MyGamePlugin',
build(app: App) {
app.insertResource(ScoreData, { value: 0 });
app.addSystemToSchedule(Schedule.Update, defineSystem([], () => {
// game logic
}));
},
finish(app: App) {
// Called after all plugins have been built — safe to read resources from other plugins
},
cleanup(app?: App) {
// Called when the app shuts down — release resources, remove listeners, etc.
},
};

Plugin Lifecycle

MethodWhenPurpose
build(app)Plugin is addedRegister resources, systems, and components
finish?(app)After all plugins are builtCross-plugin initialization that depends on other plugins being ready
cleanup?(app)App shutdownRelease resources, clean up listeners and state

Plugins can declare resource dependencies:

const analyticsPlugin: Plugin = {
name: 'AnalyticsPlugin',
dependencies: [Assets],
build(app: App) {
const assets = app.getResource(Assets);
// use asset server...
},
};

Registering Custom Plugins

Register custom plugins in your script’s setup function:

import { type App } from 'esengine';
export function setup(app: App) {
app.addPlugin(myPlugin);
}