§ 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