Skip to content

Rendering

Estella uses a component-based rendering system. Add rendering components to entities in the scene editor and the C++ backend handles drawing automatically.

For detailed guides on specific rendering components, see Sprite and Spine Animation.

Camera

A Camera component is required to see anything. The editor’s default scene already includes one.

Camera Properties

PropertyTypeDescription
projectionTypeProjectionTypeProjectionType.Perspective (0), ProjectionType.Orthographic (1)
fovnumberField of view in degrees (perspective only)
orthoSizenumberHalf-height in world units (orthographic only)
nearPlanenumberNear clipping plane
farPlanenumberFar clipping plane
isActivebooleanWhether this camera is used for rendering
aspectRationumberCamera aspect ratio
prioritynumberRender order — lower priority renders first, higher renders on top
viewportXnumberViewport left edge (0–1). Default: 0
viewportYnumberViewport bottom edge (0–1). Default: 0
viewportWnumberViewport width (0–1). Default: 1
viewportHnumberViewport height (0–1). Default: 1
clearFlagsClearFlagsClearFlags.None (0), ColorOnly (1), DepthOnly (2), ColorAndDepth (3)
showFrustumbooleanDraw the camera frustum wireframe in the editor

Orthographic Camera

Best for 2D games. The orthoSize determines how much of the world is visible (default: 540). Set projectionType to ProjectionType.Orthographic in the inspector. Import ProjectionType and ClearFlags from 'esengine'.

Canvas Integration

For screen adaptation and resolution scaling, attach a Canvas component to the camera entity. See Canvas & Resolution for setup and scale modes.

Moving the Camera

Query the camera entity and modify its transform:

import { defineSystem, addSystem, Res, Input, Query, Mut, Transform, Camera } from 'esengine';
addSystem(defineSystem(
[Res(Input), Query(Mut(Transform), Camera)],
(input, query) => {
for (const [entity, transform, camera] of query) {
if (input.isKeyDown('ArrowLeft')) {
transform.position.x -= 5;
}
if (input.isKeyDown('ArrowRight')) {
transform.position.x += 5;
}
}
}
));

Multi-Camera Rendering

Multiple cameras can render simultaneously to different viewport regions. Each camera has its own viewport (viewportX/Y/W/H) and clearFlags.

Main Camera + Minimap:

Create two Camera entities:

  • Main camera: viewport (0, 0, 1, 1), priority: 0
  • Minimap camera: viewport (0.75, 0.75, 0.25, 0.25), priority: 1, clearFlags: ClearFlags.ColorAndDepth

The minimap renders in the top-right corner on top of the main view.

Split Screen:

  • Player 1: viewport (0, 0, 0.5, 1), priority: 0
  • Player 2: viewport (0.5, 0, 0.5, 1), priority: 0

Both cameras render side by side. Cameras with the same priority render in entity order.

Transform

Every visible entity needs a Transform. Add it to entities in the scene editor and configure position, rotation, and scale in the inspector.

Rotation

Rotation uses quaternions. For 2D rotation around the Z-axis:

import { defineSystem, addSystem, Res, Time, Query, Mut, Transform, Sprite } from 'esengine';
addSystem(defineSystem(
[Res(Time), Query(Mut(Transform), Sprite)],
(time, query) => {
for (const [entity, transform, sprite] of query) {
const angle = time.elapsed;
const halfAngle = angle / 2;
transform.rotation = {
w: Math.cos(halfAngle),
x: 0,
y: 0,
z: Math.sin(halfAngle)
};
}
}
));

Scale

transform.scale = { x: 2, y: 2, z: 1 }; // Double size
transform.scale = { x: -1, y: 1, z: 1 }; // Flip horizontally

Render Order

Sprites are sorted by the following priority (highest first):

  1. Layer (sprite.layer) — primary sort key, higher layer draws on top
  2. Texture — within the same layer, sprites are batched by texture to reduce draw calls
  3. Z position (transform.position.z) — within the same layer and texture, lower Z draws first (behind)

Set these values in the inspector when placing entities, or modify them at runtime in systems.

Render Stats

Use Renderer.getStats() to get per-frame rendering statistics, useful for profiling and debug overlays:

import { Renderer, type RenderStats } from 'esengine';
const stats: RenderStats = Renderer.getStats();
console.log(`Draw calls: ${stats.drawCalls}, Triangles: ${stats.triangles}`);
FieldTypeDescription
drawCallsnumberTotal WebGL draw calls this frame
trianglesnumberTotal triangles rendered
spritesnumberSprites drawn
textnumberText elements drawn
spinenumberSpine skeletons drawn
meshesnumberCustom meshes drawn
cullednumberObjects culled (not drawn)

Render Stages

The rendering pipeline is divided into stages that execute in order. Each stage draws a category of objects:

StageValueDescription
Background0Background layers (skyboxes, backgrounds)
Opaque1Opaque geometry (no transparency)
Transparent2Transparent/alpha-blended objects
Overlay3UI and overlay elements (drawn on top)

Custom draw callbacks execute after the built-in sprite, text, and spine rendering within each camera pass. Use Renderer.setStage() in the render pipeline to control which stage is active:

import { Renderer, RenderStage } from 'esengine';
Renderer.setStage(RenderStage.Overlay);

Next Steps