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
| Plugin | What It Provides |
|---|---|
| Asset loading | AssetServer for loading textures, Spine data, prefabs, and other assets at runtime |
| Prefabs | Prefabs resource for instantiating prefabs |
| Input | Input resource with keyboard, mouse, and touch input — cleared each frame |
| Timeline | TimelinePlayer component for playing timeline animations, TimelineControl runtime API |
UI Plugins
| Plugin | Components |
|---|---|
| Text | Text — renders text content to sprite textures each frame |
| Image | Image — displays images with slicing, tiling, and fill modes |
| UI Mask | UIMask — scissor/stencil-based masking for UI elements |
| UI Layout | UIRect — anchored UI layout relative to camera |
| UI Render Order | Automatic render order assignment for UI hierarchies |
| UI Interaction | Interactable, Button, UIInteraction, UIEvents — pointer hit-testing and button states |
| Text Input | TextInput — editable text fields with platform input |
| Toggle | Toggle, ToggleGroup — checkbox and radio-button behavior |
| Slider | Slider — interactive value slider with fill and handle |
| Progress Bar | ProgressBar — non-interactive progress display |
| Dropdown | Dropdown — dropdown list selector |
| Scroll View | ScrollView — scrollable container with momentum and elastic bounce |
| List View | ListView — virtual scrolling for large lists |
| Drag | Draggable — drag-to-move on UI elements |
| Focus | Focusable — keyboard focus and Tab navigation |
| Safe Area | SafeArea — adapts layout to device safe areas (notches, rounded corners) |
| Layout Group | LayoutGroup — 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:
| Phase | When |
|---|---|
Startup | Once, when the game starts |
First | Beginning of each frame |
FixedPreUpdate | Before fixed-timestep update |
FixedUpdate | Fixed-timestep update (physics) |
FixedPostUpdate | After fixed-timestep update |
PreUpdate | Before main update |
Update | Main update (default for addSystem) |
PostUpdate | After main update |
Last | End 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
| Method | When | Purpose |
|---|---|---|
build(app) | Plugin is added | Register resources, systems, and components |
finish?(app) | After all plugins are built | Cross-plugin initialization that depends on other plugins being ready |
cleanup?(app) | App shutdown | Release 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);}