draw
Screen drawing. All draw.* calls go to the current frame's foreground draw list and must be called every render frame inside event.on("frame_update"). The draw list is reset every frame; missing a frame = a blank flicker on screen. See Getting Started.
Color parameter: throughout this page
coloris anumber(32-bit packed color), built withdraw.rgba/draw.u8/draw.hex; you cannot pass an{r,g,b,a}table directly (the table form is only for ESP element paletteopts).
Color Construction (returns packed uint32)
draw.rgba(r, g, b, a) → uint32 — floats 0..1
draw.u8(r, g, b, a) → uint32 — ints 0..255
draw.hex(packed : int) → uint32 — pass 0xRRGGBBAA directly
Color Transforms (in/out are packed uint32)
No new type; fully interoperable with rgba/u8/hex.
draw.color_mod_a(col, v) → uint32
Multiplies current alpha by v (0..1). Not an override — stacks well for fade-out animations.
local half = draw.color_mod_a(red, 0.5) -- alpha halveddraw.color_lerp(a, b, t) → uint32
Per-channel linear interpolation (alpha included). t is saturated to 0..1.
-- Low health fades green → red
local hp_col = draw.color_lerp(draw.rgba(1,0,0,1), draw.rgba(0,1,0,1), hp / 100)draw.color_darken(col, v) → uint32
RGB × (1-v), alpha preserved. v=0.5 → 50% darker.
draw.color_lighten(col, v) → uint32
RGB lerped toward 255 by v, alpha preserved. v=0.5 → halfway to white.
draw.color_hsv(h, s, v, a?) → uint32
HSV constructor. h ∈ [0,360) degrees (wraps via mod), s/v/a ∈ [0,1]. a defaults to 1.0.
-- Rainbow cycle
local hue = (time.now() * 60) % 360
local rainbow = draw.color_hsv(hue, 1.0, 1.0)Shapes
draw.line(x1, y1, x2, y2, color, opts?)
opts: {thickness = 1.0}
draw.rect(x1, y1, x2, y2, color, opts?)
opts: {filled = false, rounding = 0, thickness = 1, flags = 0}
draw.rect_gradient(x1, y1, x2, y2, c_tl, c_tr, c_br, c_bl)
A rectangle with one color per corner. Typical use: gradient health bars / gradient backgrounds.
draw.triangle_multicolor(x1, y1, c1, x2, y2, c2, x3, y3, c3)
Triangle with a different color per vertex; the GPU interpolates the fill. Useful for fade arrows and gradient hit markers.
draw.circle_multicolor(x, y, r, center_col, edge_col, opts?)
Radial gradient from center to edge (center one color, edge another, GPU interpolates). opts: {segments = 36}. Typical use: energy orbs, radar marker glows, minimap edge fades.
-- A glowing orb: bright white center, transparent edge
local cy = draw.rgba(1, 0.9, 0.4, 0.9)
local cx = draw.rgba(1, 0.9, 0.4, 0)
draw.circle_multicolor(x, y, 30, cy, cx)draw.circle(x, y, r, color, opts?)
opts: {filled = false, segments = 0, thickness = 1, fill = 1.0}
fill∈ [0,1] — clockwise arc starting from 12 o'clock.fill = 1.0(default) = full circle.filled = true+fill < 1.0→ draws a pie (center → arc → center)filled = false+fill < 1.0→ draws an open arc- Use for CD progress rings / loading spinners / radar sectors.
fill = 1.0takes ImGui's fast path with zero overhead.
-- 75% progress ring
draw.circle(cx, cy, 30, draw.rgba(0.3,0.9,0.4,1),
{ fill = 0.75, thickness = 4 })
-- Filled pie wedge
draw.circle(cx, cy, 30, draw.rgba(1,0.6,0,0.4),
{ filled = true, fill = 0.5 })draw.triangle(x1, y1, x2, y2, x3, y3, color, opts?)
opts: {filled = false, thickness = 1}. Suited for directional arrows, FOV triangle indicators, down arrows.
draw.quad(x1, y1, x2, y2, x3, y3, x4, y4, color, opts?)
opts: {filled = false, thickness = 1}. The core of projecting a 3D box: the top/bottom face becomes a 4-vertex quad after projecting the player's 8 world-space vertices to screen.
draw.polyline(points, color, opts?)
points: a point sequence, both forms are accepted
- flat:
{x1, y1, x2, y2, ...}(even length) - nested:
{ {x1, y1}, {x2, y2}, ... }
opts: {thickness = 1, closed = false, filled = false}
closed = true→ last point connects back to the startfilled = true→ usesAddConvexPolyFilled(reliable for convex polygons only); on the filled path, bothclosedandthicknessare ignored
A universal fallback — can draw quads / projected box polygonal outlines / radar shapes / FOV arcs.
Shadow + Glow (composite helpers)
Pure pixel helpers — multiple stacked rings approximating soft blur, no shader required. The alpha channel of col is the baseline intensity; opts.alpha is a multiplier (0..1).
draw.shadow_line(x1, y1, x2, y2, color, opts?)
A projected shadow line. opts: {offset_x = 0, offset_y = 2, blur = 3, layers = 3, alpha = 1.0}
Larger blur → softer; more layers → smoother but more batches. offset_x/y is the shadow's offset relative to the original (mimics a light direction).
-- A dark shadow under outlined text
draw.shadow_line(x, y + 12, x + w, y + 12,
draw.rgba(0, 0, 0, 0.6),
{ offset_y = 1, blur = 2 })draw.shadow_rect(x1, y1, x2, y2, color, opts?)
Rectangle outer shadow. opts: {spread = 6, rounding = 0, layers = 3, alpha = 1.0}
Spreads outward by spread pixels split into layers rings; inner ring almost opaque, outer rings fade out. Match rounding to your main rect's rounding. Higher layers = softer but more batches — default 3 is sufficient; keep it (or lower to 2) when batching ESP across many players.
-- Drop shadow below an HP bar to improve contrast over menus
draw.shadow_rect(x, y, x + w, y + h,
draw.rgba(0, 0, 0, 0.5),
{ spread = 4, rounding = 2 })
draw.rect(x, y, x + w, y + h, hp_col,
{ filled = true, rounding = 2 })draw.glow_circle(x, y, r, color, opts?)
Circular outer glow. opts: {spread = 8, layers = 4, alpha = 1.0}
Spreads outward by spread pixels with a quadratic radial falloff (bright center, faded edge). Pairs well with circle for focus markers / warning lights. layers is the perf knob — default 4 with quadratic falloff keeps the batch count tractable across many player markers.
-- Radar marker highlight: glow + filled core
draw.glow_circle(mx, my, 6, draw.rgba(1, 0.3, 0.3, 0.7))
draw.circle(mx, my, 4, draw.rgba(1, 0.3, 0.3, 1), { filled = true })Text + Image
draw.text(x, y, color, text, opts?)
opts:
font— font handle fromdraw.font(name)(default = system font)size— force font size float; omitted =ImGui::GetFontSize()(the currently-active font's size, not thefontargument's default)outline—true→ theme dark-grey outline (project-uniform style)uint32 color→ custom outline color- omitted /
false→ no outline
Outline implementation = 4-direction 1px offset overlay, smooth and aliasing-free; more correct than scripting manual offset draw.text calls.
draw.image(tex_handle, x, y, w, h, opts?)
Parameters: tex_handle comes from file.image's :load / :svg / :create.
opts: {tint = uint32_white, uv0 = {0,0}, uv1 = {1,1}}
uv0 / uv1 must be array-style {u, v} tables — the {u=..., v=...} named form is not accepted.
draw.image_rotated(tex_handle, cx, cy, w, h, angle_deg, opts?)
Image rotated by angle_deg around (cx, cy). w/h are pre-rotation dimensions. opts: same as draw.image (tint / uv0 / uv1).
-- Compass arrow follows player yaw
draw.image_rotated(arrow_tex, sw/2, sh/2, 32, 32, yaw_deg)Clip Stack
draw.push_clip(x1, y1, x2, y2, intersect?)
Push a rectangular clip; subsequent draws are visible only inside the rect.
intersectdefaults totrue(intersect with current clip — safer for nested clipping)- Must be paired with
pop_clipinsideframe_update; the stack does not persist across frames.
draw.pop_clip()
Pop the most recently pushed clip rect.
-- Progress bar mask (clip image to a fraction)
draw.push_clip(x, y, x + w * progress, y + h)
draw.image(icon, x, y, w, h)
draw.pop_clip()Measurement
draw.measure(text, opts?) → w, h
opts: {font, size} — omitted = use the currently active font + ImGui::GetFontSize
draw.screen() → w, h
Current frame's screen resolution.
Fonts
Fonts are loaded at project startup (ImGui font atlas is built once); adding fonts at runtime is not supported — user fonts must be placed in res/fonts/ so they are auto-scanned at startup.
draw.fonts() → table<string>
Returns the list of available font names. 7 built-ins: "default" / "small" / "icon" / "logo" / "esp" / "loot" / "models", plus any .ttf/.otf files (without extension) scanned from res/fonts/.
draw.font(name) → Font | nil
Get a font handle; nil means the name does not exist.
Note: currently
draw.font(name)only exposes the 7 built-in fonts ("default" / "small" / "icon" / "logo" / "esp" / "loot" / "models");draw.fonts()will list additional.ttf/.otffilenames underres/fonts/, but actual handle lookup follows the built-in names (extra fonts have no handle yet — reserved for future extension).
Example
event.on("frame_update", function(e)
local sw, sh = draw.screen()
local red = draw.rgba(1.0, 0.2, 0.2, 1.0)
local trans = draw.u8(255, 255, 255, 90)
-- Crosshair at screen center
draw.line(sw/2 - 8, sh/2, sw/2 + 8, sh/2, red, { thickness = 2 })
draw.line(sw/2, sh/2 - 8, sw/2, sh/2 + 8, red, { thickness = 2 })
-- Top-left watermark (gradient background)
draw.rect_gradient(10, 10, 200, 40,
draw.rgba(0.1, 0.1, 0.15, 0.7), draw.rgba(0.2, 0.2, 0.3, 0.7),
draw.rgba(0.2, 0.2, 0.3, 0.7), draw.rgba(0.1, 0.1, 0.15, 0.7))
-- Outlined text (follows theme automatically)
local big = draw.font("logo")
draw.text(15, 18, draw.rgba(1, 1, 1, 1), "HELLO",
{ font = big, size = 24, outline = true })
-- Directional arrow (pointing at a player)
local cx, cy = sw/2, sh - 100
draw.triangle(cx, cy - 12, cx - 10, cy + 8, cx + 10, cy + 8,
draw.rgba(1, 0.8, 0.2, 1), { filled = true })
end)Animation composite (CD ring + color lerp + clip progress bar + rotated icon)
local start_t = time.now()
event.on("frame_update", function(e)
local t = (time.now() - start_t) % 4.0
local progress = t / 4.0
local sw, sh = draw.screen()
-- CD ring (clockwise fill)
local ring_col = draw.color_lerp(draw.rgba(0,0.9,1,1), draw.rgba(0.2,1,0.4,1), progress)
draw.circle(120, 120, 36, ring_col, { fill = progress, thickness = 5 })
-- Progress bar via clip mask
local bar_x, bar_y, bar_w, bar_h = 80, 200, 240, 14
draw.rect(bar_x, bar_y, bar_x + bar_w, bar_y + bar_h,
draw.color_darken(ring_col, 0.7), { filled = true, rounding = 6 })
draw.push_clip(bar_x, bar_y, bar_x + bar_w * progress, bar_y + bar_h)
draw.rect(bar_x, bar_y, bar_x + bar_w, bar_y + bar_h,
ring_col, { filled = true, rounding = 6 })
draw.pop_clip()
-- Rotated icon (requires a file.image:load handle)
-- draw.image_rotated(spinner_tex, 300, 120, 32, 32, progress * 360)
end)