Catégories
Jeu vidéo ROBLOX

Crée ton propre Quiz de calcul

Introduction

Bienvenue dans ce tutoriel ! Tu vas apprendre à programmer en Lua avec Roblox Studio en construisant un vrai mini-jeu : le Quiz de calcul.

Le principe est simple : un joueur marche jusqu’à une zone(on appelle ça une Hitbox). Dès qu’il entre dans cette zone, un QCM de calcul apparaît à l’écran avec 4 boutons de réponse. S’il clique sur la bonne réponse, le jeu devient un peu plus difficile (des nombres plus grands, puis une nouvelle opération : addition, soustraction, multiplication, division). S’il se trompe, il perd de la vie.

C’est un jeu qui combine deux choses : de la logique de programmation (variables, boucles, fonctions, conditions, événements) et des maths (le calcul mental). Tu vas construire ce jeu étape par étape, en testant à chaque fois ce que tu écris.

Ce dont tu as besoin :

  • Roblox Studio installé et ouvert
  • Un nouveau projet vide (« Baseplate » par exemple)
  • De la curiosité, et l’envie de te tromper pour apprendre !

Une variable, c’est comme une boîte étiquetée dans laquelle on range une information (un nombre, un texte…). Une fonction, c’est comme une recette de cuisine : une suite d’instructions qu’on peut réutiliser autant de fois qu’on veut. Garde ces images en tête, on va s’en servir tout le long du tutoriel.

Étape 1 : Comprendre le jeu avant de coder

But : avant d’écrire la moindre ligne de code, il faut savoir exactement ce que le jeu doit faire. C’est une étape que beaucoup de débutants sautent, et c’est une erreur : coder sans plan, c’est comme construire une maison sans plan de l’architecte.

Concepts clés : analyse d’un problème, cahier des charges.

Explications

Voici les règles de notre jeu, écrites en langage humain (pas encore en code) :

  1. Un joueur touche une zone au sol (la Hitbox).
  2. Une question de calcul apparaît : un nombre à trouver, et 4 boutons de réponse possibles.
  3. Un seul bouton contient la bonne décomposition (par exemple si le nombre cible est 14, la bonne réponse pourrait être « 9 + 5 »).
  4. Si le joueur clique sur la bonne réponse : une nouvelle question apparaît, un peu plus difficile.
  5. Si le joueur clique sur une mauvaise réponse : il perd des points de vie.
  6. Quand le joueur sort de la zone, le quiz disparaît.
  7. Au fil des bonnes réponses, le jeu change d’opération : d’abord des additions, puis des soustractions, puis des multiplications, puis des divisions.

À toi de jouer !

Avant de coder quoi que ce soit, prends une feuille et réponds à ces questions :

  • Quelles informations le jeu doit-il « retenir » en permanence ? (Indice : la vie du joueur, la difficulté actuelle…)
  • Quels sont les événements qui déclenchent une action ? (Indice : « toucher » une zone, « cliquer » sur un bouton…)

QCM

1. Pourquoi analyse-t-on le jeu avant de coder ?

  • a) Parce que c’est obligatoire dans Roblox Studio
  • b) Pour savoir précisément ce qu’on doit programmer et éviter de se perdre
  • c) Pour que le code soit plus court

2. Dans notre jeu, quel événement déclenche l’apparition du quiz ?

  • a) Le joueur appuie sur une touche du clavier
  • b) Le joueur entre en contact avec la Hitbox
  • c) Le joueur atteint un score de 10

Pour aller plus loin

Imagine une variante du jeu (un quiz de vocabulaire, un quiz de géographie…) et écris ses règles sur ta feuille, de la même façon que ci-dessus.

Étape 2 : Créer la structure des objets dans l’Explorer

But : construire dans Roblox Studio les objets (les « briques ») sur lesquels ton script va s’appuyer, avant même d’écrire du code.

Concepts clés : hiérarchie d’objets, Explorer, Workspace, ScreenGui.

Explications

Dans Roblox, tout ce qui existe dans le jeu (une partie de décor, une zone invisible, une interface) est un objet, rangé dans une arborescence visible dans la fenêtre Explorer. C’est comme les dossiers et sous-dossiers sur un ordinateur.

Pour notre jeu, on a besoin de :

Dans Workspace :

  • Un dossier nommé NumberGame
    • Une Part (bloc) nommée HitBox, transparente, non collisionnable (CanCollide = false), qui représente la zone au sol

Dans StarterGui (l’interface qui sera copiée pour chaque joueur) :

  • Un ScreenGui
    • Un cadre nommé NumberGenerator contenant un ou plusieurs TextLabel (pour afficher le nombre à décomposer)
    • Un cadre nommé Answers contenant 4 TextButton (les boutons de réponse)

À toi de jouer !

  1. Dans l’Explorer, clique droit sur Workspace → Insert Object → Folder, renomme-le NumberGame.
  2. À l’intérieur, insère une Part, renomme-la HitBox, réduis son Transparency à 0.5 et désactive CanCollide.
  3. Dans StarterGui, insère un ScreenGui, puis à l’intérieur deux Frame nommés NumberGenerator et Answers.
  4. Dans NumberGenerator, ajoute un TextLabel. Dans Answers, ajoute 4 TextButton.

Teste : tu dois voir dans l’Explorer une arborescence qui ressemble à un plan bien rangé, sans avoir encore écrit une ligne de code.

QCM

1. Où doit-on placer la Hitbox du jeu ?

  • a) Dans StarterGui
  • b) Dans Workspace
  • c) Dans ServerScriptService

2. À quoi sert un ScreenGui ?

  • a) À afficher des éléments d’interface à l’écran du joueur
  • b) À déplacer le personnage
  • c) À créer une zone de collision

Pour aller plus loin

Documentation Roblox sur la hiérarchie des objets

Étape 3 : Créer le premier script — LocalScript ou Script ?

But : comprendre la différence entre un script qui tourne côté serveur et un script qui tourne côté joueur, puis choisir le bon pour notre jeu.

Concepts clés : Script (serveur), LocalScript (client), sécurité, autorité serveur.

Explications

Dans Roblox, il existe deux grandes familles de scripts :

  • Le Script s’exécute sur le serveur. Il ne tourne qu’une seule fois pour tout le monde, et voit tous les joueurs en même temps. C’est le serveur qui a toujours raison (« l’autorité »), un joueur ne peut pas le tricher.
  • Le LocalScript s’exécute sur l’ordinateur du joueur (le « client »). Il est rapide pour l’affichage, mais un joueur malveillant peut le modifier ou le désactiver pour tricher.

Notre choix : un Script placé dans ServerScriptService.

Pourquoi ? Parce que notre jeu doit :

  • Détecter tous les joueurs qui touchent la même Hitbox partagée dans le monde (pas propre à un seul joueur) ;
  • Faire perdre de la vie de façon fiable : si c’était géré uniquement côté joueur, un tricheur pourrait modifier son propre script pour ne jamais perdre de vie ;
  • Le serveur peut quand même modifier l’affichage (les TextLabel, TextButton) dans l’interface d’un joueur précis : les changements de propriétés faits par le serveur sont automatiquement envoyés (« répliqués ») à l’écran du joueur.

Pour un jeu plus avancé, on séparerait souvent la logique (serveur) de l’affichage (client) avec des RemoteEvent, mais pour ce tutoriel, un seul Script serveur suffit et reste plus simple à comprendre.

À toi de jouer !

  1. Dans l’Explorer, clique droit sur ServerScriptService → Insert Object → Script.
  2. Renomme-le NumberGameScript.
  3. Ouvre-le et écris juste ceci pour vérifier que tout fonctionne :
-- Ce print doit apparaître dans la console (fenêtre Output) au lancement du jeu
print("Le script du Number Game a bien démarré !")
  1. Clique sur Play (▶) en haut de Roblox Studio.
  2. Ouvre la fenêtre Output (View → Output) : tu dois voir ton message s’afficher.

QCM

1. Où s’exécute un Script (et non un LocalScript) ?

  • a) Sur l’ordinateur du joueur
  • b) Sur le serveur
  • c) Dans le navigateur internet

2. Pourquoi ne pas gérer la perte de vie uniquement avec un LocalScript ?

  • a) Parce que les LocalScript n’existent pas dans Roblox
  • b) Parce qu’un joueur pourrait modifier son propre script pour tricher
  • c) Parce que c’est plus lent

Pour aller plus loin

Documentation Roblox — Script vs LocalScript

Étape 4 : Écrire le code, petit à petit

But : construire la logique du jeu en plusieurs petites briques, du plus simple au plus complexe, au lieu d’écrire tout le script d’un coup.

Concepts clés : variables, fonctions, conditions if/else, boucles for, événements.

On ne va pas écrire tout le script NumberGameScript d’un coup : ce serait trop pour bien comprendre. On va le construire en 4 sous-étapes.

4.1 — Les variables et les constantes

Explications : une variable stocke une information qui peut changer (par exemple la vie perdue à chaque erreur). Une constante est une variable qu’on ne modifiera jamais dans le code, mais qui est facile à régler en un seul endroit (par exemple le nombre de points de vie perdus).

-- ==================== CONSTANTES ====================
local HEALTH_LOSS = 10        -- Points de vie perdus pour une mauvaise réponse
local MIN_DIFFICULTY = 1      -- Difficulté minimale (nombre de chiffres)
local MAX_DIFFICULTY = 3      -- Difficulté maximale (nombre de chiffres)

-- ==================== SERVICES ====================
local Players = game:GetService("Players")

-- ==================== RÉFÉRENCES WORKSPACE ====================
local numberGameFolder = workspace:WaitForChild("NumberGame")
local hitbox = numberGameFolder:WaitForChild("HitBox")

WaitForChild demande à Roblox d’attendre que l’objet existe avant de continuer : c’est important car le jeu peut mettre quelques instants à tout charger.

4.2 — Une fonction pour générer une question d’addition

Explications : une fonction permet de regrouper des instructions sous un nom, pour les réutiliser facilement. Ici, on écrit une version très simplifiée qui ne gère qu’une addition, avant d’ajouter la soustraction, la multiplication et la division plus tard.

local function generateSimpleAddition(maxValue)
    -- On choisit un nombre cible au hasard
    local targetNumber = math.random(2, maxValue)
    -- On choisit un premier morceau, le deuxième complète jusqu'au nombre cible
    local part1 = math.random(1, targetNumber - 1)
    local part2 = targetNumber - part1
    return targetNumber, part1, part2
end

-- Test rapide
local target, a, b = generateSimpleAddition(20)
print(target, "=", a, "+", b)

4.3 — L’événement Touched : détecter le joueur

Explications : un événement, c’est quelque chose qui se produit dans le jeu (un contact, un clic…) auquel on peut « réagir » avec une fonction. Touched se déclenche quand un objet touche la HitBox.

hitbox.Touched:Connect(function(hit)
    local character = hit.Parent
    if not character then return end -- si "hit" n'a pas de parent, on arrête

    local humanoid = character:FindFirstChildOfClass("Humanoid")
    if not humanoid then return end -- ce n'est pas un personnage de joueur

    local player = Players:GetPlayerFromCharacter(character)
    if not player then return end

    print(player.Name .. " a touché la Hitbox !")
end)

Remarque le if ... then return end : c’est une condition qui arrête la fonction tout de suite si quelque chose ne va pas. C’est une façon très courante d’écrire des vérifications de sécurité en Lua.

4.4 — Les 4 boutons de réponse

Explications : on utilise une boucle for avec ipairs pour parcourir chaque bouton de la liste, et on connecte un événement de clic (MouseButton1Click) sur chacun.

local function getPlayerGuiElements(player)
    local playerGui = player:WaitForChild("PlayerGui")
    local screenGui = playerGui:WaitForChild("ScreenGui")
    local numberDisplay = screenGui:WaitForChild("NumberGenerator")
    local answerButtons = screenGui:WaitForChild("Answers")
    return numberDisplay, answerButtons
end

local numberDisplay, answerButtons = getPlayerGuiElements(somePlayer)

for index, part in ipairs(answerButtons:GetDescendants()) do
    if part:IsA("TextButton") then
        part.MouseButton1Click:Connect(function()
            print("Bouton numéro " .. index .. " cliqué")
        end)
    end
end

À toi de jouer !

Recopie chaque sous-étape (4.1 à 4.4) l’une après l’autre dans ton NumberGameScript, en testant à chaque fois avec Play. Ne passe pas à la sous-étape suivante tant que la précédente n’affiche pas ce que tu attends dans l’Output.

QCM

1. Que fait WaitForChild ? a) Il supprime un objet b) Il attend que l’objet existe avant de continuer le script c) Il crée un nouvel objet

2. Que se passe-t-il quand hit.Parent n’a pas de Humanoid ? a) Le jeu plante b) La fonction s’arrête grâce au return, sans erreur c) Le joueur perd de la vie

Pour aller plus loin

Documentation Roblox — événement Touched

Documentation Roblox — boucles for et ipairs

Étape 5 : Débugger et tester avec print

But : apprendre à utiliser print() pour « voir » ce qui se passe à l’intérieur du script, et corriger les erreurs efficacement.

Concepts clés : débogage, console Output, lecture des messages d’erreur.

Explications

Un script ne fait jamais ce qu’on veut qu’il fasse — il fait ce qu’on lui a écrit. Quand le résultat n’est pas celui attendu, on utilise print() pour afficher la valeur des variables à des endroits stratégiques et comprendre où ça coince.

local function generateSimpleAddition(maxValue)
    local targetNumber = math.random(2, maxValue)
    local part1 = math.random(1, targetNumber - 1)
    local part2 = targetNumber - part1

    -- Ligne de débogage : on vérifie que le calcul est correct
    print("DEBUG - cible:", targetNumber, "part1:", part1, "part2:", part2)

    return targetNumber, part1, part2
end

Quand une erreur rouge apparaît dans l’Output, elle indique en général le numéro de ligne et le type d’erreur (par exemple attempt to index nil, ce qui veut souvent dire qu’un objet n’a pas été trouvé avec WaitForChild). Lis toujours ce message avant de chercher au hasard.

À toi de jouer !

  1. Ajoute volontairement une faute dans ton script (par exemple, renomme hitbox en hitboxx à un seul endroit) et lance Play.
  2. Lis le message d’erreur dans l’Output : à quelle ligne pointe-t-il ?
  3. Corrige l’erreur, puis ajoute un print() dans la fonction generateSimpleAddition pour afficher targetNumber, part1 et part2 à chaque appel.

QCM

1. À quoi sert la fenêtre Output ? a) À afficher les décors du jeu b) À afficher les messages print() et les erreurs du script c) À modifier les objets de l’Explorer

2. Quand on voit une erreur attempt to index nil, cela signifie souvent que… a) Le jeu a trop de joueurs b) Un objet attendu (avec WaitForChild) n’a pas été trouvé c) Une variable contient trop de texte

Pour aller plus loin

Une fois le jeu terminé, tu pourras retirer ou commenter (mettre -- devant) les lignes print() de débogage pour « nettoyer » la console, sans les supprimer définitivement — elles peuvent resservir plus tard.

Étape 6 : Donner de l’imprévisibilité au jeu

But : comprendre math.random et pourquoi on doit parfois l’ajuster pour obtenir exactement ce qu’on veut (ici, uniquement des nombres pairs).

Concepts clés : génération de nombres aléatoires, math.random, math.ceil, math.floor.

Explications

math.random(min, max) renvoie un nombre entier au hasard entre min et max inclus. C’est ce qui rend chaque partie différente de la précédente.

Dans notre jeu, on veut que le nombre cible soit toujours pair, pour que les décompositions restent simples à calculer mentalement. Il ne suffit pas d’appeler math.random normalement, car il renverrait aussi des nombres impairs.

local function randomPair(min, max)
    -- On se place dans "l'espace des nombres pairs" en divisant par 2
    min = math.ceil(min / 2)   -- arrondi vers le haut pour ne jamais descendre sous min
    max = math.floor(max / 2)  -- arrondi vers le bas pour ne jamais dépasser max
    return math.random(min, max) * 2
end

print(randomPair(1, 20)) -- toujours un nombre pair entre 2 et 20

math.ceil arrondit toujours vers le haut, math.floor toujours vers le bas. On les utilise ici pour être certain que le résultat final reste bien compris entre min et max, même si ceux-ci sont impairs.

À toi de jouer !

  1. Ajoute la fonction randomPair dans ton script.
  2. Remplace l’appel math.random(2, maxValue) de l’étape 4.2 par randomPair(2, maxValue).
  3. Lance Play plusieurs fois et vérifie dans l’Output que le nombre affiché est toujours pair.
  4. Essaie d’appeler randomPair(1, 9) : quels nombres peux-tu obtenir ?

QCM

1. Que renvoie math.random(1, 6) ?

  • a) Toujours 6
  • b) Un nombre entier au hasard entre 1 et 6
  • c) Un nombre décimal entre 0 et 1

2. Pourquoi utilise-t-on math.ceil pour la borne minimale dans randomPair ?

  • a) Pour ne jamais descendre en dessous de la borne demandée
  • b) Parce que c’est plus rapide à calculer
  • c) Pour obtenir un nombre impair

Pour aller plus loin

Documentation Roblox — math.random

Étape 7 : Organiser son code — constantes et fonctions

But : apprendre à ranger son script pour qu’il reste lisible et facile à modifier, même quand il grandit.

Concepts clés : organisation du code, constantes en majuscules, fonctions séparées par responsabilité.

Explications

Un script bien organisé se lit « de haut en bas » comme une histoire claire :

  1. D’abord les constantes (tout en MAJUSCULES par convention), regroupées en haut, pour pouvoir régler le jeu en un coup d’œil sans chercher dans tout le code.
  2. Ensuite les services Roblox utilisés (Players, etc.).
  3. Puis les fonctions utilitaires, chacune avec un rôle précis et un nom qui décrit ce qu’elle fait (calculateAnswer, randomPair, generateQuestion…).
  4. Enfin les événements qui utilisent toutes ces fonctions.
-- ==================== CONSTANTES ====================
local HEALTH_LOSS = 10
local MIN_DIFFICULTY = 1
local MAX_DIFFICULTY = 3
local OPERATORS = {"+", "-", "*", "/"}

-- ==================== FONCTIONS UTILITAIRES ====================

-- Vérifie si une réponse (part1 OPERATOR part2) donne bien le résultat attendu
local function calculateAnswer(operator, part1, part2, result)
    if operator == "+" then
        return (part1 + part2) == result
    elseif operator == "-" then
        return (part1 - part2) == result
    elseif operator == "*" then
        return (part1 * part2) == result
    elseif operator == "/" then
        return (part1 / part2) == result
    end
end

Séparer le code en petites fonctions, chacune avec un seul but, rend chaque morceau plus facile à tester, à comprendre, et à corriger si une erreur apparaît.

À toi de jouer !

Range ton script actuel dans cet ordre : constantes, services, références workspace, fonctions utilitaires, puis événements. Ajoute des lignes de commentaires -- ==================== comme des titres de section pour t’y retrouver facilement.

QCM

1. Pourquoi écrit-on les constantes en MAJUSCULES ?

  • a) C’est obligatoire en Lua, sinon le script plante
  • b) C’est une convention qui permet de les repérer facilement dans le code
  • c) Ça rend le script plus rapide

2. Quel est l’avantage de séparer le code en plusieurs petites fonctions ?

  • a) Le jeu se lance plus vite
  • b) Chaque fonction est plus facile à comprendre, tester et corriger
  • c) Cela évite d’avoir à utiliser des variables

Pour aller plus loin

Documentation Roblox — bonnes pratiques de style de code Luau

Étape 8 : Optimiser son code — lire tous les objets d’un dossier

But : éviter de répéter le même code pour chaque TextLabel ou TextButton, en parcourant automatiquement tous les objets d’un dossier.

Concepts clés : GetDescendants, boucle for ... in, IsA.

Explications

Plutôt que d’écrire une ligne de code différente pour chaque bouton (Answers.Button1, Answers.Button2…), on demande à Roblox la liste de tous les objets contenus dans un dossier avec GetDescendants(), puis on filtre ceux qui nous intéressent avec IsA("TextButton").

for index, part in ipairs(answerButtons:GetDescendants()) do
    if part:IsA("TextButton") then
        part.Text = "Réponse " .. index
    end
end

L’avantage : si tu ajoutes un 5ème bouton de réponse plus tard dans l’Explorer, ce code fonctionnera sans aucune modification. C’est ce qu’on appelle écrire du code « générique », qui s’adapte automatiquement au contenu du jeu.

À toi de jouer !

  1. Utilise GetDescendants() et IsA("TextLabel") pour mettre à jour le texte de tous les TextLabel du dossier NumberGenerator en une seule boucle.
  2. Ajoute un 2ème TextLabel dans l’Explorer sans toucher au script : vérifie qu’il se met bien à jour lui aussi automatiquement.

QCM

1. Que fait GetDescendants() ?

  • a) Il renvoie la liste de tous les objets contenus dans un objet (et ses sous-dossiers)
  • b) Il supprime tous les objets d’un dossier
  • c) Il crée un nouveau dossier

2. À quoi sert IsA("TextButton") ?

  • a) À vérifier le nom exact de l’objet
  • b) À vérifier le type de l’objet, pour ne garder que les boutons par exemple
  • c) À changer la couleur du bouton

Pour aller plus loin

Documentation Roblox — GetDescendants

Étape 9 : Construire l’architecture du jeu — assembler le script complet

But : relier toutes les briques construites dans les étapes précédentes pour obtenir le script final et fonctionnel.

Concepts clés : assemblage, événements Touched/TouchEnded, gestion multi-joueurs, nettoyage des connexions.

Explications

Un jeu multijoueur doit gérer plusieurs joueurs en même temps, sans qu’ils se marchent dessus. On utilise pour cela des tables qui associent chaque joueur à son propre état :

local playersInZone = {}       -- [Player] = true tant qu'il est dans la zone
local playerConnections = {}   -- [Player] = liste des connexions de boutons à déconnecter

On doit aussi penser à « nettoyer » les connexions d’événements quand un joueur sort de la zone ou quitte le jeu, sinon elles restent actives en mémoire inutilement (une fuite de mémoire) et peuvent déclencher plusieurs fois le même clic.

Voici le script complet, qui assemble toutes les briques précédentes :

--[[
    Script : Number Game - Quiz de calcul (addition)
    Description : Quand un joueur entre dans la Hitbox, un QCM de calcul s'affiche.
    Une bonne réponse augmente la difficulté, une mauvaise réponse fait perdre de la vie.
]]

-- ==================== CONSTANTES ====================
local HEALTH_LOSS = 10
local MIN_DIFFICULTY = 1
local MAX_DIFFICULTY = 3
local ENTER_DELAY = 1
local EXIT_DELAY = 1
local OPERATORS = {"+", "-", "*", "/"}

-- ==================== SERVICES ====================
local Players = game:GetService("Players")

-- ==================== RÉFÉRENCES WORKSPACE ====================
local numberGameFolder = workspace:WaitForChild("NumberGame")
local hitbox = numberGameFolder:WaitForChild("HitBox")

-- ==================== ÉTAT DU JEU ====================
local playersInZone = {}
local playerConnections = {}

-- ==================== FONCTIONS UTILITAIRES ====================

local function calculateAnswer(operator, part1, part2, result)
    if operator == "+" then
        return (part1 + part2) == result
    elseif operator == "-" then
        return (part1 - part2) == result
    elseif operator == "*" then
        return (part1 * part2) == result
    elseif operator == "/" then
        return (part1 / part2) == result
    end
end

local function randomPair(min, max)
    min = math.ceil(min / 2)
    max = math.floor(max / 2)
    return math.random(min, max) * 2
end

local function randomIncorrectNumber(maxValue, targetNumber, operator)
    local wrong1 = randomPair(1, maxValue // 2)
    local wrong2 = randomPair(1, maxValue // 2)
    if operator == "*" or operator == "/" then
        wrong2 = randomPair(1, 10)
    end
    while calculateAnswer(operator, wrong1, wrong2, targetNumber) do
        wrong2 = randomPair(1, maxValue // 2)
    end
    return wrong1, wrong2
end

local function generateQuestion(numberDisplay, answerButtons, difficulty, operator)
    if not numberDisplay or not answerButtons then return nil end
    operator = operator or "+"

    numberDisplay.Visible = true
    answerButtons.Visible = true

    difficulty = math.clamp(difficulty, MIN_DIFFICULTY, MAX_DIFFICULTY)
    local maxValue = (10 ^ difficulty) - 1
    local targetNumber = randomPair(1, maxValue)
    local part1, part2 = 0, 0

    if operator == "+" then
        part1 = math.random(1, targetNumber - 1)
        part2 = targetNumber - part1
    elseif operator == "-" then
        part2 = math.random(1, targetNumber // 2)
        part1 = targetNumber + part2
    elseif operator == "*" then
        part2 = randomPair(1, 10)
        part1 = randomPair(1, maxValue)
        targetNumber = part1 * part2
    elseif operator == "/" then
        part2 = randomPair(1, 10)
        part1 = targetNumber * part2
    end

    for _, part in ipairs(numberDisplay:GetDescendants()) do
        if part:IsA("TextLabel") then
            part.Text = string.format("%0" .. difficulty .. "d", targetNumber)
        end
    end

    local correctAnswerText = string.format("%0" .. difficulty .. "d", part1)
        .. " " .. operator .. " " .. string.format("%0" .. difficulty .. "d", part2)
    local correctIndex = math.random(1, 4)

    for index, part in ipairs(answerButtons:GetDescendants()) do
        if part:IsA("TextButton") then
            if index == correctIndex then
                part.Text = correctAnswerText
            else
                local wrong1, wrong2 = randomIncorrectNumber(maxValue, targetNumber, operator)
                part.Text = string.format("%0" .. difficulty .. "d", wrong1) .. " "
                    .. operator .. " " .. string.format("%0" .. difficulty .. "d", wrong2)
            end
        end
    end

    return correctIndex
end

local function getPlayerGuiElements(player)
    local playerGui = player:WaitForChild("PlayerGui")
    local screenGui = playerGui:WaitForChild("ScreenGui")
    local numberDisplay = screenGui:WaitForChild("NumberGenerator")
    local answerButtons = screenGui:WaitForChild("Answers")
    return numberDisplay, answerButtons
end

local function hideQuiz(numberDisplay, answerButtons)
    if numberDisplay then numberDisplay.Visible = false end
    if answerButtons then answerButtons.Visible = false end
end

local function disconnectPlayerButtons(player)
    local connections = playerConnections[player]
    if not connections then return end
    for _, connection in ipairs(connections) do
        connection:Disconnect()
    end
    playerConnections[player] = nil
end

local function getNextOperator(currentIndex)
    currentIndex += 1
    if currentIndex > #OPERATORS then
        currentIndex = #OPERATORS
    end
    return OPERATORS[currentIndex], currentIndex
end

local function startQuizForPlayer(player, humanoid)
    local numberDisplay, answerButtons = getPlayerGuiElements(player)

    local currentDifficulty = MIN_DIFFICULTY
    local currentOperator, currentIndexOperator = getNextOperator(0)
    local currentAnswerIndex = generateQuestion(numberDisplay, answerButtons, currentDifficulty, currentOperator)

    disconnectPlayerButtons(player)
    local connections = {}

    for index, part in ipairs(answerButtons:GetDescendants()) do
        if part:IsA("TextButton") then
            local connection = part.MouseButton1Click:Connect(function()
                if humanoid.Health <= 0 then return end

                if index ~= currentAnswerIndex then
                    humanoid.Health = math.max(humanoid.Health - HEALTH_LOSS, 0)
                else
                    if currentDifficulty == MAX_DIFFICULTY then
                        currentOperator, currentIndexOperator = getNextOperator(currentIndexOperator)
                        currentDifficulty = MIN_DIFFICULTY
                    end
                    currentAnswerIndex = generateQuestion(numberDisplay, answerButtons, currentDifficulty, currentOperator)
                    currentDifficulty = math.min(currentDifficulty + 1, MAX_DIFFICULTY)
                end
            end)
            table.insert(connections, connection)
        end
    end

    playerConnections[player] = connections
end

-- ==================== ÉVÉNEMENTS DE COLLISION ====================

hitbox.Touched:Connect(function(hit)
    local character = hit.Parent
    if not character then return end

    local humanoid = character:FindFirstChildOfClass("Humanoid")
    if not humanoid then return end

    local player = Players:GetPlayerFromCharacter(character)
    if not player then return end

    if playersInZone[player] then return end
    playersInZone[player] = true

    task.wait(ENTER_DELAY)
    if not playersInZone[player] or humanoid.Health <= 0 then return end

    startQuizForPlayer(player, humanoid)
end)

hitbox.TouchEnded:Connect(function(hit)
    local character = hit.Parent
    if not character then return end

    local humanoid = character:FindFirstChildOfClass("Humanoid")
    if not humanoid then return end

    local player = Players:GetPlayerFromCharacter(character)
    if not player then return end

    task.wait(EXIT_DELAY)

    local stillTouching = false
    for _, part in pairs(hitbox:GetTouchingParts()) do
        if part.Parent == character then
            stillTouching = true
            break
        end
    end

    if stillTouching or not playersInZone[player] then return end

    playersInZone[player] = nil
    disconnectPlayerButtons(player)

    local numberDisplay, answerButtons = getPlayerGuiElements(player)
    hideQuiz(numberDisplay, answerButtons)
end)

-- ==================== GESTION DES JOUEURS ====================

Players.PlayerAdded:Connect(function(player)
    player.CharacterAdded:Connect(function()
        local numberDisplay, answerButtons = getPlayerGuiElements(player)
        hideQuiz(numberDisplay, answerButtons)
    end)
end)

Players.PlayerRemoving:Connect(function(player)
    playersInZone[player] = nil
    disconnectPlayerButtons(player)
end)

À toi de jouer !

  1. Remplace le contenu de ton NumberGameScript par ce script complet.
  2. Teste le jeu en solo avec Play, en te déplaçant vers la Hitbox.
  3. Teste ensuite avec Play plusieurs joueurs (Test → Clients : 2) pour vérifier que deux joueurs peuvent jouer en même temps sans se gêner.

QCM

1. Pourquoi range-t-on l’état de chaque joueur dans des tables comme playersInZone, plutôt que dans une seule variable ?

  • a) Pour que le jeu soit plus joli
  • b) Pour gérer plusieurs joueurs en même temps, chacun avec son propre état
  • c) Ce n’est pas nécessaire, une seule variable suffirait

2. Pourquoi appelle-t-on disconnectPlayerButtons avant de recréer les connexions des boutons ?

  • a) Pour éviter que les anciens clics déclenchent encore une action (fuite mémoire, doublons)
  • b) Pour changer la couleur des boutons
  • c) Ce n’est pas utile, on peut s’en passer

Pour aller plus loin

Documentation Roblox — RBXScript Connection et Disconnect

Étape 10 : Pour aller plus loin — améliore ton jeu

But : utiliser ce que tu as appris pour transformer ce jeu en quelque chose qui te ressemble.

Concepts clés : réutilisation, créativité, itération.

Explications

Tu maîtrises maintenant les briques essentielles de la programmation en Lua : variables, constantes, fonctions, conditions, boucles, événements, et un peu de hasard maîtrisé. Un vrai développeur de jeu ne s’arrête jamais à la première version : il l’améliore petit à petit, en testant à chaque changement.

À toi de jouer !

Voici quelques idées de règles ou d’éléments à ajouter, du plus simple au plus ambitieux :

  • Ajoute un chronomètre : le joueur doit répondre en moins de 10 secondes, sinon il perd de la vie automatiquement.
  • Ajoute un score : chaque bonne réponse rapporte des points, affichés dans un TextLabel en haut de l’écran.
  • Ajoute un message de victoire quand la difficulté maximale et le dernier opérateur (division) sont atteints.
  • Ajoute des obstacles dans le décor autour de la Hitbox pour rendre le déplacement plus difficile.
  • Ajoute un son différent pour une bonne réponse et pour une mauvaise réponse.

Conclusion

Mets-toi maintenant à la place d’un joueur qui découvre ton jeu pour la première fois : est-ce clair ? Est-ce amusant ? Puis imagine un joueur devenu expert après plusieurs parties : le jeu reste-t-il intéressant, ou devient-il trop facile ?

Une fois satisfait de ton jeu, partage-le avec tes camarades de classe et demande-leur ce qu’ils en pensent. Leurs retours sont précieux : ce sont eux qui te diront si une règle est trop dure, trop facile, ou pas claire — exactement comme le font les vrais studios de jeux vidéo avec leurs joueurs.

Pour aller plus loin (documentation)

Documentation Roblox — RemoteEvent (pour séparer proprement client et serveur plus tard) :

Catégories
Jeu vidéo ROBLOX

Tuto 01 : Trouve la bonne couleur !

Tu vas construire, étape par étape, un mini-jeu appelé « Trouve la bonne couleur ».

Voici la règle du jeu : au démarrage, le jeu tire une couleur au hasard parmi rouge, vert et bleu, et l’affiche sur un bloc de référence. Sur un plateau composé de plusieurs blocs, un seul de ces blocs a exactement cette couleur, les autres ont des couleurs différentes. Le joueur doit se déplacer et toucher le bloc qui possède la bonne couleur.

Si le joueur touche le bon bloc, il regagne toute sa vie. Si le joueur touche un mauvais bloc, il perd 10 points de vie. Une fois le bon bloc trouvé, une nouvelle manche démarre automatiquement après quelques secondes, avec une nouvelle couleur à retrouver.

Ce tutoriel est découpé en huit étapes. Chaque étape t’apprend un ou plusieurs concepts de programmation que tu retrouveras dans énormément d’autres jeux et logiciels : les variables, les conditions, les boucles, les fonctions et les événements. À la fin, tu auras écrit et compris l’intégralité du script du jeu, et tu pourras l’améliorer toi-même.

Une variable, en programmation, c’est comme une boîte dans laquelle on range une information (un nombre, un texte, une couleur…) et à laquelle on donne un nom pour la retrouver facilement. C’est une idée que tu vas beaucoup utiliser dans ce tutoriel.

N’aie pas peur de te tromper. Une erreur dans un script n’est pas grave : elle t’indique simplement qu’il faut chercher et corriger, exactement comme un jeu de piste. C’est comme ça que tous les programmeurs apprennent.

Étape 1 : Comprendre le jeu avant de coder

But : Avant d’écrire la moindre ligne de code, tu vas apprendre à analyser un jeu et à le découper en petites règles simples. C’est une étape indispensable pour tout programmeur, avant même d’ouvrir un éditeur de code.

Concepts clés : analyse d’un problème, découpage en étapes logiques.

Explications :

Un programmeur ne commence jamais à écrire du code au hasard. Il commence par se poser des questions simples : que doit faire le jeu, dans quel ordre, et que doit-il se passer dans chaque situation.

Pour notre jeu, on peut résumer les règles ainsi :

  • Le jeu choisit une couleur au hasard.
  • Cette couleur est affichée sur un bloc de référence.
  • Un bloc du plateau reçoit exactement cette couleur, les autres blocs reçoivent d’autres couleurs.
  • Quand le joueur touche un bloc, le script compare sa couleur à la couleur de référence.
  • Si les couleurs sont identiques, le joueur regagne toute sa vie.
  • Si les couleurs sont différentes, le joueur perd 10 points de vie.
  • Une nouvelle manche démarre après un court délai.

Chacune de ces phrases va correspondre, un peu plus tard, à un morceau de code.

À toi de jouer !

Ensuite, essaie d’imaginer un autre mini-jeu avec la même idée (retrouver un élément parmi plusieurs) et écris ses règles de la même façon. Par exemple, un jeu où il faudrait retrouver la bonne forme plutôt que la bonne couleur.

Étape 2 : Modifier les propriétés d’un objet Roblox

But : Apprendre à accéder à un objet du jeu (une Part) et à modifier une de ses propriétés, ici sa couleur.

Concepts clés : variables, propriétés d’un objet, Color3.

Explications :

Dans Roblox, tout ce que tu vois dans le monde du jeu (un bloc, un personnage, une porte) est un objet, appelé instance. Chaque objet possède des propriétés que l’on peut lire ou modifier depuis un script, par exemple sa position, sa taille ou sa couleur.

Pour récupérer un objet du jeu dans le script, on utilise WaitForChild, qui va chercher un objet par son nom et attend qu’il existe avant de continuer. On range le résultat dans une variable, notre « boîte » qui va contenir l’objet.

Crée dans le workspace par Explorer la structure suivante un folder « ColorsGame », un script « ColorsGameScript, une part ReferenceColor :

Pour changer la couleur d’un objet, on utilise sa propriété Color, à laquelle on donne une valeur créée avec Color3.fromRGB(rouge, vert, bleu). Chacun des trois nombres va de 0 à 255 et représente la quantité de rouge, de vert ou de bleu dans la couleur finale.

Code Lua :

-- On récupère le dossier principal du jeu dans le Workspace
local colorsGameFolder = workspace:WaitForChild("ColorsGame")

-- On récupère la partie qui affiche la couleur de référence (celle à retrouver)
local referenceColorPart = colorsGameFolder:WaitForChild("ReferenceColor")

-- On modifie la propriété Color de cet objet pour lui donner une couleur rouge vif
referenceColorPart.Color = Color3.fromRGB(255, 0, 0)

À toi de jouer !

Recopie ce code dans le ColorsGameScript placé dans le Workspace de Roblox Studio, en remplaçant « Part » par le nom exact ReferenceColor d’un bloc que tu as toi-même placé dans ton lieu. Lance le jeu en mode test et observe la couleur du bloc.

Ensuite, modifie les trois nombres de Color3.fromRGB pour obtenir une couleur verte, puis une couleur bleue, puis une couleur de ton choix.

QCM :

  1. Que représente Color3.fromRGB(0, 255, 0) ?
    • a) Une couleur rouge.
    • b) Une couleur verte.
    • c) Une couleur bleue.
  2. Que fait workspace:WaitForChild("MyPart") ?
    • a) Il crée un nouveau bloc nommé « MyPart ».
    • b) Il récupère l’objet nommé « MyPart » et attend qu’il existe avant de continuer.
    • c) Il supprime l’objet « MyPart ».

Étape 3 : Débugger et tester chaque étape

But : Apprendre à utiliser l’instruction print pour afficher des informations dans la console de sortie, afin de vérifier que le script fait bien ce que l’on attend de lui.

Concepts clés : print, console de sortie, débogage.

Explications :

Quand un script ne fonctionne pas comme prévu, il est très utile de pouvoir « regarder à l’intérieur » pour comprendre ce qui se passe réellement. C’est le rôle de l’instruction print : elle affiche du texte ou la valeur d’une variable dans la fenêtre « Output » (sortie) de Roblox Studio.

Cette technique s’appelle le débogage, ou debug en anglais. C’est une des compétences les plus importantes en programmation : un programmeur passe une grande partie de son temps à vérifier pourquoi son code ne fait pas exactement ce qu’il souhaite.

Code Lua :

-- On récupère le dossier principal du jeu dans le Workspace
local colorsGameFolder = workspace:WaitForChild("ColorsGame")

-- On récupère la partie qui affiche la couleur de référence (celle à retrouver)
local referenceColorPart = colorsGameFolder:WaitForChild("ReferenceColor")

-- On modifie la propriété Color de cet objet pour lui donner une couleur rouge vif
referenceColorPart.Color = Color3.fromRGB(0, 255, 0)

-- On affiche à nouveau la couleur pour vérifier qu'elle a bien changé
print("Nouvelle couleur de MyPart :", referenceColorPart.Color)

À toi de jouer !

Ajoute ces lignes print à ton script de l’étape 2, lance le jeu en mode test, puis ouvre la fenêtre « Output » dans Roblox Studio (menu View, puis Output) pour lire les messages affichés.

Essaie d’ajouter un print supplémentaire de ton choix, par exemple pour afficher le nom de l’objet avec myPart.Name.

QCM :

  1. À quoi sert l’instruction print ?
    • a) À afficher un message ou la valeur d’une variable dans la console, pour vérifier le fonctionnement du script.
    • b) À supprimer un objet du jeu.
    • c) À faire gagner le joueur.
  2. Pourquoi le débogage est-il important en programmation ?
    • a) Il ne sert à rien, on peut s’en passer.
    • b) Il permet de comprendre ce qui se passe réellement dans le script et de corriger les erreurs.
    • c) Il remplace le besoin d’écrire du code.

Étape 4 : Donner de l’imprévisibilité au jeu

But : Apprendre à générer un nombre au hasard avec math.random, puis à utiliser une condition if / elseif pour choisir une couleur différente selon ce nombre.

Concepts clés : math.random, conditions if / elseif.

Explications :

Un jeu comme « Trouve la bonne couleur » doit surprendre le joueur à chaque manche : la couleur à retrouver ne doit jamais être toujours la même. Pour cela, on utilise le hasard, avec la fonction math.random(min, max), qui renvoie un nombre entier choisi au hasard entre min et max.

Une fois ce nombre obtenu, on utilise une condition if / elseif pour décider quoi faire selon sa valeur. Une condition permet à un script de prendre des décisions différentes selon la situation, un peu comme lorsque tu choisis un chemin différent selon le panneau indicateur que tu croises.

Code Lua :

-- On récupère le dossier principal du jeu dans le Workspace
local colorsGameFolder = workspace:WaitForChild("ColorsGame")

-- On récupère la partie qui affiche la couleur de référence (celle à retrouver)
local referenceColorPart = colorsGameFolder:WaitForChild("ReferenceColor")

-- On modifie la propriété Color de cet objet pour lui donner une couleur rouge vif
referenceColorPart.Color = Color3.fromRGB(0, 255, 0)

-- On tire un nombre entier au hasard entre 1 et 3
local randomChoice = math.random(1, 3)
print("Nombre tiré :", randomChoice)

local referenceColor = nil

-- Selon le nombre tiré, on choisit une couleur dominante différente
if randomChoice == 1 then
	referenceColor = Color3.fromRGB(math.random(150, 255), 0, 0) -- couleur rouge
elseif randomChoice == 2 then
	referenceColor = Color3.fromRGB(0, math.random(150, 255), 0) -- couleur verte
elseif randomChoice == 3 then
	referenceColor = Color3.fromRGB(0, 0, math.random(150, 255)) -- couleur bleue
end

referenceColorPart.Color = referenceColor

-- On affiche à nouveau la couleur pour vérifier qu'elle a bien changé
print("Nouvelle couleur de MyPart :", referenceColorPart.Color)

Le fait d’utiliser math.random(150, 255) plutôt que math.random(0, 255) garantit une couleur assez claire et vive, jamais trop sombre.

À toi de jouer !

Ajoute ce code à ton script et lance le jeu plusieurs fois de suite en mode test. Observe que la couleur change à chaque lancement.

Modifie ensuite l’intervalle math.random(150, 255) en math.random(0, 100) et observe la différence sur les couleurs obtenues.

QCM :

  1. Que fait math.random(1, 3) ?
    • a) Il renvoie toujours le nombre 3.
    • b) Il renvoie un nombre entier choisi au hasard entre 1 et 3.
    • c) Il renvoie un nombre décimal entre 1 et 3.
  2. À quoi sert une condition if / elseif dans un script ?
    • a) À répéter plusieurs fois la même action.
    • b) À faire prendre au script une décision différente selon une situation.
    • c) À afficher un message dans la console.

Étape 5 : Organiser son code

But : Apprendre à créer une fonction pour regrouper un ensemble d’instructions que l’on pourra réutiliser plusieurs fois.

Concepts clés : fonctions, paramètres, valeur de retour.

Explications :

Une fonction est un bloc de code auquel on donne un nom, et que l’on peut exécuter (« appeler ») autant de fois que nécessaire, sans avoir à recopier le code à chaque fois. Une fonction peut recevoir des informations en entrée, appelées paramètres, et peut renvoyer un résultat avec return.

Dans notre jeu, on va regrouper tout le code qui choisit une nouvelle couleur dans une fonction nommée chooseColor. Elle prendra en paramètre le bloc de référence et le plateau de blocs, et renverra la couleur choisie.

Code Lua :

-- On récupère le dossier principal du jeu dans le Workspace
local colorsGameFolder = workspace:WaitForChild("ColorsGame")

-- On récupère la partie qui affiche la couleur de référence (celle à retrouver)
local referenceColorPart = colorsGameFolder:WaitForChild("ReferenceColor")

-- On modifie la propriété Color de cet objet pour lui donner une couleur rouge vif
referenceColorPart.Color = Color3.fromRGB(0, 255, 0)

local function chooseColor(referenceColorPart)
	-- On tire un nombre entier au hasard entre 1 et 3
	local randomChoice = math.random(1, 3)
	print("Nombre tiré :", randomChoice)

	local referenceColor = nil

	-- Selon le nombre tiré, on choisit une couleur dominante différente
	if randomChoice == 1 then
		referenceColor = Color3.fromRGB(math.random(150, 255), 0, 0) -- couleur rouge
	elseif randomChoice == 2 then
		referenceColor = Color3.fromRGB(0, math.random(150, 255), 0) -- couleur verte
	elseif randomChoice == 3 then
		referenceColor = Color3.fromRGB(0, 0, math.random(150, 255)) -- couleur bleue
	end

	referenceColorPart.Color = referenceColor
	
	return referenceColor
end

local referenceColor = chooseColor(referenceColorPart)

-- On affiche à nouveau la couleur pour vérifier qu'elle a bien changé
print("Nouvelle couleur de MyPart :", referenceColorPart.Color)

À toi de jouer !

Ajoute ce code à ton script. Appelle la fonction chooseColor deux fois de suite dans deux variables différentes, et affiche les deux résultats avec print pour vérifier qu’ils sont bien différents.

QCM :

  1. Pourquoi utilise-t-on une fonction plutôt que de recopier plusieurs fois le même code ?
    • a) Parce que Lua interdit de recopier du code.
    • b) Pour éviter les répétitions et pouvoir réutiliser facilement le même bloc de code.
    • c) Parce que cela rend le jeu plus rapide.
  2. Que fait l’instruction return dans une fonction ?
    • a) Elle arrête complètement le jeu.
    • b) Elle renvoie un résultat à l’endroit où la fonction a été appelée.
    • c) Elle affiche un message dans la console.

Étape 6 : Optimiser son code

But : Apprendre à parcourir automatiquement tous les objets contenus dans un dossier grâce à une boucle, plutôt que de traiter chaque bloc un par un.

Concepts clés : boucles for, GetChildren, IsA.

Explications :

Notre plateau de jeu, ColorBoard, ne contient pas un seul bloc mais plusieurs. Écrire manuellement le code pour chacun des blocs serait très long, et il faudrait tout réécrire si on ajoute un bloc supplémentaire. À la place, on utilise une boucle for, qui répète automatiquement une même action pour chaque objet d’une liste.

La méthode GetChildren() renvoie la liste de tous les objets contenus dans un dossier. On la combine avec ipairs(...) pour parcourir cette liste un par un, en récupérant à chaque tour de boucle un index (1, 2, 3…) et l’objet correspondant.

Comme un dossier peut contenir des objets qui ne sont pas des blocs (un script, par exemple), on vérifie le type de chaque objet avec part:IsA("BasePart") avant de le modifier.

Enfin, pour tirer au sort l’index du bloc gagnant parmi tous les blocs du plateau, on utilise #colorBoard:GetChildren(), qui compte le nombre maxi d’objets présents dans la liste.

Code Lua :

-- On récupère le dossier principal du jeu dans le Workspace
local colorsGameFolder = workspace:WaitForChild("ColorsGame")

-- On récupère la partie qui affiche la couleur de référence (celle à retrouver)
local referenceColorPart = colorsGameFolder:WaitForChild("ReferenceColor")

-- On modifie la propriété Color de cet objet pour lui donner une couleur rouge vif
referenceColorPart.Color = Color3.fromRGB(0, 255, 0)

local function chooseColor(referenceColorPart)
	-- On tire un nombre entier au hasard entre 1 et 3
	local randomChoice = math.random(1, 3)
	print("Nombre tiré :", randomChoice)

	local referenceColor = nil

	-- Selon le nombre tiré, on choisit une couleur dominante différente
	if randomChoice == 1 then
		referenceColor = Color3.fromRGB(math.random(150, 255), 0, 0) -- couleur rouge
	elseif randomChoice == 2 then
		referenceColor = Color3.fromRGB(0, math.random(150, 255), 0) -- couleur verte
	elseif randomChoice == 3 then
		referenceColor = Color3.fromRGB(0, 0, math.random(150, 255)) -- couleur bleue
	end

	referenceColorPart.Color = referenceColor
	
	return referenceColor
end

local referenceColor = chooseColor(referenceColorPart)

-- On affiche à nouveau la couleur pour vérifier qu'elle a bien changé
print("Nouvelle couleur de MyPart :", referenceColorPart.Color)

local colorBoard = colorsGameFolder:WaitForChild("ColorBoard")

-- On compte le nombre de blocs, puis on tire au sort l'index du bloc gagnant
local correctPartIndex = math.random(1, #colorBoard:GetChildren())

-- On parcourt tous les objets contenus dans ColorBoard
for index, part in ipairs(colorBoard:GetChildren()) do

	-- On ne traite que les objets qui sont bien des blocs
	if part:IsA("BasePart") then
		print(index, correctPartIndex)

		if index == correctPartIndex then
			-- C'est le bloc tiré au sort : il reçoit la couleur de référence
			part.Color = referenceColor
		else
			-- Les autres blocs reçoivent une couleur aléatoire différente
			part.Color = Color3.fromRGB(math.random(0, 255), math.random(0, 255), math.random(0, 255))
		end
	end
end

À toi de jouer !

Place plusieurs blocs dans un dossier nommé « ColorBoard » dans le Workspace, puis ajoute ce code à ton script. Lance le jeu en mode test et observe qu’un seul bloc a exactement la couleur de référence.

Ajoute ou supprime un bloc dans le dossier « ColorBoard », relance le jeu, et vérifie que le code fonctionne toujours sans rien modifier.

QCM :

  1. Que renvoie colorBoard:GetChildren() ?
    • a) Le nom du dossier ColorBoard.
    • b) La liste de tous les objets contenus dans le dossier ColorBoard.
    • c) La couleur du dossier ColorBoard.
  2. Pourquoi utilise-t-on part:IsA("BasePart") dans la boucle ?
    • a) Pour vérifier que l’objet est bien un bloc avant de modifier sa couleur.
    • b) Pour compter le nombre d’objets.
    • c) Pour supprimer les objets qui ne sont pas des blocs.

Étape 7 : Construire l’architecture du jeu

But : Apprendre à créer une constante pour les valeurs qui ne changent jamais et assembler l’ensemble des concepts vus précédemment (variables, fonctions, boucles, conditions) avec un nouvel élément essentiel : les événements, pour obtenir le script complet et jouable du jeu.

Concepts clés : constantes, événements, Touched, Humanoid, Health, task.spawn, task.wait.

Explications :

Une constante est une variable dont la valeur ne change jamais pendant toute la partie, par exemple le nombre de points de vie perdus en cas d’erreur. On lui donne généralement un nom entièrement en majuscules, pour bien la distinguer des autres variables, par exemple LOSS_HEALTH.

Un événement est une notification envoyée par Roblox lorsqu’une action précise se produit dans le jeu, par exemple un contact entre deux objets. Chaque Part possède un événement Touched, auquel on peut « s’abonner » avec :Connect(function(hit) ... end) pour exécuter du code à chaque fois qu’un contact a lieu.

Dans la fonction connectée à Touched, hit désigne la partie qui a touché le bloc, généralement une main ou un pied. Son Parent est alors le personnage entier du joueur. Ce personnage possède un objet Humanoid, que l’on récupère avec FindFirstChildOfClass("Humanoid"), et qui gère notamment sa vie via la propriété Health.

En assemblant tout, on obtient l’architecture complète du script : on récupère les objets du jeu, on définit la fonction chooseColor, on l’appelle une première fois pour démarrer la partie, puis on parcourt tous les blocs du plateau pour préparer leurs propriétés et connecter l’événement Touched sur chacun d’eux.

Enfin, task.spawn permet d’exécuter du code « en parallèle » du reste du script, et task.wait(secondes) permet de faire patienter ce code sans bloquer le reste du jeu, ce qui est utilisé pour relancer une nouvelle manche quelques secondes après une bonne réponse.

Code Lua :

-- =========================================================
-- JEU : Trouve la bonne couleur !
-- Le jeu choisit une couleur au hasard (rouge, vert ou bleu).
-- Un des blocs de la ColorBoard aura exactement cette couleur.
-- Si le joueur touche le bon bloc, il regagne toute sa vie.
-- Si le joueur touche un mauvais bloc, il perd 10 points de vie.
-- =========================================================

local LOSS_HEALTH = 10 -- perte de santé pour un mauvais choix
local GAME_TIMEOUT = 5 -- en secondes, pour un time out avent de rejouer la partie = 5

-- On récupère le dossier principal du jeu dans le Workspace
local colorsGameFolder = workspace:WaitForChild("ColorsGame")

-- On récupère la partie qui affiche la couleur de référence (celle à retrouver)
local referenceColorPart = colorsGameFolder:WaitForChild("ReferenceColor")

-- On récupère le plateau qui contient tous les blocs de couleur
local colorBoard = colorsGameFolder:WaitForChild("ColorBoard")


local function chooseColor(referenceColorPart, colorBoard)
	-- Cette variable va contenir la couleur de référence à retrouver
	local referenceColor = nil
	-- On tire un nombre au hasard entre 1 et 3 pour choisir quelle couleur dominante on va utiliser
	local randomChoice = math.random(1, 3)
	-- Selon le nombre tiré, on crée une couleur avec un fort rouge, vert ou bleu
	-- math.random(150, 255) donne une couleur assez claire/vive, pas trop sombre
	if randomChoice == 1 then
		referenceColor = Color3.fromRGB(math.random(150, 255), 0, 0) -- couleur rouge
	elseif randomChoice == 2 then
		referenceColor = Color3.fromRGB(0, math.random(150, 255), 0) -- couleur verte
	elseif randomChoice == 3 then
		referenceColor = Color3.fromRGB(0, 0, math.random(150, 255)) -- couleur bleue
	end

	-- On applique cette couleur à la partie "ReferenceColor" pour que le joueur puisse la voir
	referenceColorPart.Color = referenceColor

	-- On choisit au hasard l'INDEX du bloc qui aura la bonne couleur (le bloc "gagnant")
	local correctPartIndex = math.random(1, #colorBoard:GetChildren())

	-- On parcourt tous les blocs présents dans le plateau de couleurs
	for index, part in ipairs(colorBoard:GetChildren()) do

		-- On vérifie que l'objet est bien un "BasePart" (un bloc, pas un script ou autre chose)
		if part:IsA("BasePart") then
			print(index, correctPartIndex)
			-- Si c'est le bloc tiré au sort, on lui donne la couleur de référence (le bon bloc)
			if index == correctPartIndex then
				part.Color = referenceColor
			else
				-- Sinon, on lui donne une couleur totalement aléatoire (un bloc piège)
				part.Color = Color3.fromRGB(math.random(0, 255), math.random(0, 255), math.random(0, 255))
			end
		end
	end
	
	return referenceColor	
end

local referenceColor = chooseColor(referenceColorPart, colorBoard)

-- On parcourt tous les blocs présents dans le plateau de couleurs
for index, part in pairs(colorBoard:GetChildren()) do

	-- On vérifie que l'objet est bien un "BasePart" (un bloc, pas un script ou autre chose)
	if part:IsA("BasePart") then
		part.Material = Enum.Material.Plastic
		part.Anchored = true
		part.CanCollide = true

		-- On surveille si un joueur touche ce bloc
		part.Touched:Connect(function(hit)

			-- "hit" est la partie du joueur qui a touché le bloc (souvent une main ou un pied)
			-- Son "Parent" est le personnage (le modèle) du joueur
			local character = hit.Parent
			if not character then return end -- si on ne trouve pas de personnage, on arrête ici

			-- On cherche l'Humanoid du personnage, c'est lui qui gère la vie (Health)
			local humanoid = character:FindFirstChildOfClass("Humanoid")
			if not humanoid then return end -- si pas d'Humanoid, on arrête ici (ce n'est pas un joueur)
			
			if part.Color == Color3.fromRGB(255, 255, 255) then return end

			-- On compare la couleur du bloc touché avec la couleur de référence
			if part.Color == referenceColor then
				-- Bonne couleur : le joueur récupère toute sa vie
				humanoid.Health = 100
				part.Color = Color3.fromRGB(255, 255, 255)
				part.Material = Enum.Material.Neon
				task.spawn(function()
					task.wait(GAME_TIMEOUT)
					referenceColor = chooseColor(referenceColorPart, colorBoard)
					part.Material = Enum.Material.Plastic
				end)
			else
				-- Mauvaise couleur : le joueur perd 10 points de vie
				humanoid.Health -= LOSS_HEALTH
			end
		end)
	end
end


À toi de jouer !

Recrée dans le Workspace un dossier nommé « ColorsGame » contenant une Part nommée « ReferenceColor » et un dossier nommé « ColorBoard » rempli de plusieurs blocs. Copie ce script complet, lance le jeu en mode test, et essaie de retrouver le bon bloc avec ton personnage.

Modifie la valeur de LOSS_HEALTH pour la mettre à 25, relance le jeu, et observe en combien de mauvais choix ton personnage perd toute sa vie.

QCM :

  1. À quoi sert l’événement Touched ?
    • a) À déplacer un bloc.
    • b) À exécuter du code lorsqu’un contact se produit avec la Part.
    • c) À changer la couleur du plateau automatiquement.
  2. Pourquoi utilise-t-on task.spawn avant task.wait(GAME_TIMEOUT) ?
    • a) Pour arrêter complètement le jeu pendant l’attente.
    • b) Pour exécuter cette attente en parallèle, sans bloquer le reste du script.
    • c) Pour supprimer le bloc touché.

Étape 8 : Améliorations pour aller plus loin

But : Utiliser tous les concepts appris pour faire évoluer le jeu par toi-même, en ajoutant de nouvelles règles.

Concepts clés : réinvestissement des concepts précédents, créativité.

Explications :

Maintenant que tu comprends l’ensemble du script, tu es capable de le modifier pour créer tes propres règles. Voici quelques pistes, de la plus simple à la plus avancée :

Ajouter un score : crée une variable playerScore initialisée à 0, augmente-la de 1 à chaque bonne réponse, et affiche-la avec print ou dans une interface.

Ajouter un chronomètre : utilise RunService.Heartbeat pour calculer le temps du jeu afin de contrôler le temps de chaque manche.

local RunService = game:GetService("RunService")
local temps = 0 -- Temps écoulé en secondes
RunService.Heartbeat:Connect(function(dt)
    temps += dt

    local tempsEntier = math.floor(temps)
    local minutes = math.floor(tempsEntier / 60)
    local secondes = tempsEntier % 60

    print(string.format("%02d:%02d", minutes, secondes))
end)

Ajouter une condition de victoire : écris une fonction checkWinCondition() qui vérifie si playerScore a atteint une valeur suffisante, et qui affiche un message de victoire si c’est le cas.

Ajouter des obstacles : place des blocs supplémentaires qui ne font pas partie du jeu de couleurs, mais qui font perdre de la vie au joueur s’il les touche, en réutilisant l’événement Touched.

À toi de jouer !

Tu peux t’inspirer de ce tuto : https://123codage.com/points-de-respawns-comptage-des-tours-meilleur-temps/

Choisis au moins une de ces améliorations et essaie de l’ajouter à ton script. N’hésite pas à utiliser print pour vérifier que tes nouvelles variables évoluent bien comme tu le souhaites.

QCM :

  1. Que faudrait-il faire pour ajouter un système de score au jeu ?
    • a) Créer une variable comme playerScore et l’augmenter à chaque bonne réponse.
    • b) Modifier uniquement la couleur des blocs.
    • c) Supprimer la fonction chooseColor.
  2. Pourquoi est-il utile de réutiliser des concepts déjà appris (variables, conditions, boucles, événements) pour ajouter de nouvelles règles ?
    • a) Parce que ce sont les seuls outils disponibles en Lua.
    • b) Parce que ces mêmes concepts permettent de construire des règles de jeu très différentes.
    • c) Parce que Roblox Studio l’exige.
-- =========================================================
-- JEU : Trouve la bonne couleur !
-- Le jeu choisit une couleur au hasard (rouge, vert ou bleu).
-- Un des blocs de la ColorBoard aura exactement cette couleur.
-- Si le joueur touche le bon bloc, il regagne toute sa vie.
-- Si le joueur touche un mauvais bloc, il perd 10 points de vie.
-- =========================================================

local LOSS_HEALTH = 10 -- perte de santé pour un mauvais choix
local GAME_TIMEOUT = 5 -- en secondes, pour un time out avent de rejouer la partie = 5

-- On récupère le dossier principal du jeu dans le Workspace
local colorsGameFolder = workspace:WaitForChild("ColorsGame")

-- On récupère la partie qui affiche la couleur de référence (celle à retrouver)
local referenceColorPart = colorsGameFolder:WaitForChild("ReferenceColor")

-- On récupère le plateau qui contient tous les blocs de couleur
local colorBoard = colorsGameFolder:WaitForChild("ColorBoard")


local winSound = colorsGameFolder.winner
local alarmSound = colorsGameFolder.alarm

-- Créer une fonction pour charger et tester un son
local function playSound(sound)
	if not sound then return end
	pcall(function() sound:Play() end)
end

local function chooseColor(referenceColorPart, colorBoard)
	-- Cette variable va contenir la couleur de référence à retrouver
	local referenceColor = nil
	-- On tire un nombre au hasard entre 1 et 3 pour choisir quelle couleur dominante on va utiliser
	local randomChoice = math.random(1, 3)
	-- Selon le nombre tiré, on crée une couleur avec un fort rouge, vert ou bleu
	-- math.random(150, 255) donne une couleur assez claire/vive, pas trop sombre
	if randomChoice == 1 then
		referenceColor = Color3.fromRGB(math.random(150, 255), 0, 0) -- couleur rouge
	elseif randomChoice == 2 then
		referenceColor = Color3.fromRGB(0, math.random(150, 255), 0) -- couleur verte
	elseif randomChoice == 3 then
		referenceColor = Color3.fromRGB(0, 0, math.random(150, 255)) -- couleur bleue
	end

	-- On applique cette couleur à la partie "ReferenceColor" pour que le joueur puisse la voir
	referenceColorPart.Color = referenceColor

	-- On choisit au hasard l'INDEX du bloc qui aura la bonne couleur (le bloc "gagnant")
	local correctPartIndex = math.random(1, #colorBoard:GetChildren())

	-- On parcourt tous les blocs présents dans le plateau de couleurs
	for index, part in ipairs(colorBoard:GetChildren()) do

		-- On vérifie que l'objet est bien un "BasePart" (un bloc, pas un script ou autre chose)
		if part:IsA("BasePart") then
			print(index, correctPartIndex)
			-- Si c'est le bloc tiré au sort, on lui donne la couleur de référence (le bon bloc)
			if index == correctPartIndex then
				part.Color = referenceColor
			else
				-- Sinon, on lui donne une couleur totalement aléatoire (un bloc piège)
				part.Color = Color3.fromRGB(math.random(0, 255), math.random(0, 255), math.random(0, 255))
			end
		end
	end
	
	return referenceColor	
end

local referenceColor = chooseColor(referenceColorPart, colorBoard)

-- On parcourt tous les blocs présents dans le plateau de couleurs
for index, part in pairs(colorBoard:GetChildren()) do

	-- On vérifie que l'objet est bien un "BasePart" (un bloc, pas un script ou autre chose)
	if part:IsA("BasePart") then
		part.Material = Enum.Material.Plastic
		part.Anchored = true
		part.CanCollide = true

		-- On surveille si un joueur touche ce bloc
		part.Touched:Connect(function(hit)

			-- "hit" est la partie du joueur qui a touché le bloc (souvent une main ou un pied)
			-- Son "Parent" est le personnage (le modèle) du joueur
			local character = hit.Parent
			if not character then return end -- si on ne trouve pas de personnage, on arrête ici

			-- On cherche l'Humanoid du personnage, c'est lui qui gère la vie (Health)
			local humanoid = character:FindFirstChildOfClass("Humanoid")
			if not humanoid then return end -- si pas d'Humanoid, on arrête ici (ce n'est pas un joueur)
			
			if part.Color == Color3.fromRGB(255, 255, 255) then return end

			-- On compare la couleur du bloc touché avec la couleur de référence
			if part.Color == referenceColor then
				-- Bonne couleur : le joueur récupère toute sa vie
				humanoid.Health = 100
				part.Color = Color3.fromRGB(255, 255, 255)
				part.Material = Enum.Material.Neon
				task.spawn(function()
					playSound(winSound)
					task.wait(GAME_TIMEOUT)
					referenceColor = chooseColor(referenceColorPart, colorBoard)
					part.Material = Enum.Material.Plastic
				end)
			else
				-- Mauvaise couleur : le joueur perd 10 points de vie
				humanoid.Health -= LOSS_HEALTH
				task.spawn(function()
					playSound(alarmSound)
				end)

			end
		end)
	end
end

Conclusion

Mets-toi maintenant à la place d’un joueur qui découvre ton jeu pour la première fois : est-il facile à comprendre ? La règle est-elle claire dès les premières secondes ? Puis imagine ce même joueur devenir peu à peu un expert, capable de battre le jeu très rapidement : le jeu reste-t-il intéressant, ou devient-il trop simple ?

Ces deux points de vue, celui du débutant et celui de l’expert, sont essentiels pour améliorer un jeu vidéo : un bon jeu doit rester accessible au début, tout en offrant un défi qui évolue avec le joueur.

Tu peux maintenant partager ton jeu avec tes camarades et observer comment ils y jouent, ou continuer à le faire évoluer, par exemple en ajoutant un système de vies limitées, plusieurs niveaux de difficulté, ou un mode à plusieurs joueurs. Chaque nouvelle règle que tu ajouteras réutilisera les mêmes bases que tu viens d’apprendre : des variables pour stocker des informations, des conditions pour prendre des décisions, des boucles pour répéter des actions, des fonctions pour organiser le code, et des événements pour réagir à ce que fait le joueur.

Catégories
Jeu vidéo ROBLOX

Comment pousser un bloc

Pousser un bloc sans code complexe

Ce script rend un bloc poussable par le joueur en utilisant directement le moteur physique de Roblox, sans avoir besoin de code complexe pour détecter les contacts. part.Anchored = false est la ligne essentielle : elle « détache » la Part du monde et lui permet de bouger sous l’effet de forces physiques (comme la poussée du joueur). part.CanCollide = true garantit que le joueur ne traverse pas le bloc mais rentre réellement dedans, ce qui déclenche la poussée physique naturelle.

PhysicalProperties.new(...) personnalise le comportement physique du bloc : ici, une densité basse (0.2) rend l’objet léger et facile à pousser, une friction basse (0.2) le fait glisser facilement, et une élasticité (0.5) lui donne un léger effet de rebond.

part.CustomPhysicalProperties = physicalProperties applique ces réglages personnalisés à la Part, plutôt que d’utiliser les valeurs par défaut de Roblox (qui donneraient un objet plus lourd et moins glissant).

local part = script.Parent

local physicalProperties = PhysicalProperties.new(
	0.2,  -- Density (densité, plus c'est bas plus c'est léger à pousser)
	0.2,  -- Friction
	0.5,  -- Elasticity
	1,    -- FrictionWeight
	1     -- ElasticityWeight
)

part.Anchored = false
part.CanCollide = true
part.CustomPhysicalProperties = physicalProperties

Pousser un bloc avec une impulsion

Ce script combine deux mécanismes pour rendre un bloc poussable :

  1. Configuration physique de base : le bloc devient mobile (Anchored = false) et reçoit des propriétés physiques personnalisées (densité, friction, élasticité) qui définissent son comportement naturel (léger/lourd, glissant/collant, rebondissant ou non).
  2. Poussée scriptée additionnelle : en plus de la physique naturelle, le script détecte quand un joueur touche le bloc et lui applique une impulsion (un « coup de pousse ») dans la direction où le joueur regarde/avance, grâce à ApplyImpulse.

Détail du fonctionnement

  • humanoidRootPart.CFrame.LookVector récupère un vecteur représentant la direction vers laquelle le joueur est orienté — c’est cette direction qui détermine le bloc va être poussé.
  • part:ApplyImpulse(direction * PUSH_FORCE * part:GetMass()) calcule la force de poussée en tenant compte de la masse du bloc (GetMass()) : plus le bloc est lourd, plus il faut une impulsion importante pour le déplacer de la même façon (comme dans la vraie vie).
  • part.Touched:Connect(onTouched) signifie que cette poussée se déclenche à chaque fois que quelque chose touche le bloc — donc potentiellement plusieurs fois par seconde si le joueur reste collé dessus.

Pourquoi combiner les deux mécanismes ?

La physique seule (Anchored = false + CanCollide = true) suffit déjà à rendre le bloc poussable naturellement. Mais ajouter une impulsion scriptée permet de renforcer ou contrôler cet effet — par exemple pour que la poussée soit plus franche et immédiate, même si le joueur touche le bloc à faible vitesse.

QCM pour mieux comprendre

1. Que se passe-t-il si on met part.Anchored = true dans ce script ?
A. Le bloc devient plus facile à pousser
B. Le bloc ne pourra plus bouger du tout, malgré l’impulsion appliquée
C. Le bloc disparaît
D. Rien ne change

2. Que représente humanoidRootPart.CFrame.LookVector ?
A. La vitesse du joueur
B. La direction vers laquelle le joueur regarde/avance
C. La position exacte du joueur
D. Le nom du joueur

3. Pourquoi multiplie-t-on la force par part:GetMass() ?
A. Pour que la poussée soit toujours la même, peu importe le poids du bloc
B. Pour adapter la force de poussée au poids réel du bloc (plus lourd = plus de force nécessaire)
C. Pour calculer la vitesse du joueur
D. Pour rendre le bloc invisible

4. Que fait part:ApplyImpulse(...) ?
A. Il déplace instantanément le bloc à une position précise
B. Il applique une force ponctuelle qui pousse le bloc dans une direction donnée
C. Il détruit le bloc
D. Il change la couleur du bloc

5. Quand la fonction onTouched se déclenche-t-elle ?
A. Une seule fois au démarrage du jeu
B. À chaque fois qu’un objet (joueur ou autre) touche le bloc
C. Uniquement quand le joueur saute sur le bloc
D. Toutes les 5 secondes automatiquement

6. Si on augmente la valeur de PUSH_FORCE, que se passe-t-il ?
A. Le bloc devient plus lourd visuellement
B. Le bloc sera poussé plus fort/plus loin à chaque contact
C. Le joueur devient plus rapide
D. Rien, cette variable n’est pas utilisée dans le script

7. Pourquoi le script vérifie-t-il if not humanoid or not humanoidRootPart then return end ?
A. Pour vérifier que c’est bien un joueur (ou personnage) qui touche le bloc, et pas un autre objet
B. Pour supprimer le joueur s’il n’a pas de vie
C. Pour ralentir le jeu volontairement
D. Ce n’est pas nécessaire, cette ligne ne sert à rien

local part = script.Parent

local physicalProperties = PhysicalProperties.new(
	0.5,  -- Density (densité, plus c'est bas plus c'est léger à pousser)
	0.5,  -- Friction
	0.5,  -- Elasticity
	1,    -- FrictionWeight
	1     -- ElasticityWeight
)

part.Anchored = false
part.CanCollide = true
part.CustomPhysicalProperties = physicalProperties

local PUSH_FORCE = 2 -- Intensité de la poussée

local function onTouched(hit)
	local character = hit.Parent
	local humanoid = character and character:FindFirstChild("Humanoid")
	local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart")
	if not humanoid or not humanoidRootPart then return end

	-- Calcule la direction dans laquelle le joueur se déplace
	local direction = humanoidRootPart.CFrame.LookVector

	-- Applique une impulsion dans cette direction
	part:ApplyImpulse(direction * PUSH_FORCE * part:GetMass())
end

part.Touched:Connect(onTouched)

Pousser plusieurs BLOCs

local partFolder = script.Parent

local physicalProperties = PhysicalProperties.new(
	0.5,  -- Density (densité, plus c'est bas plus c'est léger à pousser)
	0.5,  -- Friction
	0.5,  -- Elasticity
	1,    -- FrictionWeight
	1     -- ElasticityWeight
)


local PUSH_FORCE = 20 -- Intensité de la poussée

local function onTouched(hit, part)
	local character = hit.Parent
	local humanoid = character and character:FindFirstChild("Humanoid")
	local humanoidRootPart = character and character:FindFirstChild("HumanoidRootPart")
	if not humanoid or not humanoidRootPart then return end

	-- Calcule la direction dans laquelle le joueur se déplace
	local direction = humanoidRootPart.CFrame.LookVector

	-- Applique une impulsion dans cette direction
	part:ApplyImpulse(direction * PUSH_FORCE * part:GetMass())
end

for _, part in partFolder:GetChildren() do
	if part:IsA("BasePart") then
		
		part.Anchored = false
		part.CanCollide = true
		part.CustomPhysicalProperties = physicalProperties		
		part.Touched:Connect(onTouched, part)
	end
end
Catégories
Jeu vidéo ROBLOX

Points de respawns, comptage des tours, meilleur temps

À quoi sert ce script ?

Ce script permet de créer un parcours de course :

  • Des checkpoints invisibles qui font respawn le joueur au bon endroit s’il tombe ou meurt
  • Un compteur de tours : combien de fois le joueur a terminé le parcours
  • Un meilleur temps (record) qui s’affiche et se met à jour automatiquement

Comment l’utiliser dans votre jeu

Étape 1 : Créer les parts de checkpoint

  1. Créez un dossier (Folder) nommé PartSpawnNiv dans le Workspace.
  2. Créez un dossier GAME dans le Workspace qui contiendra ce dossier PartSpawnNiv.
  3. Dans PartSpawnNiv, créez une Part pour chaque checkpoint de votre parcours (une au départ, une à chaque étape importante, une à l’arrivée).
  4. Nommez-les avec des numéros dans l’ordre : "1", "2", "3", etc. ; c’est très important, le script trie les checkpoints selon ces numéros !
Workspace
 └── GAME
      └── PartSpawnNiv
           ├── 1  (départ)
           ├── 2
           ├── 3
           └── 4  (arrivée)

Le script rend ces parts automatiquement invisibles et traversables : pas besoin de le faire vous-même !

Étape 2 : Créer l’interface (GUI)

Dans StarterGui, créez un ScreenGui contenant :

  • Un Frame nommé Counter avec un TextLabel nommé CounterLabel (nombre de tours)
  • Un Frame nommé Best avec un TextLabel nommé BestLabel (meilleur temps)

Étape 3 : Placer le script

Placez ce script en tant que Script (serveur, pas LocalScript) directement dans ServerScriptService.

Comment fonctionne le script (en 6 points)

  1. Au démarrage, le script range tous les checkpoints du dossier PartSpawnNiv dans l’ordre de leurs numéros, puis les rend invisibles et traversables.
  2. Quand un joueur touche un checkpoint, le script vérifie qui il est, puis retient dans un tableau playersProgression : la part actuelle, le nombre de tours, l’heure de départ, et le meilleur temps.
  3. Si le joueur touche le checkpoint n°1 juste après avoir touché le DERNIER checkpoint, le script comprend qu’il a terminé un tour complet : il incrémente le compteur et compare le temps réalisé au record.
  4. Si le joueur meurt, l’événement CharacterAdded se déclenche : le script téléporte automatiquement le joueur au-dessus du dernier checkpoint touché (et pas au tout début du parcours !).
  5. Le tableau playersProgression retient les informations de chaque joueur séparément, grâce à son UserId (un peu comme une fiche personnelle par joueur).
  6. Quand un joueur quitte le jeu, ses informations sont automatiquement supprimées du tableau pour ne pas garder des données inutiles en mémoire.

Pourquoi utiliser un tableau, et que contient playersProgression ?

Pourquoi a-t-on besoin d’un tableau ici ?

Imagine que plusieurs joueurs jouent en même temps sur ton parcours. Le script doit se souvenir, pour chaque joueur individuellement :

  • à quel checkpoint il en est
  • depuis quand il a commencé son tour actuel
  • combien de tours il a déjà faits
  • son meilleur temps

Si le script utilisait une seule variable simple (par exemple local spawnActuel = ...), cette variable serait partagée par tous les joueurs — dès qu’un 2ème joueur touche un checkpoint, ça écraserait la progression du 1er joueur ! Ce serait comme si tout le monde partageait le même carnet de notes : la dernière personne qui écrit efface ce que l’autre avait noté.

Un tableau permet de créer une « fiche » séparée pour chaque joueur, un peu comme un casier avec un tiroir par élève dans une salle de classe.

Qu’est-ce qu’un tableau d’informations (une table Lua) ?

En Lua, une table peut fonctionner comme un dictionnaire : au lieu d’accéder à une information par un numéro de position (comme dans une liste), on y accède par une clé (un identifiant unique), et chaque clé pointe vers une valeur.

playersProgression = {
	[12345] = { ... },  -- clé = UserId du joueur 1
	[67890] = { ... },  -- clé = UserId du joueur 2
}

C’est comme un répertoire téléphonique : la clé, c’est le nom de la personne ; la valeur, c’est son numéro. Ici, la clé est le UserId (identifiant unique de chaque joueur sur Roblox), et la valeur est une autre table qui contient toutes les infos de progression de ce joueur précis.

Pourquoi utiliser le UserId comme clé ?

Parce que chaque joueur a un UserId unique et permanent sur Roblox — contrairement à son pseudo qui pourrait théoriquement changer, ou à sa position dans une liste qui peut varier si un joueur quitte. Utiliser le UserId garantit qu’on retrouve toujours la bonne fiche pour le bon joueur, même avec plusieurs joueurs connectés simultanément.

Le contenu détaillé de playersProgression[UserId]

Pour un joueur donné, la table stockée ressemble à ceci :

playersProgression[12345] = {
	spawn = <référence à la Part checkpoint>,
	course = 2,            -- nombre de tours réalisés
	start = 1735689042.5,  -- temps de démarrage du tour
	best = 45.32,          -- temps du meilleur parcours
}
CléSignification
spawnLe dernier checkpoint touché par ce joueur — utilisé pour savoir où faire respawn s’il meurt
courseLe nombre de tours complets déjà réalisés par ce joueur
startL’heure exacte (tick()) à laquelle le joueur a commencé son tour actuel — sert à calculer son temps une fois arrivé
bestLe meilleur temps (le record personnel) réalisé par ce joueur sur un tour complet

QCM pour vérifier la compréhension

1. Pourquoi faut-il nommer les checkpoints « 1 », « 2 », « 3 »… et pas « Depart », « Milieu », « Fin » ?
A. Pour que ce soit plus joli dans l’Explorer
B. Parce que le script trie les checkpoints selon ces numéros pour connaître leur ordre
C. Ça n’a aucune importance, on peut les nommer comme on veut
D. Parce que Roblox l’exige pour toutes les Parts

2. Que se passe-t-il quand un joueur meurt en plein milieu du parcours ?
A. Il recommence tout le parcours depuis le début
B. Il respawn au dernier checkpoint qu’il a touché
C. Le jeu se ferme
D. Son compteur de tours est remis à zéro

3. Comment le script sait-il qu’un joueur a terminé un tour complet ?
A. Quand il touche n’importe quel checkpoint
B. Quand il touche le checkpoint n°1 juste après avoir touché le tout dernier checkpoint
C. Quand il appuie sur une touche spéciale
D. Automatiquement après 60 secondes

4. Pourquoi les checkpoints sont-ils rendus invisibles et traversables (CanCollide = false) ?
A. Pour que le joueur ne les voie pas et ne soit pas bloqué en les traversant
B. Pour que le jeu soit plus rapide
C. Pour économiser de la mémoire
D. Ce n’est pas obligatoire, c’est juste un choix esthétique

5. À quoi sert le UserId du joueur dans le tableau playersProgression ?
A. À afficher le nom du joueur à l’écran
B. À retenir séparément la progression de CHAQUE joueur individuellement
C. À bannir les joueurs trichent
D. À compter le nombre total de joueurs connectés

6. Que se passe-t-il si deux checkpoints ont accidentellement le même numéro (ex: deux parts nommées « 3 ») ?
A. Rien, le script gère ça automatiquement
B. Le parcours pourrait ne pas fonctionner correctement, car l’ordre ne serait plus fiable
C. Le jeu affiche une erreur et s’arrête
D. Roblox choisit au hasard laquelle utiliser

7. Pourquoi utilise-t-on un Script (serveur) et pas un LocalScript pour ce système ?
A. Parce que le compteur de tours et le meilleur temps doivent être valables pour tout le monde, gérés par un seul endroit fiable (le serveur)
B. Parce que les LocalScripts ne fonctionnent pas avec les Parts
C. Ça n’a pas d’importance, les deux marcheraient pareil
D. Parce que c’est plus rapide à écrire

-- ═══════════════════════════════════════
-- SERVICES ET RÉFÉRENCES
-- ═══════════════════════════════════════
local Players = game:GetService("Players")
local Game = workspace.GAME

local SpawnFolder = Game:WaitForChild("PartSpawnNiv", true)

local baseplate = workspace:FindFirstChild("Baseplate")
if baseplate then
	baseplate:Destroy()
end

-- Tableau des joueurs en cours de partie avec l'étape mémorisée
local playersProgression = {}

-- Tableau de toutes les étapes du jeu, triées par nom pour garantir l'ordre
local spawnsList = SpawnFolder:GetChildren()
table.sort(spawnsList, function(a, b)
	return tonumber(a.Name) < tonumber(b.Name)
end)
local lastSpawnIndex = #spawnsList

local function formatTime(t)
	local minutes = math.floor(t / 60)
	local seconds = math.floor(t % 60)
	local centiemes = math.floor((t * 100) % 100)
	return string.format("%02d:%02d.%02d", minutes, seconds, centiemes)
end

-- ═══════════════════════════════════════
-- RESPAWN AU DERNIER CHECKPOINT
-- ═══════════════════════════════════════
local function respawnAuCheckpoint(player)
	local progression = playersProgression[player.UserId]
	if not progression or not progression.spawn then return end

	local character = player.Character
	if not character then return end

	local humanoidRootPart = character:FindFirstChild("HumanoidRootPart")
	if not humanoidRootPart then return end

	local spawn = progression.spawn
	local targetPosition = spawn.Position + Vector3.new(0, (spawn.Size.Y / 2) + 2, 0)
	humanoidRootPart.CFrame = CFrame.new(targetPosition)
end

-- ═══════════════════════════════════════
-- GESTION DU CHARGEMENT DES JOUEURS
-- ═══════════════════════════════════════
local function initPlayer(player)
	player.CharacterAdded:Connect(function(character)
		character:WaitForChild("HumanoidRootPart")
		respawnAuCheckpoint(player)
	end)
end

Players.PlayerAdded:Connect(initPlayer)
Players.PlayerRemoving:Connect(function(player)
	playersProgression[player.UserId] = nil
end)

-- ═══════════════════════════════════════
-- CHARGEMENT DES ÉTAPES ET DÉTECTION DES CHECKPOINTS
-- ═══════════════════════════════════════
for index, spawn in ipairs(spawnsList) do
	if spawn:IsA("BasePart") then
		spawn.Transparency = 1
		spawn.CanCollide = false
		spawn.Anchored = true

		spawn.Touched:Connect(function(hit)
			local player = Players:GetPlayerFromCharacter(hit.Parent)
			if not player then return end

			local progression = playersProgression[player.UserId]

			-- Ignore si le joueur est déjà sur ce checkpoint
			if progression and progression.spawn == spawn then return end

			if not progression then
				playersProgression[player.UserId] = {
					spawn = spawn,
					course = 0,
					start = tick(),
					best = 0,
					counterLabel = player.PlayerGui.ScreenGui.Counter.CounterLabel,
					bestLabel = player.PlayerGui.ScreenGui.Best.BestLabel,
				}
				return
			end

			-- Tour complet détecté : dernier checkpoint -> premier checkpoint
			if index == 1 and progression.spawn == spawnsList[lastSpawnIndex] then
				progression.course += 1
				progression.counterLabel.Text = string.format("%02d", progression.course)

				local finishTime = tick() - progression.start
				if progression.best == 0 or finishTime < progression.best then
					progression.best = finishTime
				end
				progression.bestLabel.Text = formatTime(progression.best)
			end

			progression.spawn = spawn
			progression.start = tick()
		end)
	end
end
Catégories
Jeu vidéo ROBLOX

Boost temporaire

À quoi sert ce script ?

Ce script permet au joueur d’appuyer sur une touche (F) pour obtenir un boost temporaire de vitesse et de saut pendant quelques secondes, avec une énergie limitée qui se réduit à chaque utilisation, affichée sous forme de barre.

Pourquoi ce script doit être un LocalScript et pas un script serveur

1. Détection des touches du clavier

UserInputService.InputBegan (qui détecte l’appui sur F) ne fonctionne que côté client. Le serveur ne « voit » pas directement quelle touche un joueur appuie sur son clavier — seul l’ordinateur du joueur (le client) peut détecter ça. Si ce script était sur le serveur, la détection de la touche ne fonctionnerait tout simplement pas.

2. Réactivité et fluidité

Un LocalScript s’exécute directement sur l’ordinateur du joueur, sans attendre d’aller-retour réseau avec le serveur. Le chrono et le boost réagissent donc instantanément, sans latence perceptible.

3. Affichage de l’interface (GUI)

Modifier un TextLabel ou la taille d’une barre (energy.Size) dans la PlayerGui doit se faire depuis un LocalScript : c’est l’interface personnelle de ce joueur, elle ne concerne que son écran à lui.

⚠️ Attention cependant : comme ce script est entièrement côté client, un joueur malveillant pourrait potentiellement le modifier pour tricher (boost infini, énergie illimitée…). Dans un vrai jeu, il faudrait que le serveur vérifie et valide réellement les changements de vitesse/saut, plutôt que de faire une confiance totale au client. Ce script convient pour l’affichage et le confort, mais une version « anti-triche » demanderait une communication avec un script serveur.

Pourquoi utiliser des CONSTANTES

EN MAJUSCULES (TIMELAPS, SPEEDMAX, JUMPMAX…)

  1. Convention de lisibilité : en programmation, écrire un nom de variable entièrement en majuscules est une convention largement utilisée pour signaler « ceci est une constante de configuration, une valeur qui ne doit jamais changer pendant l’exécution du script ». Ça permet, en un coup d’œil, de distinguer les réglages (en haut du script) des variables qui évoluent pendant le jeu (energyLevel, startime…).
  2. Facilité de modification : toutes les valeurs importantes sont regroupées au même endroit, en haut du script. Si tu veux changer la durée du boost ou la touche utilisée, tu modifies une seule ligne, sans devoir chercher dans tout le code où cette valeur est utilisée.
  3. Évite les « nombres magiques » : sans constante, on verrait des 50, 70, 6 disséminés dans le code sans savoir à quoi ils correspondent. Avec SPEEDMAX = 50, le nom explique immédiatement le rôle du nombre.

Pourquoi RunService.Heartbeat plutôt qu’une boucle while ... do wait() end

Le problème d’une boucle while avec wait()

-- ❌ Mauvaise pratique
while true do
	wait()
	-- code du chrono
end
  1. Bloque le thread : une boucle infinie s’exécute en continu et occupe le script en permanence, même quand il n’y a rien à faire.
  2. Timing imprécis : wait() ne garantit pas un délai exact — il peut varier légèrement selon la charge du jeu, ce qui rend le chrono moins précis.
  3. Difficile à arrêter proprement : il faut gérer soi-même une condition de sortie, et un oubli peut bloquer le script indéfiniment.

Pourquoi Heartbeat est meilleur

  1. Synchronisé avec le moteur du jeu : RunService.Heartbeat se déclenche automatiquement à chaque frame, juste après les calculs de physique — c’est le rythme naturel du jeu (environ 60 fois par seconde), donc le chrono reste fluide et précis.
  2. Pas de blocage : contrairement à une boucle while, Heartbeat:Connect(...) ne bloque jamais le reste du script — le code continue de s’exécuter normalement en parallèle.
  3. Gestion automatique : pas besoin de gérer soi-même l’arrêt de la boucle ; la fonction ne fait simplement rien (return immédiat) tant que startime est nil, sans gaspiller de ressources inutiles.
  4. Standard de l’industrie : c’est la méthode recommandée par Roblox pour tout ce qui doit se mettre à jour en continu (chronos, animations personnalisées, barres de vie…).

QCM pour vérifier la compréhension

1. Pourquoi ce script doit-il être un LocalScript et non un script serveur ?
A. Parce que les LocalScripts sont plus rapides à écrire
B. Parce que la détection des touches du clavier (InputBegan) ne fonctionne que côté client
C. Parce que Roblox interdit les scripts serveur sur les touches
D. Ça n’a aucune importance, les deux fonctionneraient pareil

2. Que signifie écrire une variable EN MAJUSCULES comme SPEEDMAX ?
A. Que la variable est plus rapide à lire par l’ordinateur
B. Que c’est une convention indiquant qu’il s’agit d’une constante de configuration
C. Que la variable est obligatoirement un nombre
D. Que la variable est visible par tous les scripts du jeu

3. Quel est le principal problème d’une boucle while true do wait() end pour gérer un chrono ?
A. Elle ne fonctionne pas du tout sur Roblox
B. Elle est difficile à écrire
C. Son timing est moins précis et elle bloque le thread en continu
D. Elle ne peut pas afficher de texte

4. Que fait RunService.Heartbeat ?
A. Il vérifie le rythme cardiaque du joueur
B. Il exécute une fonction automatiquement à chaque frame du jeu
C. Il redémarre le script à chaque respawn
D. Il détecte les touches du clavier

5. Pourquoi regrouper tous les réglages (TIMELAPS, SPEEDMAX, JUMPMAX…) en haut du script ?
A. Pour que le script soit plus court
B. Pour faciliter la modification des paramètres à un seul endroit, sans chercher dans tout le code
C. Parce que Roblox l’exige
D. Pour que le jeu se charge plus vite

6. Quel est le risque de sécurité mentionné concernant ce LocalScript ?
A. Il pourrait faire planter le jeu
B. Un joueur malveillant pourrait le modifier pour tricher (énergie infinie, boost permanent)
C. Il consomme trop de mémoire
D. Il ne fonctionne que sur mobile

7. Que se passe-t-il concrètement si on remplace Heartbeat par une boucle while wait() do dans ce script ?
A. Rien ne change, c’est strictement identique
B. Le chrono pourrait être légèrement moins précis et le code moins optimisé, bien que fonctionnellement proche
C. Le boost ne fonctionnerait plus du tout
D. Le jeu planterait immédiatement

Crée cette structure d’UI pour la barre d’énergie et le compteur du boost :

Le ScreenGui possède une propriété ResetOnSpawn, qui vaut true par défaut. Quand elle est activée :

  • À chaque respawn, Roblox détruit le ScreenGui actuel dans la PlayerGui et le recrée à neuf depuis sa version originale dans StarterGui.
  • Toutes les modifications faites par script sont donc perdues, et la barre d’énergie revient à sa taille définie dans les Properties de Studio.

Solution

Dans l’Explorer, sélectionne ton ScreenGui et décoche/mets à false la propriété ResetOnSpawn :

Crée un LocalScript sous StarterPlayerScripts

Sous StarterPlayerScripts

Le script s’exécute dès la connexion du joueur, souvent avant même que le personnage n’existe dans le jeu (player.Character vaut alors nil, car le perso n’a pas encore spawn).

Sous StarterCharacterScripts

Le script est recréé et réexécuté à chaque respawn du personnage.

Dans notre cas, nous avons besoin de garder les informations sur le joueur après un respawn, crée donc un LocalScript sous StarterPlayerScripts :

-- Récupération des services
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

-- Paramètrage du service
local TIMELAPS = 6
local SPEEDMAX = 50
local JUMPMAX = 70
local TOUCHBOOST = Enum.KeyCode.F
local ENERGY_COST = 0.1

-- Récupération du player et des écrans
local player = Players.LocalPlayer

-- La PlayerGui et son contenu ne sont PAS détruits au respawn : on les récupère une seule fois
local playerGui = player:WaitForChild("PlayerGui")
local screenGui = playerGui:WaitForChild("ScreenGui")

-- Penser à modifier la propriété suivante dans ROBLOX STUDIO screenGui.ResetOnSpawn = false
local chrono = playerGui:WaitForChild("ScreenGui"):WaitForChild("Frame"):WaitForChild("ChronoBoost")
local energy = playerGui:WaitForChild("ScreenGui"):WaitForChild("Frame"):WaitForChild("Energy")
local info = energy:WaitForChild("Frame"):WaitForChild("InfoLabel")

-- Initialisation des variables au lancement du script
local humanoid, jumpHeight, walkSpeed
local energyLevel = 1
local startime = nil

-- Initialisation de l'affichage de la barre de puissance
info.Text = "Press " .. TOUCHBOOST.Name .. " to boost for 3 seconds (Energy " .. math.floor(ENERGY_COST * 100) .. "%)"
chrono.Visible = false

-- Formatage du temps restant en seconde et dixième de seconde
local function formatTime(t)
	local seconds = math.floor(t % 60)
	local tenths = math.floor((t * 10) % 10)
	return string.format("%02d.%d", seconds, tenths)
end

-- Chargement du player
local function onCharacterAdded(character)
	humanoid = character:WaitForChild("Humanoid")
	jumpHeight = humanoid.JumpHeight
	walkSpeed = humanoid.WalkSpeed
	startime = nil
end

player.CharacterAdded:Connect(onCharacterAdded)
if player.Character then
	onCharacterAdded(player.Character)
end

-- Récupération de la touche du clavier pour actionner le boost
local function onKeyPress(input, gameProcessed)
	if gameProcessed or startime or not humanoid then return end
	if input.KeyCode ~= TOUCHBOOST then return end
	if energyLevel < ENERGY_COST then return end

	humanoid.JumpHeight = JUMPMAX
	humanoid.WalkSpeed = SPEEDMAX
	--energyLevel = math.clamp(energyLevel - ENERGY_COST, 0, 1)
	startime = tick()
	--energy.Size = UDim2.new(energyLevel, 0,  energy.Size.Y.Scale, 0)
end

UserInputService.InputBegan:Connect(onKeyPress)
-- Affichage du temps restant du boost
RunService.Heartbeat:Connect(function()
	if not startime or not humanoid then return end

	local elapsed = TIMELAPS - (tick() - startime)
	if elapsed > 0 then
		chrono.Visible = true
		chrono.Text = formatTime(elapsed)
	else
		startime = nil
		humanoid.JumpHeight = jumpHeight
		humanoid.WalkSpeed = walkSpeed
		chrono.Visible = false
		energyLevel = math.clamp(energyLevel - ENERGY_COST, 0, 1)
		energy.Size = UDim2.new(energyLevel, 0,  energy.Size.Y.Scale, 0)
	end
end)
Catégories
Jeu vidéo ROBLOX

Zone de boosts

Crée quatre parts avec un pouvoir pour chaque part :

  • devenir transparent
  • changer de taille
  • courir plus vite
  • sauter plus haut

Explication des 4 boosts

Invisibilité

Quand un joueur touche une des parts du dossier, le script vérifie s’il a déjà un dossier "Transparency" sur son Humanoid.
S’il ne l’a pas, il le crée et rend transparents tous les vêtements/accessoires et le corps du joueur (Transparency = 0.9).
S’il l’a déjà, le script le supprime et redonne au joueur son apparence normale (Transparency = 0).Un système de cooldown empêche de réactiver le pouvoir immédiatement après l’avoir utilisé.
C’est donc un interrupteur (toggle) : chaque contact avec la part active ou désactive l’invisibilité.

-- Script pour rendre invisible les joueurs qui touchent la part
local TRANSPARENCE_ACTIVE = 0.9
local TRANSPARENCE_INACTIVE = 0
local COOLDOWN = 1 -- secondes avant de pouvoir re-déclencher

local cooldowns = {} -- [character] = true pendant le cooldown

local function playerTransparency(character, transparency)
	for _, chose in ipairs(character:GetChildren()) do
		if chose:IsA("Accessory") then
			-- Rend transparent les BasePart/Decal de l'accessoire
			for _, descendant in ipairs(chose:GetDescendants()) do
				if descendant:IsA("BasePart") or descendant:IsA("Decal") then
					descendant.Transparency = transparency
				end
			end
		elseif chose:IsA("MeshPart") then
			-- Le MeshPart lui-même est la part à rendre transparente
			chose.Transparency = transparency
		end
	end
end

-- Boucle sur toutes les parts du folder des parts pour rendre invisible
local part = script.Parent

part.Touched:Connect(function(hit)
	local character = hit.Parent
	local humanoid = character and character:FindFirstChild("Humanoid")
	if not humanoid then return end
	if cooldowns[character] then return end

	cooldowns[character] = true

	local existingFolder = humanoid:FindFirstChild("Transparency")
	local transparency

	if existingFolder then
		existingFolder:Destroy()
		transparency = TRANSPARENCE_INACTIVE
	else
		local folder = Instance.new("Folder")
		folder.Name = "Transparency"
		folder.Parent = humanoid
		transparency = TRANSPARENCE_ACTIVE
	end

	playerTransparency(character, transparency)

	task.wait(COOLDOWN)
	cooldowns[character] = nil
end)

Taille

  1. Quand le joueur touche la part, le script regarde si un dossier "Scale" existe déjà sur son Humanoid.
  2. S’il n’existe pas, il sauvegarde la taille actuelle du joueur, puis l’agrandit ou le rétrécit à SCALEMAX (0.3, donc joueur miniature).
  3. S’il existe déjà, le script redonne au joueur sa taille d’origine et supprime le dossier.
  4. Les 4 dimensions (hauteur, largeur, profondeur, tête) sont modifiées en même temps pour un effet cohérent.
  5. Comme pour l’invisibilité, c’est un toggle protégé par un cooldown.
local part = script.Parent

local SCALEMAX = 0.3

local COOLDOWN = 1 -- secondes avant de pouvoir re-déclencher
local cooldowns = {} -- [character] = true pendant le cooldown

part.Touched:Connect(function(plr)
	local character = plr.Parent
	local humanoid = character and character:FindFirstChild("Humanoid")
	if not humanoid  then return end

	if cooldowns[character] then return end
	cooldowns[character] = true

	local existingFolder = humanoid:FindFirstChild("Scale")

	if existingFolder then
		humanoid.BodyHeightScale.Value = existingFolder.Value
		humanoid.BodyWidthScale.Value = existingFolder.Value		
		humanoid.BodyDepthScale.Value = existingFolder.Value	
		humanoid.HeadScale.Value = existingFolder.Value
		existingFolder:Destroy()
	else
		local folder = Instance.new("IntValue")
		folder.Name = "Scale"
		folder.Parent = humanoid
		folder.Value = humanoid.BodyHeightScale.Value
		humanoid.BodyHeightScale.Value = SCALEMAX
		humanoid.BodyWidthScale.Value = SCALEMAX		
		humanoid.BodyDepthScale.Value = SCALEMAX	
		humanoid.HeadScale.Value = SCALEMAX
	end

	task.wait(COOLDOWN)
	cooldowns[character] = nil

end)

Vitesse

  1. Quand le joueur touche la part, le script vérifie si un dossier "Speed" existe sur son Humanoid.
  2. S’il n’existe pas, il sauvegarde la vitesse actuelle du joueur (WalkSpeed) et la remplace par SPEEDMAX (50, donc très rapide).
  3. S’il existe déjà, le script restaure la vitesse normale du joueur et supprime le dossier.
  4. Cela permet au joueur de courir beaucoup plus vite qu’en temps normal.
  5. Le cooldown évite d’activer/désactiver le pouvoir trop rapidement en boucle.
local part = script.Parent

local SPEEDMAX = 50

local COOLDOWN = 1 -- secondes avant de pouvoir re-déclencher
local cooldowns = {} -- [character] = true pendant le cooldown

part.Touched:Connect(function(plr)
	local character = plr.Parent
	local humanoid = character and character:FindFirstChild("Humanoid")
	if not humanoid  then return end
	
	if cooldowns[character] then return end
	cooldowns[character] = true
	
	local existingFolder = humanoid:FindFirstChild("Speed")
	
	if existingFolder then
		humanoid.WalkSpeed = existingFolder.Value
		existingFolder:Destroy()
	else
		local folder = Instance.new("IntValue")
		folder.Name = "Speed"
		folder.Parent = humanoid
		folder.Value = humanoid.WalkSpeed
		humanoid.WalkSpeed = SPEEDMAX
	end
	
	task.wait(COOLDOWN)
	cooldowns[character] = nil
	
end)

Saut

  1. Quand le joueur touche la part, le script vérifie si un dossier "Jump" existe sur son Humanoid.
  2. S’il n’existe pas, il sauvegarde une valeur puis augmente sa hauteur de saut (JumpHeight) jusqu’à JUMPMAX (80, donc saut très haut).
  3. S’il existe déjà, le script restaure la hauteur de saut d’origine et supprime le dossier.
  4. Cela permet au joueur de sauter beaucoup plus haut que d’habitude.
  5. Le cooldown protège contre une réactivation trop rapide du pouvoir.
local part = script.Parent

local JUMPMAX = 80
local COOLDOWN = 1 -- secondes avant de pouvoir re-déclencher
local cooldowns = {} -- [character] = true pendant le cooldown

part.Touched:Connect(function(plr)
	local character = plr.Parent
	local humanoid = character and character:FindFirstChild("Humanoid")
	if not humanoid  then return end
	
	if cooldowns[character] then return end
	cooldowns[character] = true
	
	local existingFolder = humanoid:FindFirstChild("Jump")
	
	if existingFolder then
		humanoid.JumpHeight = existingFolder.Value
		existingFolder:Destroy()
	else
		local folder = Instance.new("IntValue")
		folder.Name = "Jump"
		folder.Parent = humanoid
		folder.Value = humanoid.JumpHeight
		humanoid.JumpHeight = JUMPMAX
	end
	
	task.wait(COOLDOWN)
	cooldowns[character] = nil
	
end)

Point commun aux 4 scripts : ils utilisent tous le même mécanisme (dossier-mémoire + toggle + cooldown).

QCM pour mieux comprendre

1. Que se passe-t-il si un joueur touche deux fois la part « invisibilité » (avec le cooldown écoulé entre les deux) ?
A. Rien la deuxième fois
B. Il devient invisible puis redevient visible
C. Il meurt
D. Il devient invincible

2. À quoi sert le dossier créé (ex: "Speed", "Scale", "Jump") dans chaque script ?
A. À décorer le personnage
B. À mémoriser la valeur d’origine avant modification
C. À compter le nombre de joueurs
D. À afficher un message

3. Pourquoi y a-t-il un COOLDOWN dans chaque script ?
A. Pour ralentir le jeu
B. Pour empêcher de réactiver le pouvoir trop vite d’affilée
C. Pour supprimer le joueur
D. Pour changer la couleur de la part

4. Quelle propriété du Humanoid est modifiée par le pouvoir « saut » ?
A. WalkSpeed
B. Health
C. JumpHeight
D. Transparency

5. Que modifient ENSEMBLE BodyHeightScale, BodyWidthScale, BodyDepthScale et HeadScale ?
A. La vitesse du joueur
B. La taille du joueur
C. La transparence du joueur
D. La couleur du joueur

6. Pourquoi le pouvoir « invisibilité » doit-il aussi modifier les Accessory (accessoires) du joueur ?
A. Pour que le chapeau/les cheveux etc. deviennent transparents aussi, sinon on verrait encore une partie du joueur
B. Pour supprimer les accessoires
C. Pour donner des points au joueur
D. Pour changer la forme des accessoires

Catégories
Jeu vidéo ROBLOX

Courir sinon perdre

Ce script crée un piège basé sur le temps : le joueur doit traverser une zone rapidement, sinon il meurt !

Le script utilise deux parts : part (visible, solide) et hidebox (une zone invisible qui sert à détecter le joueur, comme un capteur).

hidebox.CanCollide = false veut dire que cette zone est traversable : le joueur ne la voit pas et ne se cogne pas dessus, elle sert juste à « sentir » sa présence.

Quand le joueur entre dans la zone (Touched), le script note l’heure exacte grâce à tick() et la stocke dans le tableau lethalDuration, avec le personnage comme « étiquette ».

Quand le joueur sort de la zone (TouchEnded), le script calcule le temps passé dedans : tick() - lethalDuration[character].

Si ce temps est supérieur à DELTATIME (3 secondes), c’est que le joueur a été trop lent à traverser : sa vie est mise à 0 (humanoid.Health = 0), donc il meurt.

Si le joueur traverse en moins de 3 secondes, rien ne se passe : il a réussi l’épreuve !

En résumé : c’est un chronomètre invisible qui punit les joueurs trop lents à traverser une zone, un peu comme dans un jeu de plateforme où il faut courir avant qu’un mur ou un piège ne se referme.

    QCM pour collégiens

    1. À quoi sert la hidebox dans ce script ?
    A. À bloquer physiquement le joueur
    B. À détecter le passage du joueur sans être visible ni solide
    C. À afficher un message au joueur
    D. À téléporter le joueur

    2. Que fait tick() dans ce script ?
    A. Il compte le nombre de joueurs
    B. Il donne l’heure exacte au moment où il est appelé
    C. Il déclenche une explosion
    D. Il redémarre le script

    3. Quand se déclenche TouchEnded ?
    A. Quand le joueur entre dans la zone
    B. Quand le joueur sort de la zone
    C. Quand le joueur meurt
    D. Quand le jeu commence

    4. Que se passe-t-il si le joueur reste plus de 3 secondes dans la hidebox ?
    A. Il gagne des points
    B. Il est téléporté au départ
    C. Sa vie tombe à 0 et il meurt
    D. Rien, il continue de jouer normalement

    5. Pourquoi utilise-t-on character comme clé dans le tableau lethalDuration ?
    A. Pour donner un nom au joueur
    B. Pour retenir l’heure d’entrée de CHAQUE joueur individuellement
    C. Pour compter les vies du joueur
    D. Pour changer la couleur du personnage

    6. Que représente la variable DELTATIME ?
    A. La vitesse du joueur
    B. Le temps maximum autorisé pour traverser la zone
    C. Le nombre de joueurs dans la partie
    D. La taille de la hidebox

    local part = script.Parent
    local hidebox = part:FindFirstChild("Hidebox")
    
    local DELTATIME = 3
    
    part.Anchored = true
    part.CanCollide = true
    part.CanTouch = true
    hidebox.Anchored = true
    hidebox.CanCollide = false
    hidebox.CanTouch = true
    
    local lethalDuration= {} -- [character] = true pendant le cooldown
    
    hidebox.Touched:Connect(function(plr)
    	local character = plr.Parent
    	local humanoid = character and character:FindFirstChild("Humanoid")
    	if not humanoid  then return end
    	
    	lethalDuration[character] = tick()
    
    end)
    
    hidebox.TouchEnded:Connect(function(plr)
    	local character = plr.Parent
    	local humanoid = character and character:FindFirstChild("Humanoid")
    	if not humanoid  then return end
    	if tick() - lethalDuration[character] > DELTATIME then
    		humanoid.Health = 0
    	end
    	
    end)
    Catégories
    Jeu vidéo ROBLOX

    Afficher un chrono du temps de parcours

    Ce système affiche un chrono qui mesure le temps mis par un joueur pour aller d’un bloc de départ à un bloc d’arrivée.

    Le script serveur est placé sur les blocs (départ/arrivée) : il détecte quand le joueur les touche (Touched) et décide quand le chrono doit démarrer ou s’arrêter.

    Le LocalScript, lui, tourne uniquement sur l’ordinateur du joueur : il gère l’affichage du chrono à l’écran (le TextLabel).

    Le serveur ne peut pas modifier directement l’écran d’un joueur : il doit envoyer un message au client via un RemoteEvent.

    Le RemoteEvent fonctionne comme une messagerie entre le serveur et le client : quand le joueur touche le bloc de départ, le serveur « fire » (envoie) l’événement StartEvent.

    Le LocalScript écoute cet événement avec OnClientEvent:Connect(...) et, dès qu’il le reçoit, enregistre l’heure de départ (tick()) et active running = true.

    De la même façon, quand le joueur touche le bloc d’arrivée, le serveur envoie WinEvent, et le LocalScript reçoit ce signal pour arrêter le chrono (running = false).

    Pendant que running est actif, la fonction RunService.Heartbeat recalcule et affiche le temps écoulé à chaque frame (60 fois par seconde environ).

    La fonction formatTime transforme le nombre de secondes brut en un affichage lisible du type 01:23 (minutes:secondes).

    En résumé : le serveur décide (quand démarrer/arrêter), les RemoteEvents transmettent l’info, et le LocalScript affiche le résultat à l’écran du joueur.

      Petit rappel pour bien comprendre

      • Script serveur : tourne sur l’ordinateur qui héberge la partie. Il contrôle les règles du jeu, mais ne peut pas modifier ce qui s’affiche à l’écran d’un joueur précis.
      • LocalScript : tourne uniquement sur l’ordinateur du joueur. Il gère l’affichage (interface, sons, caméra…) mais ne peut pas être utilisé pour des règles importantes du jeu (sinon un joueur pourrait tricher).
      • RemoteEvent : le « pont » entre les deux. Le serveur envoie une info avec FireClient(), et le LocalScript la reçoit avec OnClientEvent:Connect().

      QCM pour bien comprendre

      1. Où s’exécute un LocalScript ?
      A. Sur le serveur du jeu
      B. Sur l’ordinateur du joueur uniquement
      C. Sur tous les ordinateurs en même temps
      D. Nulle part, c’est juste un fichier texte

      2. Pourquoi ne peut-on pas modifier directement le TextLabel depuis le script serveur ?
      A. Parce que c’est interdit par Roblox
      B. Parce que le serveur n’a pas accès à l’affichage local de chaque joueur
      C. Parce que le TextLabel n’existe pas sur le serveur
      D. Parce que ça coûte trop cher en mémoire

      3. À quoi sert un RemoteEvent ?
      A. À supprimer un joueur du jeu
      B. À transmettre une information entre le serveur et un client
      C. À changer la couleur d’un bloc automatiquement
      D. À sauvegarder les données du joueur

      4. Que fait startEvent:FireClient(player) côté serveur ?
      A. Il supprime le joueur
      B. Il envoie un signal au client précis pour déclencher une action
      C. Il démarre le jeu pour tous les joueurs
      D. Il crée un nouveau RemoteEvent

      5. Que fait RunService.Heartbeat dans ce script ?
      A. Il vérifie les battements de cœur du joueur
      B. Il exécute du code à chaque frame, ici pour actualiser l’affichage du chrono
      C. Il redémarre le serveur régulièrement
      D. Il compte le nombre de joueurs connectés

      6. Que se passe-t-il si running vaut false ?
      A. Le chrono continue de s’incrémenter
      B. Le texte du chrono arrête de se mettre à jour
      C. Le joueur est téléporté au départ
      D. Le RemoteEvent est détruit

      Crée un part pour déterminer le départ du chrono et un part pour déterminer le stop du chrono.

      Puis deux RemoteEvent pour gérer le démarrage et l’arrêt du chrono :

      Puis script sur le serveur pour gérer les collisions entre le player et les parts de départ et d’arrivée :

      local replicatedStorage = game:GetService("ReplicatedStorage")
      local remoteEvent = replicatedStorage:WaitForChild("Coursetimecheck")
      
      local startEvent = remoteEvent.StartEvent
      local winEvent = remoteEvent.WinEvent
      
      local start = script.Parent.StartPart
      local succes = script.Parent.SuccessPart
      
      succes.SurfaceGui.Enabled = false
      succes.ParticleEmitter.Enabled = false
      
      succes.Anchored = true
      succes.CanCollide = true
      succes.CanTouch = true
      
      start.Anchored = true
      start.CanCollide = false
      start.CanTouch = true
      start.Transparency = 1
      
      succes.Touched:Connect(function(hit)
      	local character = hit.Parent
      	local humanoid = character and character:FindFirstChild("Humanoid")
      	if not humanoid then return end
      	if succes.SurfaceGui.Enabled then return end
      	
      	remoteEvent.WinEvent:FireClient(game.Players:GetPlayerFromCharacter(character))
      
      	succes.SurfaceGui.Enabled = true
      	succes.ParticleEmitter.Enabled = true
      end)
      
      start.Touched:Connect(function(hit)
      	local character = hit.Parent
      	local humanoid = character and character:FindFirstChild("Humanoid")
      	if not humanoid then return end
      	
      	local player = game.Players:GetPlayerFromCharacter(character)
      	if not player then return end -- sécurité si ce n'est pas un joueur (NPC par ex.)
      
      	print("StartEvent déclenché pour " .. player.Name)
      
      	startEvent:FireClient(player)
      	
      end)

      Et également un localScript pour l’affichage du chrono pour le joueur :

      -- Récupération des services
      local Players = game:GetService("Players")
      local ReplicatedStorage = game:GetService("ReplicatedStorage")
      local RunService = game:GetService("RunService")
      
      -- Récupération du joueur actuel et de la PlayerGui
      local player = Players.LocalPlayer
      local playerGui = player:WaitForChild("PlayerGui")
      
      -- Récupération de l'UI du chrono
      local chrono = playerGui:WaitForChild("ScreenGui"):WaitForChild("Frame"):WaitForChild("ChronoLabel")
      
      -- Récupération des RemoteEvents start et stop du chrono
      local remoteEventsFolder = ReplicatedStorage:WaitForChild("Coursetimecheck")
      local startEvent = remoteEventsFolder:WaitForChild("StartEvent")
      local stopEvent = remoteEventsFolder:WaitForChild("WinEvent")
      
      -- Fonction pour formater le temps en minutes:secondes
      local function formatTime(t)
      	local minutes = math.floor(t / 60)
      	local seconds = math.floor(t % 60)
      	return string.format("%02d:%02d", minutes, seconds)
      end
      
      -- Variables globales pour suivre le temps et l'état du chrono
      local startTime = nil
      chrono.Text = "00:00"
      local running = false
      
      -- Connexion des événements serveur au client
      startEvent.OnClientEvent:Connect(function()
      	startTime = tick()
      	running = true
      end)
      
      stopEvent.OnClientEvent:Connect(function()
      	running = false
      end)
      
      -- Mise à jour continue de l'affichage du chrono
      RunService.Heartbeat:Connect(function()
      	if not running or not startTime then return end
      	chrono.Text = formatTime(tick() - startTime)
      end)
      Catégories
      Jeu vidéo ROBLOX

      Devenir petit en touchant un part

      Ce script est attaché à un Part (bloc) et détecte quand un joueur le touche, via l’événement Touched.

      Il vérifie que c’est bien un personnage avec un Humanoid, et bloque les déclenchements répétés grâce à un système de cooldown (table cooldowns).

      Si le joueur n’a pas encore été rétréci/agrandi, le script sauvegarde sa taille actuelle dans un dossier Scale, puis applique la taille SCALEMAX (0.3) à sa hauteur, largeur, profondeur et tête.

      Si le joueur a déjà été modifié (le dossier Scale existe), le script lui redonne sa taille d’origine et supprime le dossier.

      Autrement dit : c’est un bloc « grandir/rétrécir » qui bascule (toggle) la taille du joueur à chaque contact, avec une seconde d’attente avant de pouvoir réutiliser le bloc.

      QCM : Testez votre compréhension !

      1. Que fait l’événement Touched dans ce script ?
      A. Il vérifie si le joueur a cliqué sur le bloc
      B. Il détecte quand quelque chose touche la Part
      C. Il compte le nombre de joueurs dans le jeu
      D. Il détruit le bloc

      2. À quoi sert la variable COOLDOWN ?
      A. À changer la couleur du bloc
      B. À définir la taille maximale du joueur
      C. À empêcher de réutiliser le bloc trop vite
      D. À supprimer le personnage

      3. Que se passe-t-il si le joueur touche le bloc une deuxième fois (après le cooldown) ?
      A. Rien, ça ne marche qu’une fois
      B. Le joueur reprend sa taille normale
      C. Le joueur devient minuscule
      D. Le jeu plante

      4. À quoi sert le dossier (IntValue) nommé "Scale" ?
      A. À afficher un message au joueur
      B. À mémoriser la taille d’origine du joueur
      C. À compter le temps de cooldown
      D. À stocker le nom du joueur

      5. Pourquoi utilise-t-on une table cooldowns avec le character comme clé ?
      A. Pour donner un nom à chaque joueur
      B. Pour savoir si CE joueur précis est en cooldown
      C. Pour compter les points de chaque joueur
      D. Pour changer la vitesse du joueur

      local part = script.Parent
      
      local SCALEMAX = 0.3
      
      local COOLDOWN = 1 -- secondes avant de pouvoir re-déclencher
      local cooldowns = {} -- [character] = true pendant le cooldown
      
      part.Touched:Connect(function(plr)
      	local character = plr.Parent
      	local humanoid = character and character:FindFirstChild("Humanoid")
      	if not humanoid  then return end
      
      	if cooldowns[character] then return end
      	cooldowns[character] = true
      
      	local existingFolder = humanoid:FindFirstChild("Scale")
      
      	if existingFolder then
      		humanoid.BodyHeightScale.Value = existingFolder.Value
      		humanoid.BodyWidthScale.Value = existingFolder.Value		
      		humanoid.BodyDepthScale.Value = existingFolder.Value	
      		humanoid.HeadScale.Value = existingFolder.Value
      		existingFolder:Destroy()
      	else
      		local folder = Instance.new("IntValue")
      		folder.Name = "Scale"
      		folder.Parent = humanoid
      		folder.Value = humanoid.BodyHeightScale.Value
      		humanoid.BodyHeightScale.Value = SCALEMAX
      		humanoid.BodyWidthScale.Value = SCALEMAX		
      		humanoid.BodyDepthScale.Value = SCALEMAX	
      		humanoid.HeadScale.Value = SCALEMAX
      	end
      
      	task.wait(COOLDOWN)
      	cooldowns[character] = nil
      
      end)
      
      Catégories
      Jeu vidéo ROBLOX

      Courir plus vite ou sauter plus hait en touchant un part

      Courir plus vite

      Ce script surveille une part (un bloc) dans le jeu, et attend qu’un joueur la touche.

      Quand un joueur la touche, le script vérifie qu’il s’agit bien d’un personnage (avec un Humanoid, la partie du jeu qui gère la santé, les déplacements, etc.).

      Si c’est bien un joueur, sa vitesse de déplacement (WalkSpeed) passe à 50; il devient beaucoup plus rapide (par défaut, c’est 16).

      QCM — vérifie ta compréhension

      1. Que fait part.Touched:Connect(...) ?
      A) Il détruit la part
      B) Il détecte quand quelque chose touche la part
      C) Il déplace la part
      D) Il change la couleur de la part

      2. Pourquoi le script vérifie if not humanoid then return end ?
      A) Pour vérifier que la part existe
      B) Pour arrêter le script si ce qui a touché n’est pas un personnage (par exemple un objet sans Humanoid)
      C) Pour ralentir le joueur
      D) Pour supprimer le Humanoid

      3. Que représente Humanoid dans un personnage Roblox ?
      A) Le nom du joueur
      B) La couleur de la peau
      C) La partie qui gère la santé, les déplacements, etc.
      D) Un accessoire du personnage

      4. Que se passe-t-il quand humanoid.WalkSpeed = 50 s’exécute ?
      A) Le joueur devient invisible
      B) Le joueur saute plus haut
      C) Le joueur se déplace plus vite
      D) Le joueur perd de la vie

      5. Si un objet SANS Humanoid (comme une simple balle) touche la part, que se passe-t-il ?
      A) Le script plante avec une erreur
      B) Rien, le script s’arrête grâce au return
      C) La balle devient rapide
      D) Le jeu redémarre

      local part = script.Parent
      
      part.Touched:Connect(function(plr)
      	local character = plr.Parent
      	local humanoid = character and character:FindFirstChild("Humanoid")
      	if not humanoid  then return end
      	
      	humanoid.WalkSpeed = 50
      		
      end)
      

      Bascule courir plus vite / normalement

      Quand un joueur touche la part, le script regarde s’il a déjà une vitesse « boostée » (mémorisée dans un petit objet nommé « Speed ») : si oui, il lui rend sa vitesse normale, sinon il sauvegarde sa vitesse actuelle et le rend très rapide (50).

      Un système de « cooldown » (temps d’attente) empêche le joueur de retoucher la part plusieurs fois par seconde et de faire buguer le changement de vitesse.

      Ainsi, la part fonctionne comme un interrupteur : un coup pour accélérer, un coup pour revenir à la normale, avec une petite pause obligatoire entre deux touches.

      QCM — vérifie ta compréhension

      1. À quoi sert la variable cooldowns ?
      A) À compter combien de joueurs ont touché la part
      B) À empêcher un joueur de redéclencher l’effet plusieurs fois trop vite
      C) À stocker la couleur de la part
      D) À sauvegarder le nom du joueur

      2. Que fait l’objet IntValue nommé « Speed » ?
      A) Il affiche un message au joueur
      B) Il mémorise la vitesse de marche du joueur avant le boost, pour pouvoir la restaurer plus tard
      C) Il détruit le personnage
      D) Il change la couleur de la part

      3. Que se passe-t-il si le joueur touche la part une seconde fois (sans cooldown actif) ?
      A) Sa vitesse redevient normale, car le dossier « Speed » existe déjà
      B) Rien ne se passe
      C) Sa vitesse augmente encore plus
      D) Le jeu plante

      4. Pourquoi utilise-t-on task.wait(COOLDOWN) à la fin de la fonction ?
      A) Pour ralentir tous les joueurs du jeu
      B) Pour attendre 1 seconde avant de permettre au joueur de redéclencher l’effet
      C) Pour détruire la part après 1 seconde
      D) Pour attendre que le joueur meure

      5. Si un joueur touche la part pendant que son cooldown est actif, que fait le script ?
      A) Il double sa vitesse
      B) Il arrête la fonction immédiatement avec return, sans rien changer
      C) Il redémarre le cooldown
      D) Il supprime le dossier « Speed »

      local part = script.Parent
      
      local SPEEDMAX = 50
      
      local COOLDOWN = 1 -- secondes avant de pouvoir re-déclencher
      local cooldowns = {} -- [character] = true pendant le cooldown
      
      part.Touched:Connect(function(plr)
      	local character = plr.Parent
      	local humanoid = character and character:FindFirstChild("Humanoid")
      	if not humanoid  then return end
      	
      	if cooldowns[character] then return end
      	cooldowns[character] = true
      	
      	local existingFolder = humanoid:FindFirstChild("Speed")
      	
      	if existingFolder then
      		humanoid.WalkSpeed = existingFolder.Value
      		existingFolder:Destroy()
      	else
      		local folder = Instance.new("IntValue")
      		folder.Name = "Speed"
      		folder.Parent = humanoid
      		folder.Value = humanoid.WalkSpeed
      		humanoid.WalkSpeed = SPEEDMAX
      	end
      	
      	task.wait(COOLDOWN)
      	cooldowns[character] = nil
      	
      end)
      

      Essaye le même système pour faire sauter plus haut :

      	humanoid.JumpHeight = 20
      local part = script.Parent
      
      local SCALEMAX = 4
      
      local COOLDOWN = 1 -- secondes avant de pouvoir re-déclencher
      local cooldowns = {} -- [character] = true pendant le cooldown
      
      part.Touched:Connect(function(plr)
      	local character = plr.Parent
      	local humanoid = character and character:FindFirstChild("Humanoid")
      	if not humanoid  then return end
      
      	if cooldowns[character] then return end
      	cooldowns[character] = true
      
      	local existingFolder = humanoid:FindFirstChild("Scale")
      
      	if existingFolder then
      		humanoid.BodyHeightScale.Value = existingFolder.Value
      		humanoid.BodyWidthScale.Value = existingFolder.Value		
      		humanoid.BodyDepthScale.Value = existingFolder.Value	
      		humanoid.HeadScale.Value = existingFolder.Value
      		existingFolder:Destroy()
      	else
      		local folder = Instance.new("IntValue")
      		folder.Name = "Scale"
      		folder.Parent = humanoid
      		folder.Value = humanoid.BodyHeightScale.Value
      		humanoid.BodyHeightScale.Value = SCALEMAX
      		humanoid.BodyWidthScale.Value = SCALEMAX		
      		humanoid.BodyDepthScale.Value = SCALEMAX	
      		humanoid.HeadScale.Value = SCALEMAX
      	end
      
      	task.wait(COOLDOWN)
      	cooldowns[character] = nil
      
      end)
      

      Traverser le plus vite possible autrement ..