How to make a cross-server matchmaking system | Full Tutorial

Hello there,
In this tutorial, you will be able to learn how to make a cross-server matchmaking system based on game modes or simply matchmaking across servers.

You need to know these before we start

  • What is a data store, key, and value
  • Basics of scripting
  • Knowing what is a proximity prompt and remote events/functions
  • Client and Server side differences and understanding

Let’s begin

Step 1

Make your GUI and the proximity prompt or click detector based on how you want players to join the queue.
Here’s what I made for this tutorial:
image
image

Step 2

Alright since you have done your front-end work or Gui and trigger action
It’s time to make them work

Now, we should tell the client to show the Gui on top when he/she joins the queue, how can we do this? We use a remote event and communicate between server and client, it’s that easy : )

Now add a remote event, name it whatever you want, and add a script in ServerScriptService
image

Step 3

Now firing the remote and receiving the remote from the client
Add a script in the place where you triggering the action (ProximityPrompt or ClickDetector etc)

If you took click detector:

local clickdetector = script.Parent -- make sure you keep this correct
clickdetector.MouseClick:Connect(function(pl)
	game.ReplicatedStorage.ConfirmQueueEvent:FireClient(plr,true) -- your firing to the player, and here true means your says confirm queue is true, it means it will tell the client that queue is confirmed
end)

If you took ProximityPrompt:

local proxprompt = script.Parent
proxprompt.Triggered:Connect(function(plr)
	game.ReplicatedStorage.ConfirmQueueEvent:FireClient(plr,true) -- your firing to the player, and here true means your says confirm queue is true, it means it will tell the client that queue is confirmed
end)

Now you are firing successfully when the player triggers the function
Now on client, we make the GUI appear when the event is received by the client.

game.ReplicatedStorage.ConfirmQueueEvent.OnClientEvent:Connect(function(confirmed)
	if confirmed == true then
		script.Parent.Frame.Visible = true
	else
		script.Parent.Frame.Visible = false
	end
end)

Now in case the queue got cancelled due to an error or any reasons in the main script later, we can fire this event again with false so the GUI will be hidden, here confirmed is the boolean we fired along with the event before if you remember, check it out.

Now change the Visible property of your GUI to false

Run it and see it work in action for now :slight_smile:


Congrats you finished 40% of this tutorial successfully:)

Step 4

Now comes the main part, you should make it work right, shouldn’t you?
So the player should join the queue when the event is triggered, along with showing UI it should add you to the queue.

So add a bindable event in the replicated storage and name it whatever you want as usual.
image
Always remember we can fire any data while firing a remote event/function or bindable event/function, it can be a 2 letter word or even a big dictionary of data

Now we will tell the main script which handles the match making process to add the player

Add this 1 line below or above the function where you fire the ConfirmQueueEvent to the player

game.ReplicatedStorage.JoinQueueEvent:Fire(plr,"GameMode1")

Now you can receive it from the main script

Step 5

Making the core script for this matchmaking process
We start by defining some variables for the script

local mss = game:GetService("MemoryStoreService")
local ms1 = mss:GetSortedMap("GameMode1")
local serverid = game.JobId

Workaround (How does this process work)
Now imagine it as a clothing rack, when you keep some new clothing, you keep it at the front and then when you add another piece of clothing, that will be added to the front too and the old clothing will be sent back and back.

Similarly, you add a value to this MemoryStoreService and then it will be at first, and slowly it will you will be adding more data and its position in the queue will go back, so what we do is, we read the players in the queue from the back, it means oldest player is first to get in the queue, so this will avoid a lot of time wasting for the person, and imagine a lot of players play your game, and if you do last player is first to get in queue, imagine some player missed out, and new players keep on coming, and until the new players joining queue stops that player won’t get the queue, so to avoid this chaos we do first player is first in the queue, aka descending order based system.

Now when you add players to the queue, there should be 1 more thing, you should confirm a queue and teleport them to the round or another place, you cannot do it from every server, imagine there are 100 servers, and the queue gets confirmed on 100 servers, only 1 server will be able to teleport them and all other 99 servers will error, player not found in lobby or in game to teleport.

So here comes the concept of host server. Only one server will be managing this confirming process of a queue, it might be mode-based too. For example if easy, medium, and hard were modes, I would have 3 hosts, and each host would be managing each mode, this would avoid a lot of work on a single server and an organized process.

Also, one thing is, the player can join the queue only once, so we keep an attribute such as IsInQueue for the player if he/she joins the queue, and before adding a player to the queue, we check if they are in the queue already by seeing if they have this attribute simply.

Now we add 2 more variables and a function

local mss = game:GetService("MemoryStoreService")
local ms1 = mss:GetSortedMap("GameMode1")
local serverid = game.JobId
local isserverhost = false
local dss = game:GetService("DataStoreService")
local ds1 = dss:GetDataStore("HostServerData")

local function addplrtoqueue(plr,mode)
	if plr:GetAttribute("IsInQueue") ~= nil then
		return
	end
	plr:SetAttribute("IsInQueue")
	if mode == "GameMode1" then
		ms1:SetAsync(tostring(plr.UserId),game.JobId)
	end
end

game.ReplicatedStorage.JoinQueueEvent.Event:Connect(function(plr,gamemode)
	addplrtoqueue(plr,gamemode)
end)

And we are gonna use UpdateAsync instead of SetAsync for being more efficient here
So we add a starting value for our DataStore

Run this in the command bar:

game:GetService("DataStoreService"):GetDataStore("HostServerData"):SetAsync("HostData",{})

Also note that if you have a different name for your DataStore, replace HostServerData with that name

The script:

local mss = game:GetService("MemoryStoreService")
local ms1 = mss:GetSortedMap("GameMode1")
local ms = game:GetService("MessagingService")
local serverid = game.JobId
local isserverhost = false
local dss = game:GetService("DataStoreService")
local serverhostforwhatmode = ""
local ds1 = dss:GetDataStore("HostServerData")
local numberofplayersperround = 5 -- keep it as any number you want
local tps = game:GetService("TeleportService")

-- if your teleporting a player to that place if they get confirmed
local placeid = 000 --

local dataretrived = ds1:GetAsync("HostData")

local function checkforconfirmedqueue(gamemode)
	if isserverhost == true then
		local playerlist = ms1:GetRangeAsync(Enum.SortDirection.Descending,5)
		if #playerlist == numberofplayersperround then
			local createdplaceid = tps:ReserveServer(placeid)
			local missingplayertable = {}
			local playerstobeteleported = {}
			for i,v in pairs(playerlist) do
				if game.Players:FindFirstChild(v) then
					table.insert(playerstobeteleported,game.Players:FindFirstChild(v))
				else
					table.insert(missingplayertable,v)
				end
			end
			tps:TeleportToPrivateServer(placeid,createdplaceid,playerstobeteleported)
			if #playerstobeteleported < numberofplayersperround then
				ms:PublishAsync("teleportplayer",{["ServerId"] = createdplaceid,["Players"] = missingplayertable})
			end
		end
	else
		if dataretrived[gamemode] == nil then
			isserverhost = true
			dataretrived[gamemode] = serverid
                        ds1:UpdateAsync("HostData",dataretrived)
			ms:PublishAsync("updatedata",dataretrived)
		end
	end
end

ms:SubscribeAsync("updatedata",function(message)
	dataretrived = message.Data or ds1:GetAsync("HostData")
        -- update the host data of servers when host is changed
end)

ms:SubscribeAsync("teleportplayer",function(message)
	local playerlist = message.Data["Players"]
	local serverid = message.Data["ServerId"]
	for i,v in pairs(playerlist) do
		if game.Players:FindFirstChild(v) then
                        -- if that player is in the game, then teleport him to the round
			tps:TeleportToPrivateServer(placeid,serverid,game.Players:FindFirstChild(v))
		end
	end
end)

local function addplrtoqueue(plr,mode)
	if plr:GetAttribute("IsInQueue") ~= nil then
		return
	end
	plr:SetAttribute("IsInQueue")
	if mode == "GameMode1" then
		ms1:SetAsync(plr.Name,game.JobId)
	end
        checkforconfirmedqueue(mode)
end

game.ReplicatedStorage.JoinQueueEvent.Event:Connect(function(plr,gamemode)
	addplrtoqueue(plr,gamemode)
end)
-- removing the server as host when server is closing
game.Players.PlayerRemoving:Connect(function(plr)
	if isserverhost == true and #game.Players:GetPlayers() <= 1 then
		dataretrived[serverhostforwhatmode] = nil
		ms:PublishAsync(dataretrived)
	end
        -- if player is in queue, remove him from the queue
        if plr:GetAttribute("IsInQueue") == true then
		ms1:RemoveAsync(plr.Name)
	end
end)

I hope you understood the workaround and the script accordingly.

Congrats you finished the tutorial

Tutorial is done

You made your working cross-server match-making in just 75 Lines of code and it works alright.
Remember you can always edit your script, and match it according to your game concept and system and this is only a basic full tutorial of making a match-making system. Please don’t copy-paste scripts, understand it and you will become an expert at it.

Thank you for reading and coming this far.
Have a nice day!

Support me

Any support is appreciated!
Donate $5
Paypal $1

33 Likes

This is so underrated tysm, this is exactly what I needed

3 Likes

I was actually if you could help me with my code with multiple gamemodes.

I also don’t think this code works anymore. after editing it, the code doesn’t work without error, could you take a peak at mine or make an updated one for me?

local mss = game:GetService("MemoryStoreService")
local ms1 = mss:GetSortedMap("GameMode1") --easy
local ms2 = mss:GetSortedMap("GameMode2") --medium
local ms3 = mss:GetSortedMap("GameMode3") --hard
local ms = game:GetService("MessagingService")
local serverid = game.JobId
local isserverhost = false
local dss = game:GetService("DataStoreService")
local serverhostforwhatmode = ""
local ds1 = dss:GetDataStore("HostServerData")
local numberofplayersperround = 2
local tps = game:GetService("TeleportService")

-- if your teleporting a player to that place if they get confirmed
local Places = {
	 Easy = 000,
	 Medium = 000,
	 Hard = 000,
}


local dataretrived = ds1:GetAsync("HostData")

local function checkforconfirmedqueue(gamemode)
	print("init")
	if isserverhost == true then
		local playerlist 
		if gamemode == "Easy" then
			print("ez")
			playerlist = ms1:GetRangeAsync(Enum.SortDirection.Descending,numberofplayersperround)
		elseif gamemode == "Medium" then
print("medd")
			playerlist = ms2:GetRangeAsync(Enum.SortDirection.Descending,numberofplayersperround)
		elseif gamemode == "Hard" then
			print("Harrrd")
			playerlist = ms3:GetRangeAsync(Enum.SortDirection.Descending,numberofplayersperround)
		end
		if #playerlist == numberofplayersperround then
			print("start req")
			local createdplaceid = tps:ReserveServer(Places[gamemode])
			local missingplayertable = {}
			local playerstobeteleported = {}
			for i,v in pairs(playerlist) do
				if game.Players:FindFirstChild(v) then
					table.insert(playerstobeteleported,game.Players:FindFirstChild(v))
				else
					table.insert(missingplayertable,v)
				end
			end
			
			
			tps:TeleportToPrivateServer(Places[gamemode],createdplaceid,playerstobeteleported)
			if #playerstobeteleported < numberofplayersperround then
				ms:PublishAsync("teleportplayer",{["ServerId"] = createdplaceid,["Players"] = missingplayertable,["Gamemode"]=gamemode})
			end
			
			
		end
	else
		print("No")
		if dataretrived == nil or dataretrived[gamemode] == nil then
			print("data time")
			isserverhost = true
			dataretrived[gamemode] = serverid
			ds1:SetAsync("HostData",dataretrived)
			ms:PublishAsync("updatedata",dataretrived)
		end
	end
end

ms:SubscribeAsync("updatedata",function(message)
	dataretrived = message.Data or ds1:GetAsync("HostData")
	-- update the host data of servers when host is changed
end)

ms:SubscribeAsync("teleportplayer",function(message)
	print("teleport")
	local playerlist = message.Data["Players"]
	local serverid = message.Data["ServerId"]
	local gamemode = message["Gamemode"]
	for i,v in pairs(playerlist) do
		if game.Players:FindFirstChild(v) then
			-- if that player is in the game, then teleport him to the round
			tps:TeleportToPrivateServer(Places[gamemode],serverid,game.Players:FindFirstChild(v))
		end
	end
end)

local function addplrtoqueue(plr,mode)
	if plr:GetAttribute("IsInQueue") ~= nil then
		return
	end
	plr:SetAttribute("IsInQueue")
	if mode == "Easy" then
		print("easy que")
		plr:SetAttribute("QueueMode","Easy")
		ms1:SetAsync(plr.Name,game.JobId,math.huge)
	end
	if mode == "Medium" then
		print("medium que")
		plr:SetAttribute("QueueMode","Medium")
		ms2:SetAsync(plr.Name,game.JobId,math.huge)
	end
	if mode == "Hard" then
		print("hard que")
		plr:SetAttribute("QueueMode","Hard")
		ms3:SetAsync(plr.Name,game.JobId,math.huge)
	end
	checkforconfirmedqueue(mode)
end

game.ReplicatedStorage.JoinQueueEvent.Event:Connect(function(plr,gamemode)
	print("Join QUeue Event Recieved: "..gamemode.." : "..plr.Name)
	addplrtoqueue(plr,gamemode)
end)
-- removing the server as host when server is closing
game.Players.PlayerRemoving:Connect(function(plr)
	if isserverhost == true and #game.Players:GetPlayers() <= 1 then
		dataretrived[serverhostforwhatmode] = nil
		ms:PublishAsync(dataretrived)
	end
	-- if player is in queue, remove him from the queue
	if plr:GetAttribute("IsInQueue") == true then
		if plr:GetAttribute("QueueMode") == "Easy" then
			ms1:RemoveAsync(plr.Name)
		end
		if plr:GetAttribute("QueueMode") == "Medium" then
			ms2:RemoveAsync(plr.Name)
		end
		if plr:GetAttribute("QueueMode") == "Hard" then
			ms3:RemoveAsync(plr.Name)
		end
	end
end)
4 Likes

Could you share the error if it’s possible?

1 Like

there is no error. It just doesn’t work without any errors.

btw, to make it easier to talk, you mind if we go on discord? my user is: unceen

3 Likes

Is there a way to add an cancel button. whenever the player change his mind that he doesn’t want to play the mode anymore or wanted to do something in the lobby and he queued in that mode, but he want’s to cancel it, so how could i actually add a cancel button?

1 Like

Also i’m little bit confused about this
is this the same code or different? or in the second picture you just post it the whole script?


2 Likes


It is also showing errors in the output

3 Likes

Hey there!

Please take a look at this Memory Stores | Documentation - Roblox Creator Hub

1 Like

Hey i fixe the argument 3 missing or nil, and tested out in the actually game, i got confused why it doesn’t show any errors but it’ doesn’t also teleport us into the game. I’m not sure what’s causing why we won’t teleport

1 Like

Try adding any number as argument 3, which is how long a key stays in the datastore.

Hi, I experienced a bug here when testing out the code

Ensure you check that the ds1 is properly assigned along with the dataretrived variable.

1 Like

Hey there! hope everyone goes well, you should make a public place uncopylocked so everyone that are beginner or whatever have a example place to start off and let the teleport place id to 00000 so they know they need to change this one but as far i know you can make a settings module directly instead of writing it inside script :slight_smile:

If i get some free time i will edit for everyone and give out my version !

2 Likes

Hey, once again! I finished my own version with some of @Deadwoodx’s code and fixed quite a few problems he did, but my script is 100% functional, easy to understand and easy to install.

It’s possible to have multiple gamemodes with my script, you’ll have a settings module and that’s it I haven’t put too much effort into adding comments but it should be easily readable for everyone.

Through my game place uncopylocked it have everything: Test Match - Roblox

P.S: yep as i described above i made a example place instead with both method (Proximity Prompt and Click Detector)

And as always happy coding/developing!

2 Likes

Is your Test Match game supposed to do anything after you click on the blocks?

or I guess not if the id is 0…

how would one use your test match? it doesn’t seem to do anything when ‘playing’ it.

did you just post it so someone can edit it and get the code and not have to type it in?

Heya! for everyone to be clear you should add a placeId in the settings in the gameMode you want here an example:

My settings:

local newSettings = {
	memoryStores = { -- MemoryStore (feel free to add more or less it is auto managed)
		gameModes = {
			easy = {
				name = "GameModeEasy"
			},
			
			medium = {
				name = "GameModeNormal"
			},
			
			hard = {
				name = "GameModeHard"
			}
		}
	},
	
	dataStores = { -- Server DataStore
		hostData = {
			name = "HostServerData",
			key = "HostData"
		}
	},
	
	gameModes = { -- GameMode (feel free to add more or less it is auto managed)
		easy = {
			requiredPlayer = 2,
			teleportId = 134039049518039 -- placeId (my own placeId forgot to change it to 00000)
		},
		
		medium = {
			requiredPlayer = 3,
			teleportId = 00000 -- placeId
		},
		
		hard = {
			requiredPlayer = 4,
			teleportId = 00000 -- placeId
		}
	},
	
	subscribes = { -- Subscribes Store
		update = "updateData",
		teleport = "teleportPlayerData"
	}
}

And what you can do is remove any gamemode and memorystore also if you don’t need them so i will do for easy mode only and i will make my new settings like this:

local newSettings = {
	memoryStores = { -- MemoryStore (feel free to add more or less it is auto managed)
		gameModes = {
			easy = {
				name = "GameModeEasy" -- name of the memory store (can be anything but should be unique)
			},
		}
	},
	
	dataStores = { -- Server DataStore
		hostData = {
			name = "HostServerData",
			key = "HostData"
		}
	},
	
	gameModes = { -- GameMode (feel free to add more or less it is auto managed)
		easy = {
			requiredPlayer = 2,
			teleportId = 134039049518039 -- my own placeId (you should replace it)
		},
	},
	
	subscribes = { -- Subscribes Store
		update = "updateData",
		teleport = "teleportPlayerData"
	}
}

Now that you set up your settings you should go in workspace and it have Proximity prompt example and click detector example go inside and you should see a script in the instance of it

I will show with proximity prompt but it’s same as of the click detector:

--[[
Don't copy this script into click detector it will not work
since click detector doesn't have the same event method of proximity prompt.
--]]

local RS = game:GetService("ReplicatedStorage")

local remotesF = RS:WaitForChild("Remotes", 10)

local joinRem = remotesF:WaitForChild("MatchmakingJoin", 10)
local queueRem = remotesF:WaitForChild("MatchmakingQueue", 10)

local prox = script.Parent
local gameMode = "easy"

prox.Triggered:Connect(function(player)
	queueRem:FireClient(player, true, gameMode)
	joinRem:Fire(player, gameMode)
end)

as you see in my proximity prompt it have easy mode selected and if you see my own settings it have an easy game mode fully set up with correct placeId so when players go interact with my proximity prompt they will be in easy mode matchmaking and the required players are two so the second player that will press proximity prompt or click detector (if both have same gamemode) while be teleported with the first player

But feel free to make edits of my script to teleport player only when both are in queue :smiley:

I hope everyone understand better and more clearly what it does and how it work thanks for reading.

My game should be working if you play it and press any click detector or proximity prompt you will be queued into the queue and if another player join and do exactly same this you will be teleported into another place with you and the other player and with only one part to test out if it was working lol

Here an example video from my own game with my alt and my account to see how it works :slight_smile:

1 Like