atherhubather.hub
Back to Guides
SUNC
13 min read
May 11, 2026

The SUNC Drawing API

The Drawing library lets scripts paint primitives directly onto the screen, outside Roblox's own UI stack. It renders above everything else, doesn't cost an instance, and is the backbone of features like ESPs, FPS counters, and debug overlays. This article is a complete reference for every object type and every shared property in the SUNC spec.

Ather
Ather
Lead developer at Atherhub. Writes about Roblox internals, Luau, script engineering, and platform security.Last updated May 11, 2026

The big picture

Every drawing object is created with Drawing.new(type), which returns an object with properties you can read and write like a table. Setting Visible = true makes the object render every frame; setting Visible = false hides it without destroying it. When you're done with an object you call :Remove() on it — the underlying handle is released and the object becomes unusable.

luau
local line = Drawing.new("Line")
line.From = Vector2.new(100, 100)
line.To = Vector2.new(300, 300)
line.Color = Color3.fromRGB(255, 0, 0)
line.Thickness = 2
line.Visible = true

-- ...later
line:Remove()
ReturnsDrawing
A drawing object of the requested type. Subsequent property writes update the object in place. The object renders every frame while Visible is true.

Shared properties

Every drawing object shares a base set of properties:

  • Visible: boolean — render this frame or skip it. Defaults to false.
  • ZIndex: number — relative draw order. Higher renders on top.
  • Transparency: number — 0 = fully transparent, 1 = fully opaque. Yes, the convention is flipped from Roblox's usual Transparency property.
  • Color: Color3 — the primary colour of the primitive. What "primary" means varies by type.
The Transparency convention
Drawing's Transparency is opacity (1 = fully visible). Roblox UI's Transparency is the opposite. This is the single most common Drawing bug — a value of 0 makes your drawing invisible, not fully visible.

Line

The simplest type. Draws a straight line between two screen points.

luau
local line = Drawing.new("Line")
line.From = Vector2.new(0, 0)         -- starting screen position
line.To = Vector2.new(800, 600)       -- ending screen position
line.Thickness = 2                     -- line width in pixels
line.Color = Color3.fromRGB(255, 0, 0)
line.Visible = true
FromTo
A typical Line drawing: From → To, thickness 2, red.

Type-specific properties: From: Vector2, To: Vector2, Thickness: number.

Text

Renders a string at a position with control over font, size, and centering.

luau
local text = Drawing.new("Text")
text.Text = "FPS: 60"
text.Position = Vector2.new(20, 20)
text.Size = 18
text.Center = false                          -- if true, Position is the centre point
text.Outline = true                          -- draw a 1px outline for contrast
text.OutlineColor = Color3.fromRGB(0, 0, 0)
text.Color = Color3.fromRGB(255, 255, 255)
text.Font = Drawing.Fonts.UI                 -- enum: UI | System | Plex | Monospace
text.Visible = true

Type-specific properties: Text: string, Position: Vector2, Size: number, Center: boolean, Outline: boolean, OutlineColor: Color3, Font: number. The Font values come from theDrawing.Fonts enum:

luau
Drawing.Fonts = {
    UI = 0,
    System = 1,
    Plex = 2,
    Monospace = 3,
}

A useful Text-only property is TextBounds — read- only, returns the rendered pixel size as a Vector2. Handy when you need to lay out adjacent elements without guessing widths.

Image

Renders an image from raw byte data. You supply the bytes; the drawing handles decoding and rendering.

luau
local img = Drawing.new("Image")
img.Data = game:HttpGet("https://example.com/icon.png")
img.Position = Vector2.new(50, 50)
img.Size = Vector2.new(64, 64)
img.Transparency = 1
img.Visible = true

Type-specific properties: Data: string (the raw binary bytes of a PNG or JPEG), Size: Vector2, Position: Vector2, Rounding: number (corner radius in pixels).

Circle

Renders a circle. Can be filled or stroked.

luau
local circle = Drawing.new("Circle")
circle.Position = Vector2.new(200, 200)
circle.Radius = 50
circle.NumSides = 32                          -- approximation; 32 looks smooth
circle.Thickness = 2
circle.Filled = false                         -- true = fill, false = stroke only
circle.Color = Color3.fromRGB(0, 255, 100)
circle.Visible = true
Filled = falseFilled = true
Stroke vs filled circle. Both forms accept the same properties.

Type-specific properties: Position: Vector2, Radius: number, NumSides: number, Thickness: number, Filled: boolean.

Square

Axis-aligned rectangle. Position is the top-left corner; Size is the width/height.

luau
local box = Drawing.new("Square")
box.Position = Vector2.new(100, 100)
box.Size = Vector2.new(200, 80)
box.Thickness = 2
box.Filled = false
box.Color = Color3.fromRGB(255, 200, 0)
box.Visible = true

Type-specific properties: Position: Vector2, Size: Vector2, Thickness: number, Filled: boolean.

Quad

Four arbitrary corners. Used for non-axis-aligned shapes — rotated boxes, perspective markers, etc.

luau
local quad = Drawing.new("Quad")
quad.PointA = Vector2.new(100, 100)
quad.PointB = Vector2.new(220, 80)
quad.PointC = Vector2.new(240, 200)
quad.PointD = Vector2.new(120, 220)
quad.Thickness = 2
quad.Filled = false
quad.Color = Color3.fromRGB(120, 180, 255)
quad.Visible = true

Type-specific properties: PointA / B / C / D: Vector2, Thickness, Filled. Points are traversed in declaration order to form the outline.

Triangle

Three corners; otherwise identical in shape semantics to Quad.

luau
local tri = Drawing.new("Triangle")
tri.PointA = Vector2.new(150, 50)
tri.PointB = Vector2.new(250, 200)
tri.PointC = Vector2.new(50, 200)
tri.Thickness = 2
tri.Filled = true
tri.Color = Color3.fromRGB(220, 80, 220)
tri.Visible = true

Type-specific properties: PointA / B / C: Vector2, Thickness, Filled.

A small ESP example

Putting it together: a per-player box ESP. Each player gets a square that follows their character's screen position, plus a Text element above it with their name. The whole thing is three drawings updated per frame.

luau
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local camera = workspace.CurrentCamera

local function createEsp(player)
    local box = Drawing.new("Square")
    box.Thickness = 1
    box.Filled = false
    box.Color = Color3.fromRGB(0, 255, 0)
    box.Visible = false

    local label = Drawing.new("Text")
    label.Size = 14
    label.Center = true
    label.Outline = true
    label.Color = Color3.fromRGB(255, 255, 255)
    label.Visible = false

    RunService.RenderStepped:Connect(function()
        local char = player.Character
        local head = char and char:FindFirstChild("Head")
        if not head then
            box.Visible = false
            label.Visible = false
            return
        end

        local pos, onScreen = camera:WorldToViewportPoint(head.Position)
        if not onScreen then
            box.Visible = false
            label.Visible = false
            return
        end

        local height = 60
        local width = 32
        box.Size = Vector2.new(width, height)
        box.Position = Vector2.new(pos.X - width / 2, pos.Y - 20)
        label.Text = player.Name
        label.Position = Vector2.new(pos.X, pos.Y - 36)

        box.Visible = true
        label.Visible = true
    end)
end

for _, p in Players:GetPlayers() do
    if p ~= Players.LocalPlayer then createEsp(p) end
end

Two things to notice. First, the connection runs every render step — drawings don't reposition themselves, you reposition them. Second, off-screen handling: when the world point falls outside the camera, we hide both drawings instead of letting them snap to the nearest edge. That detail is what separates a rough ESP from a clean one.

Wrap-up

The Drawing library is small and shaped exactly like its name — seven primitive types, a handful of shared properties per type, and one global function to instantiate them. Most non-trivial script overlays you'll see in the wild are built on these eight or nine pieces, occasionally combined with Roblox UI where pointer interaction is needed. (Drawings don't capture input — that's ScreenGuis' job.)

Ather
Written by Ather

Ather is the lead developer behind Atherhub. He's been writing Luau and Roblox tooling for the better part of a decade, with a focus on the messy interface between game-script internals and the platforms that host them. Have feedback on this article? Drop it in the Discord.