Skip to main content

Drawing & Visuals

Two systems for rendering visuals: the executor Drawing API for fast 2D screen overlays, and Roblox Instances (Highlights, BillboardGuis) for 3D-attached visuals. Each has its place.

Executor Drawing API

The Drawing API draws directly to the screen — lines, circles, text, boxes. It's immediate, fast, and works even when Roblox's UI is hidden.

-- Line
local line = Drawing.new("Line")
line.Visible = true
line.From = Vector2.new(0, 0)
line.To = Vector2.new(500, 500)
line.Color = Color3.fromRGB(255, 0, 0)
line.Thickness = 2
line.Transparency = 1 -- 1 = fully opaque in Drawing API

-- Circle
local circle = Drawing.new("Circle")
circle.Visible = true
circle.Position = Vector2.new(400, 300)
circle.Radius = 50
circle.Color = Color3.fromRGB(0, 255, 0)
circle.Thickness = 2
circle.Filled = false -- outline only
circle.NumSides = 64 -- smoothness

-- Square (bounding box)
local box = Drawing.new("Square")
box.Visible = true
box.Position = Vector2.new(100, 100) -- top-left corner
box.Size = Vector2.new(50, 80)
box.Color = Color3.fromRGB(255, 255, 0)
box.Thickness = 1
box.Filled = false

-- Text
local label = Drawing.new("Text")
label.Visible = true
label.Position = Vector2.new(200, 150)
label.Text = "Enemy"
label.Size = 16 -- font size
label.Color = Color3.fromRGB(255, 255, 255)
label.Outline = true
label.OutlineColor = Color3.fromRGB(0, 0, 0)
label.Center = true -- center horizontally at Position

Remove a drawing object when done:

line:Remove()
circle:Remove()

World to Screen Conversion

To draw on screen at a 3D object's position, convert the world position:

local function worldToScreen(worldPos)
local cam = workspace.CurrentCamera
local screenPos, onScreen = cam:WorldToViewportPoint(worldPos)
return Vector2.new(screenPos.X, screenPos.Y), onScreen, screenPos.Z
end

-- Use it:
local enemyPos = enemyHRP.Position + Vector3.new(0, 3, 0) -- slightly above
local screenPos, visible, depth = worldToScreen(enemyPos)

if visible and depth > 0 then
-- Draw a dot at the enemy's screen position
espDot.Position = screenPos
espDot.Visible = true
else
espDot.Visible = false
end

Box ESP with Drawing API

A classic box ESP draws a rectangle around an entity on screen. To get the 2D bounding box from a 3D character:

local function getBoundingBox(character)
local hrp = character:FindFirstChild("HumanoidRootPart")
if not hrp then return nil end

local cam = workspace.CurrentCamera
local pos = hrp.Position

-- Approximate character height: center is hrp, top is +3, bottom is -3
local topScreen, topVis = cam:WorldToViewportPoint(pos + Vector3.new(0, 3.5, 0))
local botScreen, botVis = cam:WorldToViewportPoint(pos + Vector3.new(0, -3.5, 0))

if not topVis or topScreen.Z <= 0 then return nil end

local height = math.abs(topScreen.Y - botScreen.Y)
local width = height * 0.65 -- approximate aspect ratio

return {
x = topScreen.X - width/2,
y = topScreen.Y,
width = width,
height = height,
}
end

-- Draw the box
local box = Drawing.new("Square")
box.Thickness = 1
box.Filled = false

game:GetService("RunService").Heartbeat:Connect(function()
local char = workspace:FindFirstChild("EnemyName")
local bb = char and getBoundingBox(char)
if bb then
box.Position = Vector2.new(bb.x, bb.y)
box.Size = Vector2.new(bb.width, bb.height)
box.Color = Color3.fromRGB(255, 0, 0)
box.Visible = true
else
box.Visible = false
end
end)

Highlight Instances (3D)

Highlights render a colored silhouette over a 3D model. They work for both visible and occluded objects:

local highlight = Instance.new("Highlight")
highlight.FillColor = Color3.fromRGB(255, 0, 0) -- body fill color
highlight.FillTransparency = 0.5 -- 0 = solid, 1 = invisible
highlight.OutlineColor = Color3.fromRGB(255, 255, 255) -- outline color
highlight.OutlineTransparency = 0 -- 0 = solid outline
highlight.DepthMode = Enum.HighlightDepthMode.AlwaysOnTop -- show through walls
highlight.Adornee = enemyCharacter -- what to highlight
highlight.Parent = game:GetService("CoreGui") -- must be in CoreGui to persist

The Adornee is the Model or Part to highlight. Set it to a character model to highlight the full body.

tip

Always parent Highlights to CoreGui, not workspace. If parented to workspace or a character, Roblox's replication system destroys it when the server replicates changes.

Cleaning up:

highlight:Destroy()

BillboardGui Name Labels

BillboardGui attaches a 2D UI panel to a 3D position in the world, always facing the camera:

local function createLabel(adornee, text)
local billboard = Instance.new("BillboardGui")
billboard.Name = "ESPLabel"
billboard.Size = UDim2.new(0, 120, 0, 30)
billboard.StudsOffset = Vector3.new(0, 3, 0) -- offset above the adornee
billboard.AlwaysOnTop = true -- show through walls
billboard.Adornee = adornee -- attach to this part
billboard.Parent = game:GetService("CoreGui")

local label = Instance.new("TextLabel")
label.Size = UDim2.fromScale(1, 1)
label.BackgroundTransparency = 1
label.Text = text
label.TextColor3 = Color3.fromRGB(255, 255, 255)
label.TextScaled = true
label.Font = Enum.Font.GothamBold
label.Parent = billboard

return billboard
end

-- Usage: create a label above an enemy's head
local enemy = workspace.Enemies:FindFirstChild("Boss")
if enemy then
local hrp = enemy:FindFirstChild("HumanoidRootPart")
local label = createLabel(hrp, "Boss — 5000 HP")
end

Health Bar

Add a health bar to the BillboardGui:

local function createHealthBar(adornee, maxHp)
local billboard = Instance.new("BillboardGui")
billboard.Size = UDim2.new(0, 80, 0, 8)
billboard.StudsOffset = Vector3.new(0, 2.5, 0)
billboard.AlwaysOnTop = true
billboard.Adornee = adornee
billboard.Parent = game:GetService("CoreGui")

-- Background (red = missing HP)
local bg = Instance.new("Frame")
bg.Size = UDim2.fromScale(1, 1)
bg.BackgroundColor3 = Color3.fromRGB(150, 0, 0)
bg.BorderSizePixel = 0
bg.Parent = billboard

-- Fill (green = current HP)
local fill = Instance.new("Frame")
fill.Size = UDim2.fromScale(1, 1) -- start full
fill.BackgroundColor3 = Color3.fromRGB(0, 200, 0)
fill.BorderSizePixel = 0
fill.Parent = bg

-- Update function
local function updateHP(current)
local ratio = math.clamp(current / maxHp, 0, 1)
fill.Size = UDim2.fromScale(ratio, 1)
-- Color shift: green → yellow → red
if ratio > 0.6 then
fill.BackgroundColor3 = Color3.fromRGB(0, 200, 0)
elseif ratio > 0.3 then
fill.BackgroundColor3 = Color3.fromRGB(220, 180, 0)
else
fill.BackgroundColor3 = Color3.fromRGB(220, 50, 0)
end
end

return billboard, updateHP
end

Cleanup Pattern

Always track your visual objects so you can clean them up:

local visuals = {} -- store all Drawing objects and Instances

-- Add visuals
local h = Instance.new("Highlight")
h.Parent = CoreGui
table.insert(visuals, h)

local d = Drawing.new("Text")
table.insert(visuals, d)

-- Cleanup function
local function cleanupAll()
for _, v in visuals do
if typeof(v) == "Instance" then
v:Destroy()
else
v:Remove() -- Drawing API objects use :Remove()
end
end
visuals = {}
end