Custom Drawing
Most visuals are components — sprites, tilemaps, particles. When you need something they can’t express — debug overlays, procedural shapes, custom geometry — the engine exposes its drawing layer directly.
Which API to use
Section titled “Which API to use”| API | Model | Reach for it when |
|---|---|---|
Draw |
Immediate, global — commands batch and clear every frame. | Debug overlays, gizmos, anything redrawn from scratch each frame. |
Graphics |
Retained path recorder — build once, replay through Draw with flush(). |
Vector shapes with paths/béziers/arcs you don’t want to re-tessellate per frame. |
ShapeRenderer / Mesh2D |
Components — entities with sorting layers, materials, editor gizmos. | Shapes/meshes that are part of the scene (see Sprites). |
Rule of thumb: if the visual belongs to the scene (sorted, saved, inspectable), make it a component; if it belongs to a frame (debug, HUD-ish overlays, procedural effects), draw it in a callback.
Draw callbacks
Section titled “Draw callbacks”Register a callback and it runs inside the render pass every frame, with the camera’s view-projection already active — no setup, just draw:
import { registerDrawCallback, Draw } from 'esengine';
registerDrawCallback('debug-overlay', (elapsed) => { Draw.line({ x: 0, y: 0 }, { x: 100, y: 50 }, { r: 1, g: 0, b: 0, a: 1 }, 2); Draw.circleOutline({ x: 0, y: 0 }, aggroRadius, { r: 1, g: 1, b: 0, a: 0.5 });});Callbacks draw on top of the scene; registerPreSceneDrawCallback draws
under it (its callback receives { width, height, elapsed }). Remove one
with unregisterDrawCallback(id). All coordinates are world-space.
The Draw API
Section titled “The Draw API”Draw is an immediate-mode singleton — commands are batched automatically and
cleared every frame, so call it each frame you want the shape visible:
| Method | Draws |
|---|---|
line(from, to, color, thickness?) |
A line segment (thickness in px, default 1). |
rect(pos, size, color, filled?) / rectOutline(pos, size, color, thickness?) |
A rectangle — pos is the center. |
circle(center, radius, color, filled?, segments?) / circleOutline(…) |
A circle (default 32 segments). |
texture(pos, size, textureHandle, tint?) |
A textured quad. |
textureRotated(pos, size, rotation, textureHandle, tint?) |
Same, rotated (radians). |
drawMesh(geometry, shader, transform) / drawMeshWithMaterial(geometry, material, transform?) |
A custom mesh by handle (see the Geometry API below). |
setLayer(layer) / setDepth(depth) |
Sorting of subsequent commands. |
setBlendMode(mode) / setDepthTest(on) |
Blend / depth state for subsequent commands. |
getDrawCallCount() / getPrimitiveCount() |
Frame stats. |
Vector paths: Graphics
Section titled “Vector paths: Graphics”For shapes built from paths — polylines, béziers, arcs, rounded rects —
Graphics records a path and replays it through Draw when you flush():
import { Graphics, registerDrawCallback } from 'esengine';
const g = new Graphics();g.lineStyle(2, { r: 1, g: 1, b: 1, a: 1 });g.beginFill({ r: 0, g: 0.5, b: 1, a: 1 });g.drawRoundRect(-50, -25, 100, 50, 8);g.endFill();g.moveTo(0, 0);g.curveTo(50, 80, 100, 0); // quadratic bézier
registerDrawCallback('hud-shape', () => g.flush());Path methods: moveTo / lineTo, curveTo (quadratic) / cubicCurveTo,
arc, drawRect / drawRoundRect / drawCircle / drawEllipse, clear().
lineStyle(thickness, color) sets the stroke; beginFill(color) / endFill()
fill rects and circles (path outlines stay strokes). The path is retained —
build it once, flush() it every frame.
Custom meshes: Mesh2D
Section titled “Custom meshes: Mesh2D”Mesh2D is a component that renders an arbitrary indexed triangle list —
procedural terrain slices, radial health arcs, water surfaces. Author the
geometry on the component (positions as x,y pairs; optional per-vertex UVs and
colors):
cmds.spawn() .insert(Transform, { position: { x: 0, y: 0, z: 0 } }) .insert(Mesh2D, { geometry: { positions: [0, 0, 100, 0, 50, 80], colors: [1,0,0,1, 0,1,0,1, 0,0,1,1], indices: [0, 1, 2], }, });The mesh shares the standard renderable surface: texture, color, layer,
lit, parallax, and material. Geometry set in a scene (or at spawn) uploads
automatically; to mutate it at runtime, go through the Meshes2D resource —
setGeometry(entity, geometry) re-uploads (indices are validated engine-side),
getGeometry / clearGeometry round it out. Direct writes to the geometry
field alone are not change-detected.
Custom geometry: the Geometry API
Section titled “Custom geometry: the Geometry API”Where Mesh2D is the component path, Geometry is the immediate one: it
uploads an indexed vertex buffer and hands back a handle you draw yourself —
paired with a material via
Draw.drawMeshWithMaterial(geometry, material, transform?) (or a raw shader via
Draw.drawMesh(geometry, shader, transform)).
import { Geometry, Draw, Material, registerDrawCallback } from 'esengine';
const quad = Geometry.createQuad(120, 120); // 1 unit ≙ 1 design pxconst mat = Material.create({ shader }); // a compileShader material
registerDrawCallback('custom-mesh', () => { Draw.drawMeshWithMaterial(quad, mat); // identity transform});GeometryOptions
Section titled “GeometryOptions”| Field | Default | Description |
|---|---|---|
vertices |
— | Interleaved vertex data as one Float32Array (max 64K floats per upload). |
layout |
— | VertexAttributeDescriptor[] — one { name, type } per attribute, in buffer order. |
indices |
— | Uint16Array or Uint32Array triangle list (max 16K indices per upload). |
dynamic |
false |
Allocate for frequent updates via updateVertices. |
DataType names each attribute’s shape: Float, Float2, Float3, Float4,
Int, Int2, Int3, Int4.
Methods & helpers
Section titled “Methods & helpers”| Method | Does |
|---|---|
create(options) |
Uploads the mesh; returns a GeometryHandle (throws on failure or oversized data). |
updateVertices(handle, vertices, offset?) |
Rewrites vertex data on a dynamic geometry (offset in floats). |
release(handle) / isValid(handle) |
Free / probe a handle. |
createQuad(width?, height?) |
A centered quad (defaults 1×1), layout a_position + a_texCoord. |
createCircle(radius?, segments?) |
A triangle-fan circle (defaults 1, 32), same layout. |
createPolygon(points) |
A fan-triangulated polygon from {x, y} points, UVs from its bounding box. |
The helper meshes all use the two-attribute Float2 position + Float2 UV
layout, so they pair directly with fragment-only .esshader materials.
Offscreen targets: RenderTexture
Section titled “Offscreen targets: RenderTexture”For minimaps, portraits, mirrors — and as the backing of
cache-as-bitmap — RenderTexture allocates an offscreen
render target. Wrap draw commands in begin/end to render into it; the
resulting texture is an ordinary texture handle usable anywhere (a
Sprite.texture, Draw.texture, a material texture param):
import { RenderTexture, Draw } from 'esengine';
const rt = RenderTexture.create({ width: 256, height: 256, filter: 'linear' });
RenderTexture.begin(rt, viewProjection); // an ortho view-projection you supplyDraw.circle({ x: 0, y: 0 }, 80, { r: 1, g: 0.5, b: 0, a: 1 });RenderTexture.end();
sprite.texture = rt.texture;RenderTextureOptions
Section titled “RenderTextureOptions”| Field | Default | Description |
|---|---|---|
width / height |
— | Target size in pixels (required). |
depth |
true |
Attach a depth buffer — turn off for pure 2D compositing to save memory. |
filter |
'nearest' |
Sampling filter when the result is drawn: 'nearest' or 'linear'. |
Methods
Section titled “Methods”| Method | Does |
|---|---|
create(options) |
Allocates the target; returns a handle with texture (resource-table handle for components), textureId (raw device id for low-level paths), width, height. |
begin(rt, viewProjection) |
Redirects subsequent draws into the target (you supply the matrix). |
end() |
Ends offscreen rendering, back to the screen. |
resize(rt, width, height) |
Releases and re-creates — returns a new handle (the texture handle changes). |
getDepthTexture(rt) |
The depth attachment as a sampleable texture handle. |
release(rt) |
Frees the target and its textures. |
This is a low-level surface: it doesn’t render the scene for you — it redirects
whatever you draw between begin/end, under a view-projection matrix you build.
See also
Section titled “See also”- Sprites & Rendering — the component path for images and shapes.
- Materials & Shaders — custom shaders for any renderable.
- Camera — where the view-projection your callbacks draw under comes from.