Module 04 · Leçon 3 sur 5
RemoteFunctions et BindableEvents
Pose une question au serveur et attends sa réponse avec une RemoteFunction, évite les pièges d’InvokeClient et fais dialoguer des scripts d’un même côté.
- ⏱️ 25 min
- 📝 Quiz de 6 questions
- 🔄 Vérifié le 24 octobre 2026
Sur cette page
- La RemoteFunction : une question, une réponse
- Exemple : demander son inventaire
- Trois règles pour des RemoteFunctions saines
- InvokeClient : la fausse bonne idée
- La bonne alternative : deux RemoteEvents
- Si tu dois vraiment utiliser InvokeClient
- Les Bindables : parler à un script du même côté
- Exemple : l’événement « étape atteinte »
- Ce qu’il faut savoir sur les Bindables
- Quel outil choisir ?
- Quiz de fin de leçon
🎯 Dans cette leçon, tu vas apprendre à…
- Faire une requête avec réponse grâce à InvokeServer et OnServerInvoke
- Connaître les risques d’InvokeClient et les éviter
- Faire communiquer des scripts d’un même côté avec BindableEvent et BindableFunction
- Choisir le bon outil de communication selon la situation
Avec un RemoteEvent, on envoie un message et on continue sa vie sans attendre de réponse. Mais parfois, le client a besoin d’une réponse : « quelles traînées est-ce que je possède ? ». C’est le travail de la RemoteFunction. Dans cette leçon, tu vas aussi découvrir pourquoi l’inverse (le serveur qui questionne un client) est risqué, et comment des scripts du même côté peuvent se parler avec les Bindables.
La RemoteFunction : une question, une réponse
Une RemoteFunction se range au même endroit que les RemoteEvents, dans ReplicatedStorage.Remotes. Elle fonctionne comme un appel téléphonique :
- le client appelle
InvokeServer(...)et attend (son script est mis en pause) ; - le serveur exécute la fonction qu’il a assignée à
OnServerInvoke(ce genre de fonction s’appelle un callback, ou « fonction de rappel ») ; - ce que cette fonction renvoie (avec
return) revient au client, et son script reprend.
Comme pour OnServerEvent, le serveur reçoit le joueur en premier paramètre, puis les arguments du client. Et les mêmes limites s’appliquent aux arguments et aux valeurs renvoyées : tables copiées, fonctions transformées en nil, tables mixtes à éviter, etc.
Exemple : demander son inventaire
Dans l’Obby des Nuages, l’interface de la boutique doit savoir quelles traînées le joueur possède déjà. Crée une RemoteFunction nommée ObtenirInventaire dans ReplicatedStorage.Remotes.
-- Script placé dans ServerScriptService (côté serveur)
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remotes = ReplicatedStorage:WaitForChild("Remotes")
local obtenirInventaire = remotes:WaitForChild("ObtenirInventaire") :: RemoteFunction
-- L'inventaire vit sur le serveur (source de vérité). Au module 5, il sera sauvegardé.
local inventaires: { [Player]: { string } } = {}
Players.PlayerAdded:Connect(function(player: Player)
inventaires[player] = {} -- nouvelle partie : aucun objet
end)
Players.PlayerRemoving:Connect(function(player: Player)
inventaires[player] = nil
end)
-- Callback : on lui AFFECTE une fonction avec =, on ne le « connecte » pas
obtenirInventaire.OnServerInvoke = function(player: Player)
local inventaire = inventaires[player] or {}
-- On renvoie une liste pure de textes : elle arrive au client sous forme de copie
return table.clone(inventaire)
end
-- LocalScript placé dans StarterPlayer > StarterPlayerScripts (côté client)
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local remotes = ReplicatedStorage:WaitForChild("Remotes")
local obtenirInventaire = remotes:WaitForChild("ObtenirInventaire") :: RemoteFunction
-- pcall : si l'appel échoue, notre script ne s'arrête pas net
local ok, resultat = pcall(function()
return obtenirInventaire:InvokeServer()
end)
if ok and typeof(resultat) == "table" then
print("Je possède", #resultat, "objet(s) :", table.concat(resultat, ", "))
else
warn("Impossible de récupérer l'inventaire :", resultat)
end
Remarque le typeof(resultat) == "table" côté client : on vérifie ce qu’on reçoit avant de l’utiliser. Bonne habitude, même si c’est le serveur qui répond. Le pcall protège contre une erreur levée pendant l’appel. Il ne fixe pas de limite de temps : si le serveur ne répond jamais, le script attend.
Trois règles pour des RemoteFunctions saines
- Un seul callback.
OnServerInvoken’accepte qu’une fonction à la fois : si tu en affectes plusieurs, seule la dernière s’exécute. - Répondre vite. Le client attend pendant tout le temps que met ton callback. Évite les longues attentes (
task.wait) dansOnServerInvoke. - Pas de réponse utile ? Pas de RemoteFunction. La documentation recommande un RemoteEvent quand on n’a pas besoin du résultat : c’est asynchrone et personne n’attend.
InvokeClient : la fausse bonne idée
La RemoteFunction marche aussi dans l’autre sens : le serveur appelle InvokeClient(player, ...) et le client répond via OnClientInvoke. La documentation déconseille fortement cette direction et liste trois risques :
- si le client lève une erreur (son code plante), le serveur lève aussi l’erreur ;
- si le client se déconnecte pendant l’appel,
InvokeClientlève une erreur ; - si le client ne répond jamais (son callback ne se termine pas, ou il n’en a défini aucun), le serveur attend pour toujours.
Or un tricheur contrôle entièrement le code de son client. Il peut donc bloquer volontairement ton script serveur. De plus, il n’existe pas de paramètre de délai maximum (timeout) dans InvokeClient : rien ne vient débloquer l’attente à ta place.
La bonne alternative : deux RemoteEvents
Dans la grande majorité des cas, le serveur n’a pas besoin d’attendre. La documentation fait remarquer que le client a rarement une information que le serveur n’a pas. Si le serveur veut quand même une réponse, utilise deux messages à sens unique :
- le serveur envoie une question avec
FireClient(sans attendre) ; - le client répond plus tard avec
FireServer; - le serveur traite la réponse s’il la reçoit, et la valide comme n’importe quel message client.
Si tu dois vraiment utiliser InvokeClient
Isole l’appel dans un fil d’exécution séparé (task.spawn), protège-le avec pcall, et fixe ta propre limite de temps :
-- Extrait d'un Script serveur (ServerScriptService)
-- Pose une question à un client sans risquer de bloquer le script appelant pour toujours
local function invoquerAvecDelai(remote: RemoteFunction, player: Player, delaiMax: number, question: string): (boolean, any)
local termine = false
local reussi = false
local reponse: any = nil
-- L'appel risqué tourne dans un fil séparé, protégé par pcall
task.spawn(function()
local ok, valeur = pcall(function()
return remote:InvokeClient(player, question)
end)
reussi = ok
reponse = valeur
termine = true
end)
-- On attend au maximum delaiMax secondes
local debut = os.clock()
while not termine and os.clock() - debut < delaiMax do
task.wait(0.1)
end
if not termine then
return false, "Délai dépassé"
end
return reussi, reponse -- la réponse vient du client : à valider avant usage !
end
Même ainsi, si le client ne répond jamais, le fil séparé reste en attente indéfiniment. C’est une raison de plus de préférer la solution avec deux RemoteEvents.
Les Bindables : parler à un script du même côté
Parfois, deux scripts du même côté doivent communiquer : par exemple, le script des checkpoints veut prévenir un autre script serveur (récompenses, statistiques…) qu’un joueur a atteint une étape. Les RemoteEvents ne servent pas à ça. On utilise les Bindables :
| Objet | Rôle | Envoyer | Recevoir |
|---|---|---|---|
BindableEvent |
Message à sens unique | Fire(...) |
.Event:Connect(...) |
BindableFunction |
Question avec réponse | Invoke(...) |
OnInvoke = function |
La documentation recommande de les placer dans ServerScriptService pour la communication entre scripts serveur, et dans ReplicatedStorage pour la communication entre scripts client.
Exemple : l’événement « étape atteinte »
Crée un BindableEvent nommé EtapeAtteinte dans ServerScriptService.
-- Extrait du Script des checkpoints (ServerScriptService) : on annonce l'étape aux autres scripts serveur
local ServerScriptService = game:GetService("ServerScriptService")
local etapeAtteinte = ServerScriptService:WaitForChild("EtapeAtteinte") :: BindableEvent
-- À appeler quand une étape est validée (par exemple dans le script Progression
-- de la leçon 5, juste après etape.Value = numero)
local function quandEtapeValidee(player: Player, numeroEtape: number)
-- Fire ne met pas le script en pause : les scripts abonnés réagissent de leur côté
etapeAtteinte:Fire(player, numeroEtape)
end
-- Script « Recompenses » placé dans ServerScriptService (côté serveur)
local ServerScriptService = game:GetService("ServerScriptService")
local etapeAtteinte = ServerScriptService:WaitForChild("EtapeAtteinte") :: BindableEvent
etapeAtteinte.Event:Connect(function(player: Player, numeroEtape: number)
print(player.Name .. " a atteint l'étape " .. numeroEtape)
-- Ici : bonus de pièces toutes les 5 étapes, statistiques (module 9)…
end)
L’avantage : le script des checkpoints n’a pas besoin de connaître tous les scripts qui s’intéressent aux étapes. Chacun s’abonne de son côté.
Ce qu’il faut savoir sur les Bindables
- Les arguments subissent les mêmes limites que les remotes : clés non textuelles converties en textes, tables mixtes à éviter, métatables perdues, tables copiées.
- Si plusieurs fonctions sont connectées au même
BindableEvent, leur ordre d’exécution n’est pas garanti. BindableFunction:Invoke()attend la réponse. Si aucunOnInvoken’a jamais été défini, le script qui appelle ne reprend jamais. Et comme pour les RemoteFunctions, seul le dernierOnInvokeaffecté compte.- La documentation rappelle que les ModuleScripts sont souvent une meilleure solution pour partager des données ou des fonctions entre scripts. Garde les Bindables pour les vrais « événements ».
Quel outil choisir ?
| Besoin | Outil |
|---|---|
| Le client demande une action au serveur | RemoteEvent (FireServer) |
| Le serveur prévient un joueur ou tout le monde | RemoteEvent (FireClient / FireAllClients) |
| Effet visuel éphémère, donnée continue | UnreliableRemoteEvent |
| Le client a besoin d’une réponse du serveur | RemoteFunction (InvokeServer) |
| Le serveur a besoin d’une réponse du client | Deux RemoteEvent (éviter InvokeClient) |
| Deux scripts du même côté | BindableEvent, BindableFunction ou un ModuleScript |
Ce qu’il faut retenir
- Une RemoteFunction fait une requête avec réponse :
InvokeServercôté client,OnServerInvoke = function(player, ...)côté serveur. - L’appelant attend la réponse ; le callback serveur doit être rapide et valider ses arguments.
OnServerInvokeest un callback : on l’affecte avec=, un seul à la fois.- Évite
InvokeClient: erreur relayée, déconnexion, attente infinie. PréfèreFireClientpuisFireServer. - BindableEvent / BindableFunction : communication entre scripts du même côté ; mêmes limites d’arguments que les remotes.