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
| Property | Type | Description |
|---|---|---|
projectionType | ProjectionType | ProjectionType.Perspective (0), ProjectionType.Orthographic (1) |
fov | number | Field of view in degrees (perspective only) |
orthoSize | number | Half-height in world units (orthographic only) |
nearPlane | number | Near clipping plane |
farPlane | number | Far clipping plane |
isActive | boolean | Whether this camera is used for rendering |
aspectRatio | number | Camera aspect ratio |
priority | number | Render order — lower priority renders first, higher renders on top |
viewportX | number | Viewport left edge (0–1). Default: 0 |
viewportY | number | Viewport bottom edge (0–1). Default: 0 |
viewportW | number | Viewport width (0–1). Default: 1 |
viewportH | number | Viewport height (0–1). Default: 1 |
clearFlags | ClearFlags | ClearFlags.None (0), ColorOnly (1), DepthOnly (2), ColorAndDepth (3) |
showFrustum | boolean | Draw 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 sizetransform.scale = { x: -1, y: 1, z: 1 }; // Flip horizontallyRender Order
Sprites are sorted by the following priority (highest first):
- Layer (
sprite.layer) — primary sort key, higher layer draws on top - Texture — within the same layer, sprites are batched by texture to reduce draw calls
- 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}`);| Field | Type | Description |
|---|---|---|
drawCalls | number | Total WebGL draw calls this frame |
triangles | number | Total triangles rendered |
sprites | number | Sprites drawn |
text | number | Text elements drawn |
spine | number | Spine skeletons drawn |
meshes | number | Custom meshes drawn |
culled | number | Objects culled (not drawn) |
Render Stages
The rendering pipeline is divided into stages that execute in order. Each stage draws a category of objects:
| Stage | Value | Description |
|---|---|---|
Background | 0 | Background layers (skyboxes, backgrounds) |
Opaque | 1 | Opaque geometry (no transparency) |
Transparent | 2 | Transparent/alpha-blended objects |
Overlay | 3 | UI 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
- Canvas & Resolution — screen adaptation and scale modes
- Sprite — sprite rendering and animation
- Spine Animation — skeletal animation
- Bitmap Text — GPU-rendered bitmap font text
- Custom Draw — immediate-mode drawing for debug visualization and dynamic graphics
- Post-Processing — full-screen effects (blur, vignette, grayscale)
- Render Texture — off-screen rendering for minimaps, mirrors, and effects
- Geometry & Meshes — custom mesh creation for advanced rendering