Module 03 · Leçon 5 sur 6
Réagir aux événements
Comprends les événements de Roblox (contacts, joueurs, personnages), le debounce et les attributs, repère les connexions en trop dans du code généré, et mets en place les checkpoints de ton obby.
- ⏱️ 45 min
- 📝 Quiz de 7 questions
- 🔄 Vérifié le 24 octobre 2026
Sur cette page
- Qu’est-ce qu’un événement ?
- Couper une connexion : Disconnect et Once
- Attendre un événement : :Wait()
- L’événement Touched en détail
- Les joueurs et leurs personnages
- WaitForChild ou FindFirstChild ?
- task.spawn et task.delay
- Le debounce : éviter les déclenchements en rafale
- Les attributs
- Fil rouge : les checkpoints de l’Obby des Nuages
- Préparer la scène
- Le code
- Lire ce code comme un relecteur
- Exercice 1 : dans quel ordre l’Output affiche-t-il les messages ?
- Exercice 2 : que se passe-t-il si… ?
- Quiz de fin de leçon
🎯 Dans cette leçon, tu vas apprendre à…
- Lire une connexion à un événement et savoir quand elle se déclenche
- Comprendre l’arrivée des joueurs et l’apparition de leurs personnages
- Repérer un debounce manquant ou une connexion créée en trop
- Stocker des informations sur des objets avec les attributs
Pour l’instant, ton code s’exécute au lancement du jeu puis s’arrête, sauf la fonction surContact de la kill brick, qui se déclenche « toute seule ». C’est grâce à un événement. Presque tout ce qui rend un jeu vivant passe par des événements : un joueur arrive, un personnage réapparaît, une Part est touchée, une valeur change… C’est aussi là que le code généré par une IA cache ses bugs les plus sournois : un contact compté dix fois, une connexion créée en trop à chaque saut, un joueur « oublié » au démarrage. Dans cette leçon, tu apprends à lire et à maîtriser les événements, puis tu mets en place les checkpoints de l’Obby des Nuages.
Qu’est-ce qu’un événement ?
Un événement (event) est un signal qu’un objet envoie quand quelque chose se produit. Tu peux y connecter une fonction : Roblox l’appellera automatiquement à chaque fois que le signal est envoyé, en lui passant des informations utiles (les arguments de l’événement).
-- Script placé DANS une Part du Workspace
local part = script.Parent
-- Touched envoie en argument la Part qui a touché
local function surContact(autrePart)
print(part.Name .. " a été touchée par " .. autrePart.Name)
end
part.Touched:Connect(surContact) -- ATTENTION : pas de () après surContact
On écrit surContact sans parenthèses : on donne la fonction elle-même à Roblox, qui l’appellera plus tard. Avec surContact(), tu l’appellerais tout de suite, une seule fois.
Couper une connexion : Disconnect et Once
:Connect renvoie un objet connexion (RBXScriptConnection). Garde-le dans une variable si tu veux arrêter d’écouter l’événement plus tard.
-- Script placé DANS une Part : réagit seulement aux 3 premiers contacts
local part = script.Parent
local nombreContacts = 0
local connexion
connexion = part.Touched:Connect(function(autrePart)
nombreContacts += 1
print("Contact n°" .. nombreContacts .. " avec " .. autrePart.Name)
if nombreContacts >= 3 then
connexion:Disconnect() -- la fonction ne sera plus jamais appelée
end
end)
-- Once : connexion qui se coupe automatiquement après le premier déclenchement
part.Touched:Once(function(autrePart)
print("Premier contact de la partie :", autrePart.Name)
end)
Bon à savoir : d’après la documentation Roblox, quand un objet est détruit (:Destroy()), les connexions à ses événements sont en général coupées automatiquement. Couper toi-même une connexion devenue inutile reste une bonne pratique.
Attendre un événement : :Wait()
:Wait() met le script en pause jusqu’au prochain déclenchement de l’événement, puis renvoie ses arguments. L’exemple le plus courant récupère le personnage d’un joueur, qu’il soit déjà apparu ou pas encore :
-- Script dans ServerScriptService
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
-- Si le personnage existe déjà, on le prend ; sinon on attend qu'il apparaisse
local personnage = player.Character or player.CharacterAdded:Wait()
print("Personnage prêt :", personnage.Name)
end)
Tu reconnais l’astuce du or de la leçon 2 : si player.Character vaut nil, on attend le prochain CharacterAdded.
L’événement Touched en détail
Touched est l’événement des obbys : kill bricks, checkpoints, pièces, plateformes piégées… Ce qu’en dit la documentation Roblox :
- il se déclenche à cause d’un mouvement physique : déplacer une Part en changeant son CFrame pour qu’elle chevauche une autre ne le déclenche pas ;
- au moins une des deux Parts ne doit pas être ancrée (Anchored). Ça tombe bien : le personnage du joueur ne l’est pas ;
- la propriété CanTouch doit être activée (elle l’est par défaut) ;
- il peut se déclencher plusieurs fois de suite pour un seul contact, à cause de petites collisions successives (et chaque membre du personnage qui touche la Part compte).
Pour savoir quel joueur a touché, on utilise Players:GetPlayerFromCharacter(autrePart.Parent), qui renvoie le joueur à qui appartient ce personnage, ou nil si ce n’est pas un personnage de joueur.
Les joueurs et leurs personnages
Deux niveaux à bien distinguer :
- le joueur (
Player), qui existe de son arrivée à son départ, dans le service Players ; - son personnage (
Character), le Model 3D dans le Workspace, détruit et recréé à chaque réapparition.
| Événement | Quand il se déclenche |
|---|---|
Players.PlayerAdded |
Une fois, quand un joueur rejoint la partie |
Players.PlayerRemoving |
Une fois, quand un joueur quitte la partie |
player.CharacterAdded |
À chaque apparition et réapparition du personnage |
player.CharacterRemoving |
Juste avant que le personnage soit retiré (souvent après sa mort) |
-- Script dans ServerScriptService
local Players = game:GetService("Players")
local function surPersonnageAjoute(personnage)
print(personnage.Name .. " vient d'apparaître")
end
local function surJoueurAjoute(player)
print(player.Name .. " a rejoint l'Obby des Nuages")
player.CharacterAdded:Connect(surPersonnageAjoute)
end
Players.PlayerAdded:Connect(surJoueurAjoute)
-- Les joueurs déjà présents avant la connexion ne déclenchent pas PlayerAdded :
-- on les traite à la main pour ne rater personne
for _, player in Players:GetPlayers() do
-- task.spawn appelle surJoueurAjoute(player) sans bloquer la boucle (détaillé plus bas)
task.spawn(surJoueurAjoute, player)
end
WaitForChild ou FindFirstChild ?
FindFirstChild("Nom")cherche un enfant tout de suite et renvoienils’il n’existe pas. À utiliser quand l’objet peut légitimement être absent, avec un testif objet then.WaitForChild("Nom")attend que l’enfant existe, puis le renvoie. À utiliser quand l’objet doit exister mais n’est peut-être pas encore chargé (très fréquent côté client, voir module 4).
Si l’objet n’arrive jamais, WaitForChild bloque le script ; après 5 secondes, l’Output affiche un avertissement « Infinite yield possible on… ». Tu peux donner un délai maximal : WaitForChild("Nom", 10) renvoie nil après 10 secondes.
task.spawn et task.delay
Certaines fonctions mettent le script en pause (task.wait, :Wait(), WaitForChild…). Pour lancer du code sans bloquer la suite, la bibliothèque task propose :
task.spawn(fonction, arguments...)exécute la fonction immédiatement, mais dans un fil d’exécution séparé : si elle se met en pause, le reste du script continue ;task.delay(secondes, fonction, arguments...)exécute la fonction plus tard, après le délai, sans bloquer le script.
-- Script dans ServerScriptService
task.delay(5, function()
print("5 secondes après le lancement")
end)
task.spawn(function()
while true do
task.wait(60)
print("Une minute de plus !")
end
end)
print("Ce message s'affiche en premier : rien ne bloque")
Rappel : spawn et delay (sans task.) sont dépréciées, comme wait.
Le debounce : éviter les déclenchements en rafale
Comme Touched se déclenche plusieurs fois par contact, une lave qui retire 20 PV (points de vie) par contact en retirerait 60 ou 80 d’un coup. La solution s’appelle un debounce : un verrou qui ignore les nouveaux déclenchements pendant un court délai.
Le debounce le plus simple est une variable true/false, mais il bloque tous les joueurs pendant le délai. Dans un jeu multijoueur, on préfère un verrou par joueur, rangé dans une table :
-- Script placé DANS une Part « Lave » (retire 20 PV, au plus une fois par seconde et par joueur)
local Players = game:GetService("Players")
local lave = script.Parent
local DEGATS = 20
local DELAI = 1
local joueursEnAttente = {} -- clé = joueur, valeur = true pendant le délai
lave.Touched:Connect(function(autrePart)
local personnage = autrePart.Parent
local player = Players:GetPlayerFromCharacter(personnage)
if not player or joueursEnAttente[player] then
return -- pas un joueur, ou joueur déjà « verrouillé » : on ignore
end
local humanoid = personnage:FindFirstChildOfClass("Humanoid")
if not humanoid then
return
end
joueursEnAttente[player] = true -- on verrouille AVANT d'agir
humanoid:TakeDamage(DEGATS)
task.delay(DELAI, function()
joueursEnAttente[player] = nil -- on déverrouille après le délai
end)
end)
Les attributs
Les attributs sont des propriétés personnalisées que tu ajoutes toi-même à n’importe quel objet : un nombre, un texte, un booléen, une couleur, un Vector3… Tu peux les créer dans Studio (en bas de la fenêtre Properties, section Attributes) ou par script :
-- Script dans ServerScriptService
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
-- SetAttribute crée l'attribut s'il n'existe pas, ou le modifie
player:SetAttribute("Etape", 0)
-- GetAttributeChangedSignal : un événement déclenché quand CET attribut change
player:GetAttributeChangedSignal("Etape"):Connect(function()
-- la fonction ne reçoit rien : on relit la valeur avec GetAttribute
print(player.Name .. " est maintenant à l'étape " .. player:GetAttribute("Etape"))
end)
end)
À savoir :
GetAttribute("Nom")renvoienilsi l’attribut n’existe pas ;SetAttribute("Nom", nil)le supprime ;- les noms d’attributs n’utilisent que des lettres, des chiffres et
_ - . /, sans espaces, 100 caractères maximum, et ne commencent pas parRBX; - les changements faits par le serveur sont répliqués (transmis) aux clients : un LocalScript peut donc lire un attribut du joueur pour l’afficher (au module 5, l’interface de la boutique s’en servira pour savoir quelles traînées le joueur possède) ;
- pendant un test, sélectionne l’objet dans l’Explorer : ses attributs s’affichent en direct dans Properties, idéal pour déboguer.
Fil rouge : les checkpoints de l’Obby des Nuages
Objectif : quand un joueur touche le checkpoint suivant, on retient son étape ; quand il meurt (kill brick !), il réapparaît sur son dernier checkpoint.
Préparer la scène
- Dans le Workspace, crée un Folder nommé
Checkpoints. - Place des Parts plates et bien visibles sur ton parcours, ancrées, et nomme-les
1,2,3… dans l’ordre du parcours (le nom est le numéro d’étape). - Range-les dans
Checkpoints.
Le code
Ce script est plus long que les précédents : c’est typiquement le genre de code que tu feras écrire par Claude Code (prompt dans l’encadré plus bas), dans le fichier src/ServerScriptService/Checkpoints.server.luau de ton projet Rojo. Ton rôle est de le lire : repère chaque événement connecté, et demande-toi à chaque fois « quand se déclenche-t-il, et combien de fois ? ».
-- Script « Checkpoints » dans ServerScriptService
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local dossierCheckpoints = Workspace:WaitForChild("Checkpoints")
local HAUTEUR_REAPPARITION = Vector3.new(0, 4, 0) -- pour apparaître AU-DESSUS du checkpoint
-- Quand un joueur touche un checkpoint
local function surCheckpointTouche(checkpoint, autrePart)
local player = Players:GetPlayerFromCharacter(autrePart.Parent)
if not player then
return -- ce n'est pas un joueur
end
local numero = tonumber(checkpoint.Name) -- "3" devient 3 (nil si le nom n'est pas un nombre)
local etapeActuelle = player:GetAttribute("Etape") or 0
-- Un checkpoint n'est accepté que s'il est le SUIVANT (étape actuelle + 1) :
-- impossible de sauter des étapes ou de revenir en arrière.
-- Cette condition sert aussi de debounce naturel : retoucher le même checkpoint ne fait rien.
if numero and numero == etapeActuelle + 1 then
player:SetAttribute("Etape", numero)
print(`{player.Name} a atteint l'étape {numero}`)
end
end
-- Quand le personnage (ré)apparaît : on le replace sur son checkpoint
local function placerAuCheckpoint(player, personnage)
local etape = player:GetAttribute("Etape") or 0
local checkpoint = dossierCheckpoints:FindFirstChild(tostring(etape))
if not (checkpoint and checkpoint:IsA("BasePart")) then
return -- étape 0 ou checkpoint introuvable : la SpawnLocation fait son travail
end
-- CharacterAdded se déclenche AVANT que le personnage soit placé dans le Workspace :
-- on attend qu'il y soit, puis une image de plus pour laisser Roblox finir de le positionner
if not personnage:IsDescendantOf(Workspace) then
personnage.AncestryChanged:Wait()
end
task.wait()
if not personnage:IsDescendantOf(Workspace) then
return -- le personnage a déjà disparu pendant l'attente (joueur parti, nouvelle réapparition...)
end
personnage:PivotTo(checkpoint.CFrame + HAUTEUR_REAPPARITION)
end
local function surJoueurAjoute(player)
player:SetAttribute("Etape", 0)
player.CharacterAdded:Connect(function(personnage)
placerAuCheckpoint(player, personnage)
end)
end
-- 1) Connecter chaque checkpoint
for _, checkpoint in dossierCheckpoints:GetChildren() do
if checkpoint:IsA("BasePart") then
checkpoint.Touched:Connect(function(autrePart)
surCheckpointTouche(checkpoint, autrePart)
end)
end
end
-- 2) Gérer les joueurs (y compris ceux déjà présents)
Players.PlayerAdded:Connect(surJoueurAjoute)
for _, player in Players:GetPlayers() do
task.spawn(surJoueurAjoute, player)
end
Ce qu’il faut comprendre :
- l’étape de chaque joueur est rangée dans un attribut
Etapesur son objetPlayer: c’est le numéro du dernier checkpoint validé, 0 au départ ; - un checkpoint
Nn’est accepté que siN == etape + 1: le joueur doit les valider dans l’ordre ; tonumber(checkpoint.Name)transforme le nom de la Part en nombre : c’est pour ça que les checkpoints s’appellent1,2,3… ;- on utilise une fonction anonyme dans
Touched:Connectpour transmettre aussi lecheckpointconcerné àsurCheckpointTouche; PivotTodéplace tout le Model du personnage d’un coup, vers une position et une orientation (un CFrame) ; on ajoute 4 studs de hauteur pour ne pas apparaître dans la Part.
Lire ce code comme un relecteur
Exercice 1 : dans quel ordre l’Output affiche-t-il les messages ?
-- Script dans ServerScriptService
print("A")
task.delay(1, function()
print("B")
end)
task.spawn(function()
print("C")
task.wait(2)
print("D")
end)
print("E")
Exercice 2 : que se passe-t-il si… ?
Une IA a proposé ce début de script. Ton dossier s’appelle Checkpoints (avec un « s »).
-- Script dans ServerScriptService (proposé par une IA)
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local dossierCheckpoints = Workspace:WaitForChild("Checkpoint")
Players.PlayerAdded:Connect(function(player)
player:SetAttribute("Etape", 0)
print(player.Name .. " commence à l'étape 0")
end)
print(#dossierCheckpoints:GetChildren(), "checkpoints")
- Que se passe-t-il au lancement ?
- Une fois le nom corrigé, il arrive en Play Solo que le message de la ligne 9 ne s’affiche jamais. Pourquoi ?
Ce qu’il faut retenir
evenement:Connect(fonction)appelle la fonction à chaque déclenchement et renvoie une connexion (:Disconnect()) ;:Oncene réagit qu’une fois ;:Wait()met en pause jusqu’au déclenchement.Touchedse déclenche plusieurs fois par contact : protège-le avec un debounce, idéalement par joueur.PlayerAdded= arrivée du joueur (une fois) ;CharacterAdded= chaque apparition du personnage. Traite aussi les joueurs déjà présents.WaitForChildattend un objet,FindFirstChildle cherche tout de suite (et peut renvoyernil).task.spawnettask.delaylancent du code sans bloquer ; les attributs stockent tes propres données sur les objets.- En relisant, demande-toi pour chaque
:Connect: quand se déclenche-t-il, combien de fois, et la connexion n’est-elle créée qu’une fois ?
Sources et pour aller plus loin
- Events — documentation Roblox
- Debounce patterns — documentation Roblox
- Properties and attributes — documentation Roblox
- Instance attributes dans la fenêtre Properties (réplication) — documentation Roblox
- SpawnLocation (choix aléatoire entre plusieurs points d’apparition) — référence API
- Players (PlayerAdded, GetPlayers, GetPlayerFromCharacter) — référence API
- Player (CharacterAdded, ordre de chargement du personnage, RespawnLocation) — référence API
- Instance (WaitForChild, FindFirstChild, SetAttribute) — référence API
- BasePart (Touched, CanTouch) — référence API
- Bibliothèque task — référence API
- Fil DevForum « Checkpoint script not working » (retour de développeurs : attendre avant de replacer le personnage)