Aller au contenu
Forma Roblox

Rechercher dans la formation

Module 04 · Leçon 2 sur 5

Communiquer avec les RemoteEvents

Fais dialoguer tes LocalScripts et tes Scripts serveur avec les RemoteEvents, passe-leur des arguments et range-les proprement dans ReplicatedStorage.

  • ⏱️ 30 min
  • 📝 Quiz de 6 questions
  • 🔄 Vérifié le 24 octobre 2026
Sur cette page

🎯 Dans cette leçon, tu vas apprendre à…

  • Envoyer un message du client vers le serveur, et du serveur vers un ou tous les clients
  • Passer des arguments en connaissant les types transmissibles et leurs limites
  • Organiser ses remotes dans ReplicatedStorage
  • Savoir quand utiliser un UnreliableRemoteEvent

Dans la leçon précédente, tu as vu que le client ne peut pas modifier l’état du jeu pour les autres. Mais il doit bien pouvoir demander des choses : « je veux réapparaître au checkpoint », « je veux acheter cette traînée ». Et le serveur doit pouvoir prévenir les joueurs : « étape 3 atteinte ! ». C’est le rôle des RemoteEvents, les messages qui traversent la frontière entre client et serveur.

Un RemoteEvent, c’est une boîte aux lettres

Un RemoteEvent est un objet que tu places dans l’Explorer. Les deux côtés s’en servent comme d’une boîte aux lettres :

  • un côté envoie un message (avec Fire…) ;
  • l’autre côté écoute et réagit (avec On…Event).

C’est un message à sens unique : celui qui envoie n’attend pas de réponse et continue son code immédiatement.

Créer et ranger ses remotes

Les deux côtés doivent pouvoir voir le RemoteEvent. La documentation recommande ReplicatedStorage. Pour l’Obby des Nuages, on adopte une convention pour toute la formation :

  1. Dans l’Explorer, survole ReplicatedStorage, clique sur ⊕ et insère un Folder (dossier) nommé Remotes.
  2. Dans Remotes, insère un RemoteEvent pour chaque action, avec un nom qui décrit l’action : DemanderReapparition, Notification, AnnonceVictoire, AcheterObjet…
ReplicatedStorage
└── Remotes (Folder)
    ├── AcheterObjet (RemoteEvent)
    ├── AnnonceVictoire (RemoteEvent)
    ├── DemanderReapparition (RemoteEvent)
    └── Notification (RemoteEvent)

Du client vers le serveur

Côté client, on appelle FireServer(...). Côté serveur, on se connecte à OnServerEvent. Point capital : le serveur reçoit toujours le joueur expéditeur en premier paramètre, ajouté automatiquement par Roblox. Les arguments passés à FireServer arrivent ensuite.

Exemple du fil rouge : un bouton « Retour au checkpoint » pour un joueur coincé. Le client demande, le serveur fait réapparaître le personnage.

-- LocalScript placé dans StarterPlayer > StarterPlayerScripts (côté client)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")

-- WaitForChild : côté client, les objets peuvent arriver un peu après le démarrage du script
local remotes = ReplicatedStorage:WaitForChild("Remotes")
local demanderReapparition = remotes:WaitForChild("DemanderReapparition") :: RemoteEvent

-- Touche R : on DEMANDE au serveur, on ne fait rien nous-mêmes
UserInputService.InputBegan:Connect(function(input: InputObject, dejaTraite: boolean)
	if dejaTraite then
		return -- le joueur tapait dans le chat, par exemple
	end
	if input.KeyCode == Enum.KeyCode.R then
		demanderReapparition:FireServer()
	end
end)
-- Script placé dans ServerScriptService (côté serveur)
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local remotes = ReplicatedStorage:WaitForChild("Remotes")
local demanderReapparition = remotes:WaitForChild("DemanderReapparition") :: RemoteEvent

local DELAI_MINIMUM = 5 -- secondes entre deux demandes d'un même joueur
local derniereDemande: { [Player]: number } = {}

-- Le premier paramètre est TOUJOURS le joueur qui a envoyé le message
demanderReapparition.OnServerEvent:Connect(function(player: Player)
	local maintenant = os.clock()
	local precedente = derniereDemande[player]
	if precedente and maintenant - precedente < DELAI_MINIMUM then
		return -- trop rapproché : on ignore (on détaillera ces protections en leçon 4)
	end
	derniereDemande[player] = maintenant
	player:LoadCharacterAsync() -- recrée le personnage du joueur (remplace l'ancienne méthode LoadCharacter, dépréciée)
end)

-- On oublie le joueur quand il part, pour ne pas garder de données inutiles
Players.PlayerRemoving:Connect(function(player: Player)
	derniereDemande[player] = nil
end)

Le :: RemoteEvent en fin de ligne est une annotation de type, vue dans la leçon 6 du module 3 : elle aide Studio à proposer les bonnes méthodes, sans rien changer à l’exécution.

Tu remarques le petit délai minimum ? Sans lui, un joueur (ou un programme de triche) pourrait envoyer la demande en rafale, des centaines de fois par seconde. La référence de Player déconseille d’ailleurs de rappeler LoadCharacterAsync pour un même joueur avant que son apparence ait fini de se charger. On approfondira ces protections dans la leçon 4.

Du serveur vers les clients

Vers un seul joueur : FireClient

Le serveur appelle FireClient(player, ...) : le premier argument indique à qui envoyer. Côté client, on écoute OnClientEvent. Pas besoin de paramètre player ici : le client sait qui il est grâce à Players.LocalPlayer.

-- Script placé dans ServerScriptService (côté serveur)
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local notification = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("Notification") :: RemoteEvent

-- Prévient UN joueur précis
local function prevenirEtape(player: Player, numeroEtape: number)
	notification:FireClient(player, "Étape " .. numeroEtape .. " atteinte !")
end

-- Démonstration : chaque joueur reçoit un message 5 secondes après son arrivée
Players.PlayerAdded:Connect(function(player: Player)
	task.wait(5)
	prevenirEtape(player, 1)
end)
-- LocalScript placé dans StarterPlayer > StarterPlayerScripts (côté client)
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local notification = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("Notification") :: RemoteEvent

notification.OnClientEvent:Connect(function(message: string)
	-- Pour l'instant on affiche dans l'Output (au module 6, un LocalScript l'affichera à l'écran)
	print("Notification :", message)
end)

Vers tout le monde : FireAllClients

FireAllClients(...) envoie le message à tous les clients connectés. Exemple : annoncer qu’un joueur a terminé l’obby.

-- Script placé dans ServerScriptService (côté serveur)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local Players = game:GetService("Players")

local annonceVictoire = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("AnnonceVictoire") :: RemoteEvent
local ligneArrivee = Workspace:WaitForChild("LigneArrivee") :: BasePart

local dejaAnnonces: { [Player]: boolean } = {}

ligneArrivee.Touched:Connect(function(partieTouchee: BasePart)
	local player = Players:GetPlayerFromCharacter(partieTouchee.Parent)
	if player and not dejaAnnonces[player] then
		dejaAnnonces[player] = true
		-- Tous les clients reçoivent le nom du gagnant
		annonceVictoire:FireAllClients(player.Name)
	end
end)

Players.PlayerRemoving:Connect(function(player: Player)
	dejaAnnonces[player] = nil
end)

Attention, ce script fait encore confiance au contact avec la ligne d’arrivée : la leçon 5 ajoutera la vérification « a-t-il vraiment passé toutes les étapes ? ».

Ce qu’on peut envoyer (et ce qui se perd en route)

Tu peux passer plusieurs arguments à FireServer, FireClient et FireAllClients. La plupart des types passent sans souci :

  • nombres, textes (string), booléens, nil ;
  • types Roblox comme Vector3, CFrame, Color3, les Enum ;
  • des instances (une Part, un joueur…), à condition que le destinataire puisse les voir ;
  • des tables, avec des limites.

La documentation détaille ces limites, à connaître absolument :

Situation Ce qui arrive
Une fonction passée en argument Elle n’est pas transmise : le destinataire reçoit nil.
Une table dont les clés ne sont pas des textes (une instance, par exemple) Les clés sont converties en textes.
Une table mixte (des clés numériques ET des clés texte) À éviter : envoie soit une liste, soit un dictionnaire.
Une table avec des trous (nil au milieu) À éviter, pour les listes comme pour les dictionnaires.
Une table avec une métatable La métatable est perdue : seuls les champs directs de la table arrivent.
N’importe quelle table Elle est copiée : le destinataire reçoit une nouvelle table, pas la même.
Une instance que le destinataire ne voit pas (dans ServerStorage pour un client, ou créée par un LocalScript pour le serveur) Elle arrive sous la forme nil.
-- LocalScript (côté client) : exemples d'envois vers un RemoteEvent de test
-- (pour l'essayer, crée un RemoteEvent « RemoteTest » dans ReplicatedStorage.Remotes)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")

local remoteTest = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("RemoteTest") :: RemoteEvent

-- Bien : une liste pure
remoteTest:FireServer({ "TrailNuage", "TrailSoleil" })

-- Bien : un dictionnaire pur
remoteTest:FireServer({ nom = "Traînée Nuage", couleur = Color3.fromRGB(0, 170, 255) })

-- À éviter : table mixte (liste + dictionnaire)
remoteTest:FireServer({ "TrailNuage", equipee = true })

-- Le serveur recevra nil : cette Part n'existe que sur ce client
local partLocale = Instance.new("Part")
partLocale.Parent = Workspace
remoteTest:FireServer(partLocale)

Fiabilité, ordre et limites de débit

Selon la documentation, un RemoteEvent est fiable et ordonné : un message perdu est renvoyé tant que le destinataire reste connecté, et les messages arrivent dans l’ordre d’envoi. Deux cas où un message peut quand même se perdre :

  • personne n’écoute : Roblox garde les messages en file d’attente jusqu’à ce qu’une fonction se connecte, mais cette file est limitée. Une fois pleine, les messages suivants sont jetés et une erreur apparaît dans l’Output ;
  • le destinataire se déconnecte avant de recevoir le message.

Les messages envoyés par un client au serveur sont aussi limités en débit. Au moment de la rédaction (octobre 2026), la référence de RemoteEvent indique environ 500 requêtes par seconde et par client, une limite partagée entre tous les RemoteEvents du jeu (et, séparément, entre tous les UnreliableRemoteEvents). Pour un RemoteEvent classique, les messages en excès sont traités plus tard, pas jetés. Ce chiffre peut évoluer : vérifie la page officielle. Retiens surtout qu’il ne faut pas envoyer de messages en boucle sans raison.

Aperçu : l’UnreliableRemoteEvent

Il existe une variante : l’UnreliableRemoteEvent (« remote event non fiable »). Il s’utilise exactement comme un RemoteEvent (FireServer, FireClient, FireAllClients, OnServerEvent, OnClientEvent), mais il échange la fiabilité contre de meilleures performances réseau :

  • un message perdu n’est pas renvoyé ;
  • les messages peuvent arriver dans le désordre ;
  • un message de plus de 1 000 octets est jeté ;
  • s’il n’y a personne pour écouter, le message est jeté tout de suite (pas de file d’attente) ;
  • si un client dépasse la limite de débit, les messages en trop sont jetés.

Il sert pour les effets éphémères et les données qui changent en continu, quand seule la dernière valeur compte. Exemple : un petit effet d’étincelles chez tous les joueurs quand une pièce est ramassée. Si un message se perd, personne ne s’en rendra compte.

-- Extrait d'un Script serveur : effet purement visuel, la perte d'un message est sans gravité
-- (crée d'abord un UnreliableRemoteEvent « EffetEtincelles » dans ReplicatedStorage.Remotes)
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local effetEtincelles = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("EffetEtincelles") :: UnreliableRemoteEvent

local function montrerEtincelles(position: Vector3)
	effetEtincelles:FireAllClients(position)
end

montrerEtincelles(Vector3.new(0, 50, 0))

Pour tout ce qui compte (achat, progression, annonce de victoire), garde un RemoteEvent classique.

Ce qu’il faut retenir

  • Un RemoteEvent transporte un message à sens unique entre client et serveur ; on les range dans ReplicatedStorage.Remotes.
  • Client → serveur : FireServer(...) et OnServerEvent, dont le premier paramètre est toujours le joueur expéditeur.
  • Serveur → client : FireClient(player, ...) ; serveur → tous : FireAllClients(...) ; côté client : OnClientEvent.
  • Limites des arguments : fonctions → nil, tables mixtes et trous à éviter, métatables perdues, tables copiées, instances invisibles pour le destinataire → nil.
  • L’UnreliableRemoteEvent sacrifie fiabilité et ordre pour les données éphémères (1 000 octets maximum par message).
  • Le client peut envoyer n’importe quoi : le serveur devra tout vérifier.

Sources et pour aller plus loin

Quiz : vérifie tes acquis

6 questions · réussite à partir de 70 % · valide chaque réponse pour voir l’explication

  1. Question 1QCMCôté serveur, quel est le premier paramètre reçu par la fonction connectée à OnServerEvent ?
  2. Question 2Vrai ou fauxDeux clients peuvent s’envoyer un message directement avec un RemoteEvent, sans passer par le serveur.
  3. Question 3QCMLe serveur envoie au client une Part rangée dans ServerStorage avec FireClient. Que reçoit le client ?
  4. Question 4QCMDans quels cas un UnreliableRemoteEvent est-il un bon choix ? (plusieurs réponses possibles)

    Plusieurs réponses possibles : coche toutes les bonnes.

  5. Question 5Trouve le bugCe Script serveur doit afficher l’identifiant de l’objet que le joueur veut acheter. Il affiche pourtant le nom du joueur. Où est le bug ?
    local ReplicatedStorage = game:GetService("ReplicatedStorage")
    
    local remotes = ReplicatedStorage:WaitForChild("Remotes")
    local acheterObjet = remotes:WaitForChild("AcheterObjet")
    
    acheterObjet.OnServerEvent:Connect(function(idObjet)
    	print("Demande d'achat :", idObjet)
    end)
  6. Question 6Trouve le bugTrouve la faille dans ce code généré par IA. Ce Script serveur gère la touche « Retour au checkpoint ». Il fonctionne en test, mais un tricheur peut s’en servir pour gêner les autres joueurs. Où est la faille ?
    local Players = game:GetService("Players")
    local demanderReapparition = game:GetService("ReplicatedStorage"):WaitForChild("Remotes"):WaitForChild("DemanderReapparition") :: RemoteEvent
    local derniereDemande: { [Player]: number } = {}
    demanderReapparition.OnServerEvent:Connect(function(player: Player, cible: Player)
    	local maintenant = os.clock()
    	local precedente = derniereDemande[player]
    	if precedente and maintenant - precedente < 5 then
    		return
    	end
    	derniereDemande[player] = maintenant
    	cible:LoadCharacterAsync()
    end)
    Players.PlayerRemoving:Connect(function(player: Player)
    	derniereDemande[player] = nil
    end)