Draw ​
2D and 3D drawing primitives. Drawing only works inside frame callbacks (on_tick); calling these from event callbacks does nothing.
Colors ​
Colors are IM_COL32 packed uint32 in 0xAABBGGRR format (alpha in the high byte, red in the low byte).
local WHITE = 0xFFFFFFFF
local RED = 0xFF0000FF -- red in the low byte, FF alpha
local GREEN = 0xFF00FF00
local BLUE = 0xFFFF0000
local YELLOW = 0xFF00FFFFUse draw.color to build colors from RGBA components without hand-packing:
local muted = draw.color(180, 180, 180) -- alpha defaults to 255
local fade = draw.color(255, 255, 255, 80)draw.color also accepts a single {r, g, b, a} table as its first argument, so the result of a color picker can be passed directly:
local col = draw.color(ui.get(my_color_picker))Byte order gotcha
This format is ABGR, not ARGB. 0xFFFF0000 is solid blue, not red, most other engines and CSS use 0xAARRGGBB. If you're copying a hex color from elsewhere, the red and blue channels need to swap. draw.color(r, g, b, a) handles the packing for you.
2D primitives ​
| Function | Description |
|---|---|
draw.line(x1, y1, x2, y2, color, [thickness]) | Line between two points |
draw.rect(x, y, w, h, color, [thickness], [rounding]) | Outlined rectangle - w/h are width and height, not corner coords |
draw.rect_filled(x, y, w, h, color, [rounding]) | Filled rectangle - w/h are width and height, not corner coords |
draw.circle(x, y, radius, color, [thickness], [segments]) | Outlined circle |
draw.circle_filled(x, y, radius, color, [segments]) | Filled circle |
Text ​
Text functions accept an optional font name OR scale parameter. Pass a string for a font name, or a number for scale on the default font.
| Function | Description |
|---|---|
draw.text(x, y, text, color, [font_or_size], [size]) | Text with shadow |
draw.text_raw(x, y, text, color, [font_or_size], [size]) | Text without shadow |
draw.text3d(pos, text, color, [font_or_size], [size]) | Text anchored to a world position (vec3) |
draw.text(100, 100, "Hello", 0xFFFFFFFF) -- default font, scale 1.0
draw.text(100, 120, "Hello", 0xFFFFFFFF, 1.5) -- default font, scale 1.5
draw.text(100, 140, "Hello", 0xFFFFFFFF, "hud") -- custom font, scale 1.0
draw.text(100, 160, "Hello", 0xFFFFFFFF, "hud", 1.5) -- custom font, scale 1.5All text functions return nothing.
Fonts ​
Scripts can use custom fonts. Either pre-load fonts via fonts.lua or let users pick from system fonts via the font picker setting.
| Function | Returns | Description |
|---|---|---|
draw.set_font(name) | - | Set the per-script default font for subsequent draw.text calls. Pass nil, "", or "default" to reset to the overlay default |
draw.get_font() | string | Current per-script default font name |
draw.get_fonts() | table | Array of all loaded custom font names |
Font state is per-script. Calling draw.set_font("Arial") in Script A doesn't affect Script B.
If the font name isn't loaded yet, draw.set_font triggers dynamic loading. The engine searches in this order:
C:\TSUKI\Deadlock\assets\fonts\(custom fonts you add)C:\Windows\Fonts\(installed system fonts)
The first frame may render with the default font while the atlas rebuilds.
fonts.lua (optional pre-loading) ​
Pre-load fonts at specific sizes into the atlas at startup. Drop a fonts.lua next to your scripts. Not required; the font picker dynamically loads system fonts on demand.
-- C:\TSUKI\Deadlock\scripts\fonts.lua
return {
hud = { file = "roboto.ttf", size = 16 },
small = { file = "consolas.ttf", size = 10 },
big = { file = "inter.ttf", size = 24 },
}.ttf and .otf files go in C:\TSUKI\Deadlock\assets\fonts\. The keys (hud, small, etc.) become the names you pass to draw.set_font.
Font priority ​
Highest precedence wins:
- Explicit font passed to
draw.text(..., font_name) - Per-script default set via
draw.set_font(name) - Overlay default font
3D primitives ​
3D primitives are reprojected per frame.
| Function | Description |
|---|---|
draw.line3d(p1, p2, color, [thickness]) | Line between two vec3 points |
draw.circle3d(center, radius, color, [thickness], [segments]) | Outlined circle in world space |
draw.circle3d_filled(center, radius, color, [segments]) | Filled circle in world space |
draw.box3d(center, width, depth, height, color, [thickness]) | Axis-aligned box centered at center (vec3) with dimensions in world units along X (width), Y (depth), and Z (height) |
draw.sphere3d(center, radius, color, [thickness], [segments]) | Wireframe sphere (3 great circles); segments clamped 4-32, default 12 |
Projection and screen size ​
| Function | Returns | Description |
|---|---|---|
draw.world_to_screen(vec3) | vec2 | nil | Pixel position as a vec2 with .x and .y, or nil if behind the camera |
draw.screen_size() | vec2 | Screen dimensions as vec2 with .x (width) and .y (height) |
draw.measure_text(text, [font_or_scale], [size]) | vec2 | Rendered pixel size of text as vec2 with .x (width) and .y (height). Same font/scale resolution as draw.text |
world_to_screen is also available as camera.world_to_screen. Same function.
draw.screen_size() is also available as camera.get_screen_size(). Same function.
Example ​
local WHITE = 0xFFFFFFFF
local RED = 0xFF3232FF
function on_tick()
for _, p in ipairs(get_players()) do
if p:is_enemy() and p:is_alive() then
local box = p:screen_box()
if box then
draw.rect(box.x, box.y, box.w, box.h, RED, 2)
draw.text(box.x, box.y - 14, p:hero_name(), WHITE)
end
end
end
end