Rendering
ESEngine uses a component-based rendering system. Add rendering components to entities and the engine handles drawing automatically.
Sprites
The Sprite component makes an entity visible:
import { Commands, Sprite, LocalTransform } from 'esengine';
defineSystem([Commands()], (cmds) => { cmds.spawn() .insert(Sprite, { texture: 0, // Texture ID color: { x: 1, y: 1, z: 1, w: 1 }, // RGBA (white) size: { x: 64, y: 64 }, // Width, height uvOffset: { x: 0, y: 0 }, // Texture offset uvScale: { x: 1, y: 1 }, // Texture scale layer: 0, // Render order flipX: false, flipY: false }) .insert(LocalTransform, { position: { x: 100, y: 100, z: 0 } });});Sprite Properties
| Property | Type | Description |
|---|---|---|
texture | number | Texture resource ID |
color | Vec4 | Tint color (RGBA, 0-1) |
size | Vec2 | Sprite dimensions in pixels |
uvOffset | Vec2 | Texture coordinate offset |
uvScale | Vec2 | Texture coordinate scale |
layer | number | Render order (higher = on top) |
flipX | boolean | Flip horizontally |
flipY | boolean | Flip vertically |
Colored Rectangle
For simple colored rectangles without textures:
cmds.spawn() .insert(Sprite, { texture: 0, // No texture color: { x: 1, y: 0, z: 0, w: 1 }, // Red size: { x: 50, y: 50 } }) .insert(LocalTransform, { position: { x: 200, y: 200, z: 0 } });Camera
A Camera component is required to see anything. Create one in your startup system:
cmds.spawn() .insert(Camera, { projectionType: 1, // 0 = Perspective, 1 = Orthographic fov: 60, // Field of view (perspective only) orthoSize: 400, // Half-height in world units (ortho only) nearPlane: 0.1, farPlane: 1000, aspectRatio: 1, // Usually set automatically isActive: true, // Only one camera should be active priority: 0 // Higher priority camera wins }) .insert(LocalTransform, { position: { x: 0, y: 0, z: 10 } // Camera position });Orthographic Camera
Best for 2D games. The orthoSize determines how much of the world is visible:
// Camera showing 800x600 world unitscmds.spawn() .insert(Camera, { projectionType: 1, orthoSize: 300, // Half of 600 isActive: true }) .insert(LocalTransform, { position: { x: 400, y: 300, z: 10 } // Center of 800x600 world });Moving the Camera
Query and modify the camera’s transform:
defineSystem( [Res(Input), Query(LocalTransform, Camera)], (input, query) => { for (const [entity, transform, camera] of query) { // Pan camera with arrow keys if (input.isKeyDown('ArrowLeft')) { transform.position.x -= 5; } if (input.isKeyDown('ArrowRight')) { transform.position.x += 5; } } });Transform
Every visible entity needs a LocalTransform:
cmds.spawn() .insert(LocalTransform, { position: { x: 100, y: 200, z: 0 }, rotation: { w: 1, x: 0, y: 0, z: 0 }, // Quaternion scale: { x: 1, y: 1, z: 1 } }) .insert(Sprite, { ... });Rotation
Rotation uses quaternions. For 2D rotation around Z-axis:
// Rotate sprite over timedefineSystem( [Res(Time), Query(LocalTransform, Sprite)], (time, query) => { for (const [entity, transform, sprite] of query) { const angle = time.elapsed; // Radians const halfAngle = angle / 2; transform.rotation = { w: Math.cos(halfAngle), x: 0, y: 0, z: Math.sin(halfAngle) }; } });Scale
// Double sizetransform.scale = { x: 2, y: 2, z: 1 };
// Flip horizontallytransform.scale = { x: -1, y: 1, z: 1 };Render Order
Sprites are drawn in order by:
- Z position - Lower Z draws first (behind)
- Layer - Higher layer draws on top
// Background (behind everything)cmds.spawn() .insert(Sprite, { layer: 0, ... }) .insert(LocalTransform, { position: { x: 0, y: 0, z: -10 } });
// Player (middle)cmds.spawn() .insert(Sprite, { layer: 1, ... }) .insert(LocalTransform, { position: { x: 0, y: 0, z: 0 } });
// UI (on top)cmds.spawn() .insert(Sprite, { layer: 10, ... }) .insert(LocalTransform, { position: { x: 0, y: 0, z: 10 } });Example: Animated Sprite
import { defineSystem, defineComponent, Schedule, Res, Time, Query, LocalTransform, Sprite} from 'esengine';
const Animation = defineComponent('Animation', { frames: 4, currentFrame: 0, frameTime: 0.1, elapsed: 0});
// Animation systemapp.addSystemToSchedule(Schedule.Update, defineSystem( [Res(Time), Query(Sprite, Animation)], (time, query) => { for (const [entity, sprite, anim] of query) { anim.elapsed += time.delta;
if (anim.elapsed >= anim.frameTime) { anim.elapsed = 0; anim.currentFrame = (anim.currentFrame + 1) % anim.frames;
// Update UV offset for sprite sheet sprite.uvOffset.x = anim.currentFrame / anim.frames; } } }));