In short
Put an invisible, anchored part named MapBounds over your whole map. In a LocalScript, build a round ImageLabel with ClipsDescendants on. Every frame, turn each character's position into the part's own coordinates with bounds.CFrame:PointToObjectSpace(position), divide X and Z by the part's size and add 0.5: that gives a spot from 0 to 1 across the map, ready for UDim2.fromScale.
- North-up maps are easier to read than rotating ones and need only position maths.
- An image of your map seen from above makes the background; without one it is a plain colour.
- Mark shops and goals by tagging their parts
MinimapIcon.
Get the script
Minimap script with player dots
A LocalScript that draws a round north-up minimap in the corner: an arrow for you, team-coloured dots for other players, and squares for tagged places.
-- LocalScript in StarterPlayerScripts named Minimap.
-- A round map in the top-right corner, north up. The map covers the part named
-- MapBounds in Workspace. You are the arrow; other players are dots; parts tagged
-- "MinimapIcon" show as squares in their own colour.
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local MAP_PIXELS = 180 -- width and height of the map on screen
local MAP_IMAGE = "" -- optional: an image of your map seen from above, e.g. "rbxassetid://123"
local player = Players.LocalPlayer
local bounds = workspace:WaitForChild("MapBounds") :: BasePart
local gui = Instance.new("ScreenGui")
gui.Name = "MinimapGui"
gui.ResetOnSpawn = false
local map = Instance.new("ImageLabel")
map.AnchorPoint = Vector2.new(1, 0)
map.Position = UDim2.new(1, -12, 0, 60)
map.Size = UDim2.fromOffset(MAP_PIXELS, MAP_PIXELS)
map.BackgroundColor3 = Color3.fromRGB(40, 70, 50)
map.Image = MAP_IMAGE
map.ClipsDescendants = true
map.Parent = gui
local round = Instance.new("UICorner")
round.CornerRadius = UDim.new(0.5, 0)
round.Parent = map
local stroke = Instance.new("UIStroke")
stroke.Thickness = 3
stroke.Color = Color3.new(1, 1, 1)
stroke.Parent = map
gui.Parent = player:WaitForChild("PlayerGui")
local function makeDot(size: number, color: Color3, isRound: boolean): Frame
local dot = Instance.new("Frame")
dot.AnchorPoint = Vector2.new(0.5, 0.5)
dot.Size = UDim2.fromOffset(size, size)
dot.BackgroundColor3 = color
dot.BorderSizePixel = 0
if isRound then
local c = Instance.new("UICorner")
c.CornerRadius = UDim.new(0.5, 0)
c.Parent = dot
end
dot.Parent = map
return dot
end
-- You: a small triangle drawn as text, so it can point the way you face
local me = Instance.new("TextLabel")
me.AnchorPoint = Vector2.new(0.5, 0.5)
me.Size = UDim2.fromOffset(18, 18)
me.BackgroundTransparency = 1
me.Text = "^"
me.TextColor3 = Color3.fromRGB(255, 230, 80)
me.Font = Enum.Font.GothamBlack
me.TextSize = 22
me.ZIndex = 3
me.Parent = map
-- A world position to a spot on the map, from 0 to 1 across each side
local function toMap(position: Vector3): Vector2
local offset = bounds.CFrame:PointToObjectSpace(position)
return Vector2.new(offset.X / bounds.Size.X + 0.5, offset.Z / bounds.Size.Z + 0.5)
end
local otherDots: { [Player]: Frame } = {}
local iconDots: { [Instance]: Frame } = {}
RunService.RenderStepped:Connect(function()
-- You
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
if root and root:IsA("BasePart") then
local spot = toMap(root.Position)
me.Position = UDim2.fromScale(spot.X, spot.Y)
local look = bounds.CFrame:VectorToObjectSpace(root.CFrame.LookVector)
me.Rotation = math.deg(math.atan2(look.X, -look.Z))
end
-- Other players
for _, other in Players:GetPlayers() do
if other ~= player then
local otherRoot = other.Character and other.Character:FindFirstChild("HumanoidRootPart")
local dot = otherDots[other]
if otherRoot and otherRoot:IsA("BasePart") then
if not dot then
dot = makeDot(8, Color3.new(1, 1, 1), true)
otherDots[other] = dot
end
local spot = toMap(otherRoot.Position)
dot.Position = UDim2.fromScale(spot.X, spot.Y)
dot.BackgroundColor3 = if other.Team then other.TeamColor.Color else Color3.new(1, 1, 1)
dot.Visible = true
elseif dot then
dot.Visible = false
end
end
end
for other, dot in otherDots do
if other.Parent == nil then
dot:Destroy()
otherDots[other] = nil
end
end
-- Tagged places: shops, eggs, the finish line
for _, icon in CollectionService:GetTagged("MinimapIcon") do
if icon:IsA("BasePart") then
local dot = iconDots[icon] or makeDot(10, icon.Color, false)
iconDots[icon] = dot
local spot = toMap(icon.Position)
dot.Position = UDim2.fromScale(spot.X, spot.Y)
end
end
for icon, dot in iconDots do
if not icon:IsDescendantOf(workspace) or not CollectionService:HasTag(icon, "MinimapIcon") then
dot:Destroy()
iconDots[icon] = nil
end
end
end)
RoCode builds it in the place you have open in Roblox Studio, on Windows or macOS with a desktop browser; on a phone the button sends the prompt and the link to your computer. Free to start, with free credits every day. More Roblox Studio scripts
Where to put it
Workspace
MapBounds Part (new, anchored, invisible)
StarterPlayer
StarterPlayerScripts
Minimap LocalScript new
Settings you can change
MAP_PIXELSIn the script- Width and height of the map on screen. Default:
180. MAP_IMAGEIn the script- Optional: the asset ID of a top-down picture of your map, such as
"rbxassetid://123". Default:"".
The map shows the area inside MapBounds: make that part cover your whole playable map, flat and level, with Transparency 1, CanCollide off and CanQuery off.
Type-checked with luau-lsp strict against Roblox's API definitions on 23 October 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.
Prompt for RoCode
Add a minimap: an invisible part named MapBounds over my map, and a LocalScript that draws a round 180 pixel minimap in the top-right corner, north up, with an arrow for me that turns with my character, dots for other players in their team colour, and squares for parts tagged MinimapIcon.
On this page
How a 2D minimap works
MapBounds defines the map's area. PointToObjectSpace turns a world position into one measured from the part's centre along its own axes, so a point at the part's minus X edge is at -Size.X / 2. Divide by the size and add 0.5 and the edges become 0 and 1. The part's X axis becomes left to right on the minimap and its Z axis top to bottom, so the minimap's top is the part's minus Z direction.
| Approach | Good for |
|---|---|
| Dots on a flat frame (this guide) | Any map. Cheap, clear, works on phones. |
| Dots on a top-down screenshot | The same code with MAP_IMAGE set: players see the real layout. |
| A ViewportFrame with a camera above the map | A live 3D view; costs more, since the map is drawn twice. Roblox's battle royale kit includes a minimap system to study. |
Step 1: Mark the map area
- Insert a Part in the Workspace named
MapBounds. - Resize and move it so it covers the whole playable area from above. Its height does not matter. Keep it level, and do not rotate it unless you want the map to turn with it.
- Set Anchored on, Transparency to 1, and CanCollide, CanTouch and CanQuery off, so it is invisible and nothing hits it.
- Optional: take a screenshot of your map from straight above, crop it to the part's edges, upload it as a Decal or Image and copy its asset ID for
MAP_IMAGE.
Step 2: The minimap script
Insert a LocalScript in StarterPlayer > StarterPlayerScripts named Minimap:
-- LocalScript in StarterPlayerScripts named Minimap.
-- A round map in the top-right corner, north up. The map covers the part named
-- MapBounds in Workspace. You are the arrow; other players are dots; parts tagged
-- "MinimapIcon" show as squares in their own colour.
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local MAP_PIXELS = 180 -- width and height of the map on screen
local MAP_IMAGE = "" -- optional: an image of your map seen from above, e.g. "rbxassetid://123"
local player = Players.LocalPlayer
local bounds = workspace:WaitForChild("MapBounds") :: BasePart
local gui = Instance.new("ScreenGui")
gui.Name = "MinimapGui"
gui.ResetOnSpawn = false
local map = Instance.new("ImageLabel")
map.AnchorPoint = Vector2.new(1, 0)
map.Position = UDim2.new(1, -12, 0, 60)
map.Size = UDim2.fromOffset(MAP_PIXELS, MAP_PIXELS)
map.BackgroundColor3 = Color3.fromRGB(40, 70, 50)
map.Image = MAP_IMAGE
map.ClipsDescendants = true
map.Parent = gui
local round = Instance.new("UICorner")
round.CornerRadius = UDim.new(0.5, 0)
round.Parent = map
local stroke = Instance.new("UIStroke")
stroke.Thickness = 3
stroke.Color = Color3.new(1, 1, 1)
stroke.Parent = map
gui.Parent = player:WaitForChild("PlayerGui")
local function makeDot(size: number, color: Color3, isRound: boolean): Frame
local dot = Instance.new("Frame")
dot.AnchorPoint = Vector2.new(0.5, 0.5)
dot.Size = UDim2.fromOffset(size, size)
dot.BackgroundColor3 = color
dot.BorderSizePixel = 0
if isRound then
local c = Instance.new("UICorner")
c.CornerRadius = UDim.new(0.5, 0)
c.Parent = dot
end
dot.Parent = map
return dot
end
-- You: a small triangle drawn as text, so it can point the way you face
local me = Instance.new("TextLabel")
me.AnchorPoint = Vector2.new(0.5, 0.5)
me.Size = UDim2.fromOffset(18, 18)
me.BackgroundTransparency = 1
me.Text = "^"
me.TextColor3 = Color3.fromRGB(255, 230, 80)
me.Font = Enum.Font.GothamBlack
me.TextSize = 22
me.ZIndex = 3
me.Parent = map
-- A world position to a spot on the map, from 0 to 1 across each side
local function toMap(position: Vector3): Vector2
local offset = bounds.CFrame:PointToObjectSpace(position)
return Vector2.new(offset.X / bounds.Size.X + 0.5, offset.Z / bounds.Size.Z + 0.5)
end
local otherDots: { [Player]: Frame } = {}
local iconDots: { [Instance]: Frame } = {}
RunService.RenderStepped:Connect(function()
-- You
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
if root and root:IsA("BasePart") then
local spot = toMap(root.Position)
me.Position = UDim2.fromScale(spot.X, spot.Y)
local look = bounds.CFrame:VectorToObjectSpace(root.CFrame.LookVector)
me.Rotation = math.deg(math.atan2(look.X, -look.Z))
end
-- Other players
for _, other in Players:GetPlayers() do
if other ~= player then
local otherRoot = other.Character and other.Character:FindFirstChild("HumanoidRootPart")
local dot = otherDots[other]
if otherRoot and otherRoot:IsA("BasePart") then
if not dot then
dot = makeDot(8, Color3.new(1, 1, 1), true)
otherDots[other] = dot
end
local spot = toMap(otherRoot.Position)
dot.Position = UDim2.fromScale(spot.X, spot.Y)
dot.BackgroundColor3 = if other.Team then other.TeamColor.Color else Color3.new(1, 1, 1)
dot.Visible = true
elseif dot then
dot.Visible = false
end
end
end
for other, dot in otherDots do
if other.Parent == nil then
dot:Destroy()
otherDots[other] = nil
end
end
-- Tagged places: shops, eggs, the finish line
for _, icon in CollectionService:GetTagged("MinimapIcon") do
if icon:IsA("BasePart") then
local dot = iconDots[icon] or makeDot(10, icon.Color, false)
iconDots[icon] = dot
local spot = toMap(icon.Position)
dot.Position = UDim2.fromScale(spot.X, spot.Y)
end
end
for icon, dot in iconDots do
if not icon:IsDescendantOf(workspace) or not CollectionService:HasTag(icon, "MinimapIcon") then
dot:Destroy()
iconDots[icon] = nil
end
end
end)
The map is an ImageLabel made round by a UICorner at half its size, with a white UIStroke as the rim. ClipsDescendants hides dots that fall outside it. Your arrow is a "^" text label rotated toward where your root part faces: VectorToObjectSpace turns the facing into map directions, and math.atan2 gives the angle. Other players get dots in their team colour, removed when they leave.
To mark a place, tag any part MinimapIcon: it shows as a square in the part's own colour. A shop could be a gold square and the finish a green one.
Test it
- Press Play. The round map is in the top-right corner with a yellow arrow.
- Walk around: the arrow moves across the map and turns as you turn. Walk toward the minus Z side of MapBounds: the arrow moves up.
- Start a 2-player test: the other player shows as a white dot, or in their team colour with teams.
- Tag a red part MinimapIcon: a red square appears in its place.
Common errors and fixes
Check the Output window first.
Infinite yield possible on 'Workspace:WaitForChild("MapBounds")'
The part must be named exactly MapBounds and sit directly in the Workspace.
My arrow is in the wrong place
MapBounds does not cover the map, or it is rotated. Make it cover the whole area and keep it level with no rotation.
The map image is upside down or mirrored
The top of the minimap is the part's minus Z side. Rotate or flip the screenshot to match, or rotate MapBounds 180 degrees around Y.
Players fall through or bump into MapBounds
CanCollide must be off. Turn off CanTouch and CanQuery too, so it never blocks raycasts.
Build it with RoCode
If you would rather not set this up by hand, RoCode can build it in the place you have open. RoCode is an independent AI agent for Roblox Studio, not made by Roblox: you describe the change in a web chat, and its Studio plugin creates the part and script in your Explorer.
Add a round minimap in the top-right corner covering my whole map, with an arrow for me, team-coloured dots for other players and gold squares for every shop.
- Reads your place to size the MapBounds part to your map
- Writes the minimap LocalScript and tags the places you name
- Is told to compile-check each script with Check Script, and sets undo points around each batch
After a build, RoCode can run its test robot: it walks around your game in Studio and reports PASS or FAIL. It checks only what it chose to test, so look at the minimap on a phone size too. How RoCode connects to Studio.
Questions
How do I make a minimap in Roblox Studio?
Mark your map's area with a part, then in a LocalScript turn each player's position into a spot on a frame using PointToObjectSpace and the part's size. Step 2 has the full script.
Should I use a ViewportFrame for a minimap?
Only if you need a live 3D view. It draws the map a second time, which costs more on phones. Dots on a flat image are enough for most games.
How do I make the minimap rotate with the player?
Rotate the positions of everything by the player's facing angle around the player's spot before placing it, and keep the arrow pointing up.
Can I hide enemies on the minimap?
Yes: skip players whose Team is not the local player's Team when drawing dots.
Sources and further reading
- Roblox Creator Docs: CFrame. PointToObjectSpace and VectorToObjectSpace
- Roblox Creator Docs: Viewport frames. the 3D alternative
- Roblox Creator Docs: Battle royale minimap system. Roblox's own minimap in a sample kit
- Roblox Creator Docs: CollectionService. tagging map icons
How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 23 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.