§ How to · with Picoo
How to make a Roblox matchmaking system (with AI)
By Sametcan Tasgiran, Founder & Developer·Published ·Updated
A Roblox matchmaking system needs five pieces: queue management, room creation, skill-based matching (MMR), TeleportService routing, and a queue UI. Describe your match format and Picoo writes the scripts for it. The build below is one example of that output.
Example build: 7 files · 280 lines. Picoo writes this code for each prompt, so file names and line counts vary from run to run. Queue state is shared across servers with MessagingService.
Queue management
Players join the queue from a UI button. The server holds the queue and tries to fill a room every time someone joins. The UI shows an estimated wait time.
Room creation
Each match gets its own reserved server from TeleportService:ReserveServer. Room size is one constant, ROOM_SIZE (6 in this example).
Skill-based matching
Each player has an MMR score starting at 1000. The matcher sorts the queue by MMR and forms a room only when its lowest and highest MMR are within 100 of each other. MMR changes on each win or loss.
TeleportService routing
When a room fills, the server reserves a private server and sends the matched players there together with TeleportService:TeleportToPrivateServer.
Cross-server queue
CrossServerSync shares queue state through MessagingService, so players waiting in different servers can land in the same room.
Party support
Players can party up before queuing, and a party is matched into the same room. Parties hold up to 4 players in this example.
Example output from a build like this
7 files in this example. Picoo writes this code for your prompt, so file names and sizes vary.
ServerScriptService/QueueManager.lua
Queue state, match attempts, MMR matching
ServerScriptService/CrossServerSync.lua
MessagingService queue broadcast
ServerScriptService/MMRTracker.lua
Player MMR persistence + win/loss adjustment
ServerScriptService/PartyManager.lua
Party invites, join, leave
ReplicatedStorage/Remotes/JoinQueue
Client → queue
ReplicatedStorage/Remotes/PartyInvite
Send/accept party invites
StarterGui/MatchmakingUI.lua
Queue button, wait time, party display
Sample output: ServerScriptService/QueueManager.lua
--!strict
-- ServerScriptService/QueueManager.lua (Picoo · excerpt)
local Players = game:GetService("Players")
local TeleportService = game:GetService("TeleportService")
local MMRTracker = require(script.Parent.MMRTracker)
type QueueEntry = { player: Player, mmr: number, partyId: string?, joinedAt: number }
local ROOM_SIZE = 6
local MMR_TOLERANCE = 100
local PLACE_ID = 0 -- set to your room place ID
local queue: { QueueEntry } = {}
local function tryMatch()
if #queue < ROOM_SIZE then return end
-- Find ROOM_SIZE players with MMR within tolerance
table.sort(queue, function(a, b) return a.mmr < b.mmr end)
for i = 1, #queue - ROOM_SIZE + 1 do
local window = queue[i + ROOM_SIZE - 1].mmr - queue[i].mmr
if window <= MMR_TOLERANCE then
-- Match found — extract these players
local matched: { QueueEntry } = {}
for j = i, i + ROOM_SIZE - 1 do table.insert(matched, queue[j]) end
for j = i + ROOM_SIZE - 1, i, -1 do table.remove(queue, j) end
-- Reserve server and teleport
local code = TeleportService:ReserveServer(PLACE_ID)
local players = {}
for _, entry in ipairs(matched) do table.insert(players, entry.player) end
TeleportService:TeleportToPrivateServer(PLACE_ID, code, players)
return
end
end
end
return { join = function(p: Player) table.insert(queue, { player = p, mmr = MMRTracker:get(p), partyId = nil, joinedAt = os.clock() }); tryMatch() end }Building a Roblox matchmaking system
Matchmaking is the invisible system that decides if your competitive Roblox game feels fair. Players don't notice good matchmaking; they just enjoy the games. They notice bad matchmaking immediately: stomps, long queues, repeated opponents. Here is one example of what Picoo writes for it.
In this example Picoo wrote seven files. QueueManager holds the queue and tries to fill rooms. MMRTracker saves each player's skill rating. CrossServerSync uses MessagingService to share queue state across running servers, so players in different servers can be matched together.
MMR-based matching is the fairness layer. Each player starts at a Matchmaking Rating of 1000. The matcher sorts the queue by MMR and forms a room only when the spread between its lowest and highest player is 100 or less. A win adds 25 and a loss takes 20, so average MMR drifts upward over time.
Queue starvation is the production failure mode. New games have small player bases, strict MMR matching means long queues, and players quit. The common mitigation is to widen the tolerance the longer players wait, which you can ask for in a follow-up prompt. A room still needs enough players in the queue, so pick a room size your player count can fill.
Reserved server rooms use TeleportService:ReserveServer to create private game instances. When a room fills, the matched players teleport there together. A reserved server can only be joined with its access code, so random players cannot drop in mid-match. This is the standard pattern for ranked games in Roblox.
Party support is the social layer. Players can party up via PartyManager and queue together, and a match only forms if the whole party fits in the same room. That is what lets friends play together; without it, ranked games feel anti-social.
See more on the Luau generator, the AI Luau script writer, the 3D model generator and the game builder, read how Picoo compares with ChatGPT, or browse the full blog.
Frequently asked
How does the cross-server queue work?+
Each server publishes its queue joins and leaves with MessagingService:PublishAsync and keeps a combined view. A server can only teleport players who are in it, so the server that forms a match reserves the room and publishes the access code, and every server sends its own matched players there with it. Two servers can try to match the same players at once; the example settles that by letting the first published match win. For a sturdier shared queue, ask for MemoryStoreService in a follow-up prompt.
What if no match is found in a long time?+
The sample above uses a fixed tolerance of 100 MMR, so a small player base can wait a long time. The usual fix is to widen the tolerance the longer players wait, for example to 250 after 60 seconds and 500 after 120, then match any full room. Ask for it in a follow-up prompt.
How is MMR adjusted on win/loss?+
In this example MMRTracker adds 25 MMR for a win, subtracts 20 for a loss, and saves MMR in a DataStore. For Elo-style changes that depend on the opponents' ratings, ask for it in a follow-up prompt.
How do parties stay together?+
In this example PartyManager tracks party membership. When any party member queues, all members are added with the same partyId, and a match only forms if every party member fits in the same room.
What happens if a player disconnects mid-queue?+
In this example PlayerRemoving takes them out of the queue. If their party was part of a match that is still forming, that match is cancelled for the whole party.
Related Picoo prompts
Roblox leaderboard
2 files · 186 lines of Luau plus a 45-instance board · one recipe call. Ranks leaderstats Coins by default; name another stat in the prompt.
Roblox FPS
Example build: 14 files · 420 lines, three weapons (rifle, shotgun, pistol). The files and numbers you get follow your prompt.
Roblox combat system
3 scripts · 1,181 lines · one recipe call. Press Play and fight with M1, E, F, Q, R and Shift.