Draw on screen: text, 3D text, and markers
Markers and floating text are not objects you place once. They are painted fresh on every single frame, inside a loop that runs at your framerate. That is powerful and it is expensive, so the real skill is not the draw call, it is the distance check that stops you from drawing when nobody is looking. This lesson builds both halves at once.
Build it
Make the resource folder
Inside your server's resources folder, create this folder:
resources/qu_draw
Create the files
Create this exact file layout. There is no server file. Drawing only happens on the client, because only a client has a screen.
resources/qu_draw/
fxmanifest.lua
client.lua
Write fxmanifest.lua
Open fxmanifest.lua and paste this:
fx_version 'cerulean'
game 'gta5'
client_script 'client.lua'
Older tutorials add a lua54 'yes' line here. As of June 2025 that setting is deprecated and ignored: Lua 5.4 is now the only Lua runtime, so you leave it out.
Write the DrawText3D helper
Open client.lua and paste this helper at the top of the file. It is the part everyone copies wrong, so type it once and keep it forever:
-- Floats a text label at a world coordinate (not a fixed screen spot).
-- SetDrawOrigin anchors the next 2D draw call to a 3D point in the world,
-- so DrawText, which is normally a screen native, ends up over that point.
local function DrawText3D(x, y, z, text)
SetTextScale(0.35, 0.35)
SetTextFont(4)
SetTextColour(255, 255, 255, 215)
SetTextEntry('STRING')
SetTextCentre(true)
AddTextComponentString(text)
SetDrawOrigin(x, y, z, 0)
DrawText(0.0, 0.0) -- 0,0 means "at the draw origin we just set"
ClearDrawOrigin() -- always clear, or every later draw call inherits it
end
Write the gated render loop
Below the helper, paste the render thread. Read the distance check first, because it is the reason this script is cheap:
-- The fixed world point we decorate. Near the Legion Square parking lot.
local point = vector3(215.0, -810.0, 30.7)
local DRAW_RADIUS = 20.0 -- only draw when the player is within this many metres
CreateThread(function()
while true do
local ped = PlayerPedId()
local pos = GetEntityCoords(ped)
local dist = #(pos - point) -- straight-line distance in metres
if dist < DRAW_RADIUS then
-- Close enough to see it: draw this frame, so yield only one frame.
DrawMarker(
1, -- type 1: a vertical cylinder
point.x, point.y, point.z - 1.0, -- sink it so the disc sits on the ground
0.0, 0.0, 0.0, -- direction (unused for this type)
0.0, 0.0, 0.0, -- rotation
2.0, 2.0, 1.0, -- scale x, y, z
0, 200, 120, 120, -- colour r, g, b, alpha
false, false, 2, -- bobbing, faceCamera, rotationOrder
false, nil, nil, false -- rotate, textureDict, textureName, drawOnEnts
)
DrawText3D(point.x, point.y, point.z + 0.4, 'qu_draw checkpoint')
Wait(0) -- next frame, because we are drawing
else
-- Too far to see it: draw nothing and sleep, so this thread costs ~0.
Wait(500)
end
end
end)
Start and test it
Open server.cfg and add this line:
ensure qu_draw
Save, then run:
restart qu_draw
Join the server. Teleport or walk to the point. With the console open you can run /tp 215 -810 30 if you have a teleport command, or just spawn near Legion Square and head to the parking lot.
There is no console line for this lesson, because drawing happens on your screen, not in a log. The proof is visual: the marker is there when you are close and gone when you are far. That on/off behaviour is the distance gate working, and it is the whole point of the lesson.
Keep reading the full lesson
Sign in to start, then unlock every step of this lesson and the full FiveM School with a membership.
- How it works
- If something went wrong
- What you can do now
- Try it yourself
The remainder of Draw on screen: text, 3D text, and markers is available to FiveM School members.