Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Générer un escalier

avec une Boucle et des Clones

Apprendre à faire construire un escalier géant de 200 marches par le script lui-même ! Au lieu de placer les marches une par une à la main dans Roblox Studio (bon courage pour 200 marches ! :

  1. Utiliser une Part modèle (template) qui sert de moule.
  2. Cloner cette Part dans une boucle for (rappel du tutoriel de la pyramide !).
  3. Positionner chaque marche avec un calcul basé sur le numéro de la marche (i).
  4. Ranger proprement les marches dans un dossier créé par le script.

Ce tutoriel réunit des briques déjà connues : Clone(), Parent, Instance.new, les boucles for, les Vector3 et les paramètres en variables !

Introduction : Pourquoi générer un escalier par script ?

Imagine que tu veuilles construire un escalier de 200 marches dans Studio :

  • Chaque marche doit être positionnée un peu plus haut et un peu plus loin que la précédente.
  • À la main, c’est 200 manipulations… et si tu veux changer la hauteur des marches, il faut tout recommencer !

Avec un script :

  • Les 200 marches sont créées en moins d’une seconde.
  • Changer la hauteur, la largeur, le nombre de marches = changer 1 nombre et relancer !
  • Le niveau reste parfaitement régulier (aucune marche de travers !).

C’est toute la puissance de la programmation : l’ordinateur exécute les tâches répétitives à ta place.

Préparation dans Roblox Studio

  1. Dans Workspace, crée un dossier nommé Folder.
  2. Ajoute une Part nommée Part dans ce dossier : c’est le moule.
    • Place-la à l’endroit où l’escalier doit commencer.
    • Sa taille n’a pas grande importance (le script la règle à chaque clone !), mais tu peux lui donner une couleur sympa : toutes les marches hériteront de cette couleur (et du matériau !).
  3. Crée un Script dans ServerScriptService et colle le code.
  4. Lance le jeu :
    • L’escalier de 200 marches se construit instantanément !
    • Dans l’Explorer, un dossier EscalierDroit contient Step0, Step1, … Step199.
  5. Teste la montée : ton joueur peut-il grimper ? Si les marches sont trop hautes, réduis rise !

⚠️ Conseil de test : mets stepCount = 10 pendant tes essais (escalier court, facile à inspecter), et seulement à la fin remets 200 !

Générer une échelle

Voici le script simple commenté en français :

-- Récupérer le dossier et la Part modèle (le "moule" des marches)
local stairsFolder = workspace:WaitForChild("Folder")
local template = stairsFolder:WaitForChild("Part")

-- Ancrer le modèle pour qu'il ne tombe pas pendant la construction
template.Anchored = true
template.Size = Vector3.new(8, 0.5, 4)

-- Boucle : créer les marches une par une
-- i va de 1 à 10 
for i = 1, 10 do
	-- 1) Cloner le modèle
	local step = template:Clone()

	-- 3) LE CŒUR DU SCRIPT : positionner la marche
	-- La marche n° i se trouve à :
	--   - X : interval * i  → chaque marche est décalée de "interval" studs vers l'avant
	--   - Y : (step.Size.Y + hauteur) * i → chaque marche est décalée vers le HAUT
	--   - Z : 0 → pas de décalage sur l'axe Z (escalier droit !)
	step.Position += Vector3.new(
		0,                          -- décalage horizontal
		(step.Size.Y + 1) * i,               -- décalage vertical
		0                                      -- pas de décalage Z
	)

	-- 4) Attacher la marche au dossier et la nommer
	step.Parent = stairsFolder
	step.Name = "Step" .. i     -- "Step0", "Step1", "Step2"...
end

Générer un escalier droit

Voici le script simple commenté en français :

-- Récupérer le dossier et la Part modèle (le "moule" des marches)
local stairsFolder = workspace:WaitForChild("Folder")
local template = stairsFolder:WaitForChild("Part")

-- Ancrer le modèle pour qu'il ne tombe pas pendant la construction
template.Anchored = true
template.Size = Vector3.new(8, 0.5, 4)

-- Boucle : créer les marches une par une
-- i va de 1 à 10 
for i = 1, 10 do
	-- 1) Cloner le modèle
	local step = template:Clone()

	-- 3) LE CŒUR DU SCRIPT : positionner la marche
	-- La marche n° i se trouve à :
	--   - X : interval * i  → chaque marche est décalée de "interval" studs vers l'avant
	--   - Y : (step.Size.Y + hauteur) * i → chaque marche est décalée vers le HAUT
	--   - Z : 0 → pas de décalage sur l'axe Z (escalier droit !)
	step.Position += Vector3.new(
		3 * i,                          -- décalage horizontal
		(step.Size.Y + 1) * i,               -- décalage vertical
		0                                      -- pas de décalage Z
	)

	-- 4) Attacher la marche au dossier et la nommer
	step.Parent = stairsFolder
	step.Name = "Step" .. i     -- "Step0", "Step1", "Step2"...
end

Et le script modifié pour le même résultat mais avec des constantes pour mieux modifier le code :

-- Récupérer le dossier et la Part modèle (le "moule" des marches)
local stairsFolder = workspace:WaitForChild("Folder")
local template = stairsFolder:WaitForChild("Part2")

-- ============================================================
-- PARAMÈTRES : c'est ICI que tout se règle ! Modifie-les !
-- ============================================================
local stepCount = 200          -- nombre de marches
local rise = 2                 -- hauteur gagnée par marche (studs)
local stepWidth = 4            -- largeur de la marche (axe X)
local stepDepth = 8            -- profondeur de la marche (axe Z) — espace pour poser le pied
local stepThickness = 0.5      -- épaisseur de la planche de la marche
local interval = 5            -- distance entre deux marches (axe X)

-- Ancrer le modèle pour qu'il ne tombe pas pendant la construction
template.Anchored = true
template.Size = Vector3.new(stepWidth, stepThickness, stepDepth)

-- Boucle : créer les marches une par une
-- i va de 0 à stepCount - 1 (donc 200 tours pour stepCount = 200)
for i = 1, stepCount do
	-- 1) Cloner le modèle
	local step = template:Clone()

	-- 3) LE CŒUR DU SCRIPT : positionner la marche
	-- La marche n° i se trouve à :
	--   - X : interval * i  → chaque marche est décalée de "interval" studs vers l'avant
	--   - Y : (step.Size.Y + rise) * i → chaque marche est décalée vers le HAUT
	--   - Z : 0 → pas de décalage sur l'axe Z (escalier droit !)
	step.Position += Vector3.new(
		interval * i,                          -- décalage horizontal
		(step.Size.Y + rise) * i,               -- décalage vertical
		0                                      -- pas de décalage Z
	)

	-- 4) Attacher la marche au dossier et la nommer
	step.Parent = stairsFolder
	step.Name = "Step" .. i     -- "Step0", "Step1", "Step2"...
end


Explications Détaillées

1. La Part modèle (template) : le moule

local template = folder:WaitForChild("Part")
  • La Part nommée Part dans le dossier Folder ne sera jamais visible dans le jeu : elle sert uniquement de moule.
  • template:Clone() (rappel du tutoriel de la pyramide inversée !) crée une copie exacte à chaque tour de boucle.
  • À la fin, template:Destroy() supprime le moule : il a fait son travail ! (La ligne est facultative : sans elle, le moule resterait visible au sol.)

2. Les paramètres : le tableau de bord de l’escalier

local stepCount = 200
local rise = 2
local stepWidth = 4
local stepDepth = 8
local stepThickness = 0.5
local interval = 5

Toutes les dimensions sont des variables en haut du script : c’est le tableau de bord ! Expérimente :

ParamètreRôleQue se passe-t-il si tu le changes ?
stepCountNombre de marches10 → un petit escalier de test ! 500 → une échelle vers le ciel !
riseHauteur gagnée par marche1 → escalier doux. 5 → escalier raide qu’on ne peut pas grimper !
stepWidthLargeur (axe X)20 → autoroute géante !
stepDepthProfondeur (axe Z)2 → marches minuscules, impossible de monter.
stepThicknessÉpaisseur de la planche2 → marches massives.
intervalDistance entre marches (axe X)2 → marches serrées, plus facile à monter !

⚠️ Rappel important : pour qu’un joueur puisse monter l’escalier, la hauteur gagnée par marche (rise + stepThickness) doit rester inférieure à la hauteur de marche maximale que Roblox permet de franchir (environ 2.2 studs par défaut). Ici : stepThickness (0.5) + rise (2) = 2.5… c’est juste au-dessus ! Si ton joueur bloque sur une marche, réduis rise à 1.5 ! C’est le genre de chose qu’on découvre… en testant !

3. LA LIGNE MAGIQUE : le calcul de la position

step.Position += Vector3.new(interval * i, (step.Size.Y + rise) * i, 0)

C’est le cœur du tutoriel ! Décomposons-la :

L’axe X : interval * i

  • Marche n° 0 : décalage = 5 × 0 = 0 → position du moule.
  • Marche n° 1 : décalage = 5 × 1 = 5 studs.
  • Marche n° 2 : décalage = 5 × 2 = 10 studs.
  • Marche n° 199 : décalage = 5 × 199 = 995 studs !
  • Le numéro de la marche i pilote son décalage : plus i est grand, plus la marche est loin. C’est ça, une progression arithmétique (des maths de collège ! ).

L’axe Y : (step.Size.Y + rise) * i

  • Chaque marche est posée au-dessus de la précédente.
  • Pourquoi step.Size.Y + rise et pas juste rise ? L’épaisseur de la planche (stepThickness) doit être ajoutée : sans elle, chaque marche serait enfoncée dans la précédente au lieu d’être posée dessus. Le saut vertical entre deux marches = l’épaisseur de la planche + la hauteur gagnée.
  • Marche n° 199 : hauteur ≈ 2.5 × 199 = 497.5 studs… un escalier qui monte jusqu’au ciel !

L’axe Z : 0

  • Aucun décalage : les marches s’alignent droit. C’est pour ça que le dossier s’appelle « EscalierDroit » !

La position de départ : +=

  • step.Position += ... : on part de la position du moule et on ajoute le décalage. Avantage : l’escalier se construit où que tu aies placé la Part modèle ! Déplace le moule dans Studio → tout l’escalier suit !

4. La boucle et le compteur i

for i = 0, stepCount - 1 do
  • Pourquoi commencer à 0 et finir à stepCount - 1 ? Avec i = 0, la première marche est positionnée exactement à l’emplacement du moule (décalage × 0 = aucun décalage). C’est plus intuitif pour placer l’escalier !
  • On pourrait aussi écrire for i = 1, stepCount — mais alors le calcul des positions devrait devenir (i - 1) partout. Choisir 0, c’est éviter les -1 partout !
  • step.Name = "Step" .. i (rappel : concaténation avec un nombre !) : chaque marche a un nom unique → on pourra la retrouver plus tard avec FindFirstChild("Step" .. i).

QCM : Teste ta compréhension !

Sur le moule (template) :

  1. À quoi sert la Part nommée Part dans le dossier ?
    • a) C’est la première marche de l’escalier.
    • b) Elle sert à décorer le niveau.
    • c) C’est un moule : le script la clone pour créer chaque marche, puis la détruit.
  1. Pourquoi appelle-t-on template:Destroy() à la fin ?
    • a) Le moule a servi et n’a plus d’utilité : on nettoie le niveau.
    • b) Pour supprimer tout l’escalier.
    • c) C’est obligatoire sinon le jeu plante.

Sur les paramètres :

  1. Que se passe-t-il si on change stepCount = 200 en stepCount = 50 ?
    • a) Les marches sont 4 fois plus petites.
    • b) L’escalier ne fait plus que 50 marches (il est 4 fois plus court).
    • c) Rien ne change.
  1. Pourquoi les dimensions sont-elles mises en variables en haut du script ?
    • a) Parce que Lua oblige.
    • b) Pour décorer le script.
    • c) Pour pouvoir ajuster l’escalier facilement en changeant un seul nombre, sans toucher au reste du code.

Sur le calcul de position :

  1. Pour la marche n° 3 (i = 3), quel est son décalage horizontal (axe X) avec interval = 5 ?
    • a) 15 studs (interval * i = 5 × 3).
    • b) 5 studs.
    • c) 3 studs.
  1. Pourquoi ajoute-t-on step.Size.Y dans le calcul vertical (step.Size.Y + rise) * i ?
    • a) Pour changer la couleur des marches.
    • b) Pour compenser l’épaisseur de la planche : sinon chaque marche serait enfoncée dans la précédente au lieu d’être posée dessus.
    • c) Pour aller plus vite.
  1. Pourquoi le décalage Z est-il à 0 ?
    • a) Parce que l’escalier est DROIT : les marches avancent sur un seul axe, sans tourner.
    • b) Parce que l’axe Z n’existe pas.
    • c) Parce que les marches sont invisibles.
  1. Que signifie step.Position += Vector3.new(...) ?
    • a) On remplace la position par celle du moule.
    • b) On AJOUTE le décalage à la position actuelle du clone (celle du moule) — l’escalier se construit là où est placé le moule.
    • c) On supprime la position.
      Réponse : a)

Sur la boucle :

  1. Pourquoi la boucle va-t-elle de 0 à stepCount - 1 ?
    • a) Pour que la première marche (i = 0) soit positionnée exactement à l’emplacement du moule, sans décalage.
    • b) Parce que Lua interdit de commencer à 1.
    • c) Pour économiser une marche.
  1. Pourquoi chaque marche reçoit-elle un nom "Step" .. i ?
    • a) Pour avoir un nom unique et pouvoir retrouver chaque marche plus tard (FindFirstChild).
    • b) Pour la détruire immédiatement.
    • c) Pour changer sa couleur.
  1. Pourquoi range-t-on toutes les marches dans le dossier EscalierDroit ?
    • a) Pour garder l’Explorer propre et pouvoir manipuler tout l’escalier d’un seul coup.
    • b) Parce que les Parts ne peuvent pas être ailleurs.
    • c) Pour rien.

Idées pour aller plus loin

1. Un escalier aux couleurs dégradées

  • Objectif : dégradé du bleu au rouge du bas en haut (rappel des boucles de couleurs du tutoriel de découverte !).
  • Comment faire ? Calcule la couleur avec i :
-- Dans la boucle : 
local ratio = i / stepCount -- de 0 à 1 
step.Color = Color3.fromRGB(255 * ratio, 0, 255 * (1 - ratio))

2. Une marche sur deux avec un effet spécial

  • Objectif : les marches paires sont normales, les marches impaires personnalisées !
  • Comment faire ? Utilise l’opérateur modulo (i % 2) qui donne le reste de la division :
if i % 2 == 1 then -- marches impaires           step.Color = Color3.fromRGB(0, 255, 0) 
        step.BrickColor = BrickColor.new("Lime green") 
end

3. Un escalier construit marche par marche (effet visuel)

  • Objectif : voir l’escalier se construire sous tes yeux.
  • Comment faire ? Ajoute task.wait(0.05) à la fin de la boucle : chaque marche apparaît l’une après l’autre ! Pour 200 marches, ça donne un bel effet de construction.

4. Démontage progressif de l’escalier

  • Objectif : comme pour la pyramide, détruire les marches une par une (mode « escalier qui s’effondre » !).
  • Comment faire ? (rappel du tutoriel de découverte !)
  • task.wait(3) for i = stepCount - 1, 0, -1 do stairsFolder:FindFirstChild("Step" .. i):Destroy() task.wait(0.1) end
for i = stepCount - 1, 0, -1 do 
  stairsFolder:FindFirstChild("Step" .. i):Destroy()
  task.wait(0.1)
 
end

Résumé des concepts clés

ConceptDescriptionExemple
Part modèle (template)Un moule cloné autant de fois que nécessaire, puis détruit.template:Clone()
Paramètres en variablesToutes les dimensions réglables en haut du script.stepCount, rise, interval
Boucle for génératriceLe compteur i pilote à la fois le nombre ET la position.for i = 0, stepCount - 1 do
Progression arithmétiqueDécalage = valeur × numéro de la marche.interval * i
Vector3.new de positionDécalage X (avant), Y (haut), Z (côté).‘+= Vector3.new(interval*i, hauteur*i, 0)
Instance.new("Folder")Créer un dossier par le script pour ranger les clones..Parent = workspace
Noms uniquesRetrouver chaque clone plus tard."Step" .. i
Ne pas masquer les variablesDeux variables du même nom = confusion !folder vs stairsFolder

Astuce : teste toujours avec stepCount = 10 avant de passer à 200 — un escalier court, c’est plus rapide à inspecter, et tu gagneras un temps fou pendant tes réglages


Générer un escalier hélicoïdal ou en colimaçon

local folder = workspace:WaitForChild("Folder")
local template = folder:WaitForChild("Part")

-- Paramètres
local stepCount = 400        -- nombre de marches
local angleStep = 15        -- degrés entre deux marches (360 / 15 = 24 marches par tour)
local rise = 1              -- hauteur gagnée par marche (studs)
local radius = 12           -- distance entre l'axe de l'escalier et le centre d'une marche
local stepWidth = 8         -- largeur radiale de la marche
local stepThickness = 0.5   -- épaisseur de la marche
local center = template.Position

-- Profondeur de la marche : corde de l'arc extérieur, pour éviter les trous entre deux marches
local outerRadius = radius + stepWidth / 2
local stepDepth = 2 * outerRadius * math.sin(math.rad(angleStep) / 2)

template.Anchored = true
local stairsFolder = Instance.new("Folder")
stairsFolder.Name = "EscalierHelicoidal"
stairsFolder.Parent = workspace


for i = 0, stepCount - 1 do
	local step = template:Clone()
	
	step.Size = Vector3.new(stepWidth, stepThickness, stepDepth)
	step.Anchored = true
	
	local ratio = i / stepCount -- de 0 à 1 
	step.Color = Color3.fromRGB(255 * ratio, 0, 255 * (1 - ratio))	
	
	-- 1) on se place à la bonne hauteur, 2) on tourne autour de l'axe Y,
	-- 3) on s'écarte du centre : la marche est ainsi orientée radialement
	step.CFrame = CFrame.new(center + Vector3.new(0, i * rise, 0))
		* CFrame.Angles(0, math.rad(i * angleStep), 0)
		* CFrame.new(radius, 0, 0)

	step.Parent = stairsFolder
end

template:Destroy() -- retire la part modèle (facultatif)
Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Comprendre l’apparition des joueurs

et les changements d’états

Découvrir la vie d’un joueur dans Roblox avec un script « d’observation » :

  1. Quand un joueur se connecte (PlayerAdded).
  2. Quand son personnage apparaît (CharacterAdded).
  3. Quand son personnage change d’état (StateChanged : saut, chute, atterrissage…).

Ce script n’est pas un jeu : c’est un script espion ! Il ne fait qu’afficher dans la console (Output) tout ce qui arrive aux joueurs. C’est l’outil parfait pour comprendre ces trois événements avant de les utiliser dans de vrais jeux (comme la zone anti-gravité ou les checkpoints !).

Introduction : La vie d’un joueur Roblox

Quand tu joues à un jeu Roblox, voici ce qui se passe dans l’ordre :

1. Le JOUEUR se connecte au jeu        → événement PlayerAdded
2. Son PERSONNAGE apparaît dans le monde → événement CharacterAdded
3. Le personnage agit : court, saute, tombe, atterrit...
                                          → événements StateChanged (très fréquents !)
4. Le personnage meurt → il est détruit
5. Un NOUVEAU personnage apparaît        → de nouveau CharacterAdded !
6. Le joueur quitte le jeu               → événement PlayerRemoving

Les trois personnages-clés de ce tuto :

  • player : le compte du joueur (son nom, son UserId…). Il ne meurt jamais.
  • character : le bonhomme 3D dans le monde (le Model). Il est détruit à chaque mort et recréé à chaque respawn !
  • humanoid : l’ordinateur de bord du personnage : il gère la santé, la marche, les sauts… et ses états.

Les états d’un Humanoid (Enum.HumanoidStateType)

ÉtatSignification
RunningLe personnage court au sol.
JumpingIl vient de sauter.
FreefallIl tombe dans les airs.
LandedIl vient d’atterrir.
ClimbingIl grimpe à une échelle.
SwimmingIl nage.
SeatedIl est assis (dans un siège).
DeadIl est mort !
PlatformStanding / PhysicsÉtats spéciaux (plateformes, physique).

C’est la même liste que celle utilisée dans le tutoriel de la zone anti-gravité (Jumping, Freefall, Landed) !

Voici le script commenté en français :

-- Récupérer le service des joueurs
local Players = game:GetService("Players")

-- Fonction appelée à chaque changement d'état du Humanoid
-- Paramètres : le joueur, l'ancien état, le nouvel état
local function onStateChanged(player, oldState, newState)
    -- Affiche dans la console : nom du joueur, ancien état -> nouvel état
    print(player.Name, "État changé :", oldState, "->", newState)
end

-- Fonction appelée à chaque apparition du personnage
local function onCharacterAdded(player, character)
    -- Attendre que le Humanoid existe (le personnage se construit progressivement !)
    local humanoid = character:WaitForChild("Humanoid")

    -- Connecter le suivi des changements d'états de CE personnage
    humanoid.StateChanged:Connect(function(oldState, newState)
        onStateChanged(player, oldState, newState)
    end)
end

-- Fonction appelée à chaque connexion d'un joueur
local function onPlayerAdded(player)
    print(player.Name .. " est connecté")

    -- Cas 1 : le personnage existe DÉJÀ (joueur connecté avant le script)
    if player.Character then
        task.spawn(onCharacterAdded, player, player.Character)
    end

    -- Cas 2 : chaque apparition/réapparition FUTURE (mort, respawn...)
    player.CharacterAdded:Connect(function(character)
        onCharacterAdded(player, player.Character and character)
    end)
end

-- Nouveaux joueurs : événement pour chaque joueur qui se connecte
Players.PlayerAdded:Connect(onPlayerAdded)

-- Joueurs déjà connectés AVANT l'exécution du script
for _, player in ipairs(Players:GetPlayers()) do
    task.spawn(onPlayerAdded, player)
end

Explications Détaillées : les 4 fonctions du script

Le script est construit comme des poupées russes : chaque fonction appelle la suivante !

onPlayerAdded ──► onCharacterAdded ──► onStateChanged
     (1)                (2)                  (3)

1. onStateChanged : l’espion des états

local function onStateChanged(player, oldState, newState)
    print(player.Name, "État changé :", oldState, "->", newState)
end
  • oldState : l’état avant le changement (ex: Enum.HumanoidStateType.Freefall).
  • newState : le nouvel état (ex: Enum.HumanoidStateType.Landed).
  • Rappel des tutoriels précédents : print(...) avec plusieurs valeurs séparées par des virgules affiche tout sur une ligne (pas besoin de concaténer avec .. !).

2. onCharacterAdded : l’espion des personnages

local function onCharacterAdded(player, character)
    local humanoid = character:WaitForChild("Humanoid")
    humanoid.StateChanged:Connect(function(oldState, newState)
        onStateChanged(player, oldState, newState)
    end)
end
  • WaitForChild("Humanoid") : quand le personnage apparaît, il se construit pièce par pièce (jambes, torse, tête, Humanoid…). Si on cherche le Humanoid trop tôt, il n’existe pas encore → erreur ! WaitForChild attend qu’il soit prêt. (Rappel : c’est pareil que WaitForChild("HumanoidRootPart") dans le tutoriel des checkpoints !)
  • On connecte StateChanged pour ce personnage précis. Quand il mourra, ce personnage sera détruit — et sa connexion avec lui ! C’est pour ça qu’il faut reconnecter StateChanged à chaque nouveau personnage (d’où la fonction !).

3. onPlayerAdded : l’espion des joueurs (avec ses DEUX pièges !)

local function onPlayerAdded(player)
    print(player.Name .. " est connecté")

    -- Cas 1 : le personnage existe DÉJÀ
    if player.Character then
        task.spawn(onCharacterAdded, player, player.Character)
    end

    -- Cas 2 : apparitions futures
    player.CharacterAdded:Connect(function(character)
        onCharacterAdded(player, character)
    end)
end

Piège n°1 : le joueur est arrivé AVANT le script !

  • Le problème : PlayerAdded ne se déclenche que pour les joueurs qui arrivent après la connexion de l’événement. Si tu es déjà dans le jeu quand le script démarre (par exemple au lancement d’un test Studio), l’événement t’a raté !
  • La solution en deux temps :
    • Cas 1 : if player.Character then → le personnage existe déjà → on appelle directement onCharacterAdded.
    • Cas 2 : player.CharacterAdded:Connect(...) → on guette toutes les apparitions futures (respawn après mort !).

Piège n°2 : CharacterAdded ne se déclenche pas à la connexion… mais un peu après !

  • Quand un joueur se connecte, PlayerAdded se déclenche, puis Roblox crée son personnage (quelques millisecondes plus tard) → c’est ensuite que CharacterAdded se déclenche. C’est pour ça que le Cas 2 attrape aussi la toute première apparition !

Pourquoi task.spawn ?

  • task.spawn(fonction, arguments...) lance la fonction dans un thread séparé : le script n’attend pas qu’elle soit finie pour continuer.
  • Indispensable ici : onCharacterAdded commence par un WaitForChild… qui peut bloquer quelques instants. Sans task.spawn, un joueur « lent à charger » bloquerait le traitement des autres joueurs !

4. La boucle finale : rattraper tout le monde

Players.PlayerAdded:Connect(onPlayerAdded)

for _, player in ipairs(Players:GetPlayers()) do
    task.spawn(onPlayerAdded, player)
end
  • Players.PlayerAdded:Connect(onPlayerAdded) : tous les joueurs qui arriveront à partir de maintenant.
  • Players:GetPlayers() : la liste des joueurs déjà présents (rappel : c’est une liste numérotée, donc on utilise ipairs, comme pour les checkpoints et les blocs de la pyramide !).
  • La boucle appelle onPlayerAdded pour chacun : personne n’est oublié, même si le script a démarré en retard !

Le schéma complet :


Préparation et Test dans Roblox Studio

  1. Crée un Script dans ServerScriptService et colle le code corrigé.
  2. Ouvre la fenêtre Output (onglet View → Output) : c’est là que les print s’affichent.
  3. Lance le jeu en mode multi-joueurs (onglet Test → 2 Players) pour observer plusieurs joueurs !
  4. Expérimente et observe la console :
ActionCe que tu dois voir dans l’Output
Le jeu démarrepatrick est connecté (et celui du 2ème joueur !)
Tu marchesRien (rester en Running ne change pas l’état !)
Tu sautesJumping puis Freefall
Tu atterrisLanded puis Running
Tu montes une échelleClimbing
Tu tombes dans l’eauSwimming
Tu t’assois sur un siègeSeated
Tu te reset (menu Echap → Reset)Dead… puis re-connexion automatique de CharacterAdded !

Contrôle important : après ta mort, observe bien : onCharacterAdded est rappelé automatiquement pour ton nouveau personnage (le script continue d’afficher tes états, sans rien refaire !). C’est la preuve que le Cas 2 de onPlayerAdded fonctionne.


QCM : Teste ta compréhension !

Sur la vie du joueur :

  1. Dans quel ordre se passent les événements quand un joueur rejoint le jeu ?
    • a) PlayerAdded → CharacterAdded → puis les StateChanged.
    • b) CharacterAdded → PlayerAdded → StateChanged.
    • c) Tout en même temps.
  1. Quelle est la différence entre player et character ?
    • a) player est le compte (ne meurt jamais) ; character est le bonhomme 3D, détruit à chaque mort et recréé à chaque respawn.
    • b) Ce sont deux noms pour la même chose.
    • c) character est le compte du joueur.
  1. Que se passe-t-il pour la connexion StateChanged quand le personnage meurt et respawn ?
    • a) Elle est détruite avec l’ancien personnage ; onCharacterAdded reconnecte StateChanged sur le NOUVEAU Humanoid.
    • b) Elle reste active sur l’ancien personnage.
    • c) Roblox la reconnecte automatiquement tout seul.

Sur les états du Humanoid :

  1. Dans quel ordre les états s’enchaînent-ils quand tu sautes d’une plateforme ?
    • a) Jumping → Freefall → Landed → Running.
    • b) Running → Landed → Jumping.
    • c) Freefall → Jumping → Running.
  1. Pourquoi ne voit-on rien s’afficher quand on marche normalement pendant longtemps ?
    • a) Parce qu’on reste dans l’état Running : sans changement d’état, StateChanged ne se déclenche pas.
    • b) Parce que print est cassé.
    • c) Parce que la marche est trop rapide.
  1. Quels sont les paramètres reçus par la fonction connectée à StateChanged ?
    • a) L’ancien état et le nouvel état (oldState, newState).
    • b) Le joueur et le personnage.
    • c) Rien.

Sur les deux pièges :

  1. Pourquoi le script a-t-il besoin de la boucle for ... ipairs(Players:GetPlayers()) à la fin ?
    • a) Pour rattraper les joueurs déjà connectés AVANT le démarrage du script (l’événement PlayerAdded ne se déclenche pas pour eux).
    • b) Pour déconnecter les joueurs.
    • c) Pour afficher la liste des règles du jeu.
  1. Pourquoi utilise-t-on task.spawn(onCharacterAdded, player, player.Character) ?
    • a) Pour lancer la fonction dans un thread séparé et ne pas bloquer le script, car elle contient un WaitForChild qui peut attendre.
    • b) Pour aller plus vite que WaitForChild.
    • c) Parce que onCharacterAdded est interdite ailleurs.
  1. Pourquoi onCharacterAdded utilise-t-elle WaitForChild("Humanoid") ?
    • a) Parce que le personnage se construit pièce par pièce : au moment de l’événement, le Humanoid n’existe peut-être pas encore.
    • b) Pour faire joli.
    • c) Parce que le Humanoid n’existe jamais.
  1. Dans onPlayerAdded, à quoi sert le if player.Character then ?
    • a) À vérifier si le personnage existe DÉJÀ (le joueur était là avant le script) pour ne pas rater son suivi.
    • b) À créer un personnage.
    • c) À détruire le personnage.

Résumé des concepts clés

ConceptDescriptionExemple
PlayerAddedUn joueur se connecte au jeu.Players.PlayerAdded:Connect(onPlayerAdded)
CharacterAddedUn personnage apparaît (1ère fois OU respawn !).player.CharacterAdded:Connect(...)
StateChangedLe Humanoid change d’état (saut, chute, atterrissage…).humanoid.StateChanged:Connect(...)
Enum.HumanoidStateTypeLa liste des états possibles.Landed, Jumping, Freefall, Dead…
GetPlayers()Liste des joueurs déjà présents (rattrapage !).for _, player in ipairs(...) do
task.spawnLance une fonction sans bloquer le script.task.spawn(onPlayerAdded, player)
WaitForChild("Humanoid")Attend que le personnage soit prêt.Anti-erreur !
GetState()Lit l’état ACTUEL (sans attendre un changement).Utilisé dans la zone anti-gravité

Astuce : Garde ce script espion dans un coin de tes projets : dès qu’un comportement te semble mystérieux (pourquoi mon joueur ne saute plus ?), les print des états te diront exactement ce qui se passe dans la tête du Humanoid ! C’est l’outil de débogage parfait

Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Mes premiers pas de la programmation Lua avec Roblox

Objectif : Découvrir pas à pas les bases de la programmation Lua avec Roblox :

  • Les variables (texte et nombres)
  • Les propriétés d’objets (écran, texte, Part)
  • Les boucles (for et while)
  • Les tableaux (simples et doubles)
  • Le clonage et la destruction d’objets

Le fil du jeu : tu possèdes un écran géant (TableauPart) qui affiche des messages, et un cube magique (Part) qui va grandir, changer de couleur, devenir invisible… et même se multiplier en pyramide inversée !

Règle du jeu : à chaque étape, essaye d’écrire le code, teste, contrôle ce qui est affiché, puis réponds au QCM avant de passer à la suite !

Étape 0 : Préparer le terrain dans Roblox Studio

Avant de coder, construis la scène :

  1. Dans Workspace, crée un dossier nommé Folder.
  2. Dans ce dossier :
    • Une Part nommée TableauPart : aplatie comme un écran (ex: Size = Vector3.new(12, 8, 0.5)), avec Anchored = true.
    • Une Part nommée Part : un cube visible (Size = Vector3.new(4, 4, 4)), avec Anchored = true.
  3. Sur TableauPart, ajoute (clic droit → Insert Object) :
    • Un SurfaceGui (nomme-le SurfaceGui).
    • Dans le SurfaceGui, un TextBox (nomme-le TextBox) : étire-le sur tout l’écran avec Size = {1,0},{1,0}.
  4. Crée un Script dans ServerScriptService : c’est là que tu colleras le code, étape par étape.

En haut du script, ces lignes (elles ne changent plus jamais) :

-- Récupérer les objets de la scène
local folder = workspace:WaitForChild("Folder")
local tableauPart = folder:WaitForChild("TableauPart")
local surfaceGui = tableauPart:WaitForChild("SurfaceGui")
local screen = surfaceGui:WaitForChild("TextBox")  -- notre écran d'affichage
local part = folder:WaitForChild("Part")           -- notre cube magique

Convention : comme dans tous nos tutoriels, les noms de variables sont en anglais (screen, firstName…) et les commentaires en français.

Étape 1 : Afficher un message (les propriétés d’un objet)

But : écrire « Bonjour » sur l’écran géant.

Explication : un objet Roblox (comme notre TextBox) possède des propriétés : sa couleur, sa taille… et son texte (Text). Pour changer une propriété, on écrit objet.propriete = valeur.

-- ÉTAPE 1 : Affiche un Bonjour à l'écran
screen.Text = "Bonjour"

Teste ! Lance le jeu.
Contrôle : tu dois voir « Bonjour » s’afficher sur l’écran géant. Sinon, vérifie que ton TextBox s’appelle bien TextBox et que le script pointe vers le bon objet.

QCM :

  1. Comment change-t-on le texte affiché dans le TextBox ?
    • a) screen.Bonjour = Text
    • b) print("Bonjour")
    • c) screen.Text = "Bonjour"
  2. Text est… ?
    • a) Une propriété du TextBox.
    • b) Une boucle.
    • c) Un joueur.

Étape 2 : Les variables texte et la concaténation

But : afficher « Bonjour patrick ! » (avec ton prénom !).

Explication : une variable, c’est une boîte avec une étiquette qui contient une valeur. Pour assembler des morceaux de texte, on utilise .. (deux points) : c’est la concaténation.

-- ÉTAPE 2 : Renseigne une variable pour afficher ton prénom
task.wait(2)                       -- petite pause de 2 secondes
local firstName = "patrick"        -- remplace par TON prénom !
screen.Text = "Bonjour " .. firstName .. " !"

Teste ! Contrôle : l’écran affiche d’abord « Bonjour » (2 secondes), puis « Bonjour tonprénom ! ». Attention : il y a un espace dans "Bonjour "; essaie sans espace pour voir la différence !

QCM :

  1. Que fait "Bonjour " .. firstName ?
    • a) Il colle les deux textes bout à bout (concaténation).
    • b) Il les additionne comme des nombres.
    • c) Il supprime le premier texte.
  2. Que fait task.wait(2) ?
    • a) Il supprime 2 objets.
    • b) Il met le script en pause pendant 2 secondes.
    • c) Il écrit 2 fois le texte.

Étape 3 : Modifier les propriétés de l’écran et du texte

But : rendre l’écran coloré et le texte stylé.

Explication : on peut modifier plein de propriétés d’un objet : couleur de fond (BackgroundColor3), couleur du texte (TextColor3), bordure, taille du texte… Color3.fromRGB(rouge, vert, bleu) fabrique une couleur avec 3 nombres de 0 à 255.

-- ÉTAPE 3 : Modifie les propriétés de l'écran et du texte
screen.BackgroundColor3 = Color3.fromRGB(85, 255, 255)   -- fond cyan
sreen.TextColor3 = Color3.fromRGB(50, 50, 100)           -- gris bleu
screen.BorderMode = Enum.BorderMode.Inset               -- style de bordure
screen.BorderColor3 = Color3.fromRGB(255, 0, 0)         -- bordure rouge
screen.BorderSizePixel = 20                              -- épaisseur de la bordure
screen.TextScaled = true                                 -- texte agrandi automatiquement
screen.TextWrapped = true                               -- texte qui passe à la ligne
screen.TextXAlignment = Enum.TextXAlignment.Center      -- texte centré

Teste ! Contrôle : fond cyan, texte jaune, bordure rouge épaisse, texte centré et plus grand. Amuse-toi : change les 3 nombres de fromRGB et observe ! Que donne Color3.fromRGB(255, 0, 0) ? Et Color3.fromRGB(0, 0, 255) ?

QCM :

  1. Que représente les 3 nombres de Color3.fromRGB(...) ?
    • a) Les quantités de rouge, de vert et de bleu (de 0 à 255).
    • b) La taille de l’écran.
    • c) L’heure.
  2. Que fait screen.TextScaled = true ?
    • a) L’écran disparaît.
    • b) Le texte s’agrandit automatiquement pour remplir l’espace.
    • c) Le texte devient bleu.

Étape 4 : Les variables nombre

But : afficher ton âge à l’écran.

Explication : une variable peut contenir du texte ("patrick") ou un nombre (12). Attention au piège : pour coller un nombre à du texte, on utilise quand même .. !

-- ÉTAPE 4 : Renseigne ton âge dans une variable et affiche-le
task.wait(2)
local age = 12                          -- remplace par TON âge !
screen.Text = screen.Text .. "\nJ'ai " .. age .. " ans"

Nouveauté : \n = un saut de ligne dans le texte (le message s’écrit en dessous du précédent).

Teste ! Contrôle : le message « J’ai X ans » apparaît en dessous du message précédent. Si tout s’affiche sur une seule ligne, vérifie ton \n.

QCM :

  1. Que fait "\n" dans un texte ?
    • a) Il affiche un « n ».
    • b) Il efface le texte.
    • c) Il saute à la ligne suivante.
      Réponse : a)
  2. Peut-on coller un nombre à du texte avec .. ?
    • a) Oui, .. " ans" fonctionne avec une variable nombre.
    • b) Non, jamais.
    • c) Seulement le lundi.

Étape 5 : À toi de jouer — une variable de ton choix

But : afficher une information de ton choix (ex: « Fille » ou « Garçon », ou ta classe).

Explication : c’est le même principe qu’avant. À toi d’écrire le code sans copier ! Indice : crée une variable texte, puis concatène-la avec \n à la suite.

-- ÉTAPE 5 : Renseigne une information et affiche-la
task.wait(2)
local gender = "Garçon"                -- ou "Fille"... ou invente ta variable !
screen.Text = screen.Text .. "\n" .. gender

Teste ! Contrôle : ta nouvelle ligne s’affiche sous les précédentes, sans effacer le reste.

QCM :

  1. Que se passe-t-il si tu écris screen.Text = gender (sans concaténation) ?
    • a) Tout le texte précédent est remplacé par ta variable.
    • b) Ta variable s’ajoute à la fin.
    • c) Rien ne s’affiche.

Étape 6 : Les nombres et l’addition (ton inventaire)

But : imagine que tu récupères une épée, un laser et une lampe, et calcule le total des points de ton inventaire.

Explication : avec des variables nombre, on peut calculer : +, -, * (multiplié), / (divisé).

-- ÉTAPE 6 : Calcule le total des points de l'inventaire
task.wait(2)
local sword = 10        -- points de l'épée
local laser = 50        -- points du laser
local lamp = 2          -- points de la lampe
local total = sword + laser + lamp   -- le calcul !
screen.Text = screen.Text .. "\nTotal inventaire : " .. total

Teste ! Contrôle : l’écran affiche « Total inventaire : 62 ». Vérifie le calcul de tête : 10 + 50 + 2 = 62 ?

QCM :

  1. Que vaut total si sword = 10, laser = 50, lamp = 2 ?
    • a) 62
    • b) 10502
    • c) 60
  2. Que fait local total = sword + laser + lamp ?
    • a) Il colle les trois textes.
    • b) Il additionne les trois variables et range le résultat dans total.
    • c) Il supprime les variables.

Étape 7 : Un vrai calcul (le voyage en vaisseau)

But : ton vaisseau part avec un réservoir vide, tu achètes 100 litres, puis tu visites 3 villes qui consomment chacune 30 litres. Combien reste-t-il ?

Explication : on peut combiner plusieurs opérations, et même réutiliser une variable avec =. Les parenthèses ( ) forcent le calcul à se faire en priorité.

-- ÉTAPE 7 : Calcule le carburant restant
task.wait(2)
local fuelTank = 0                          -- réservoir vide au départ
local fuelBought = 100                      -- litres achetés
local trips = 3                             -- nombre de déplacements
local fuelPerTrip = 30                      -- litres consommés par déplacement

fuelTank = fuelTank + fuelBought - (trips * fuelPerTrip)   -- le calcul !

screen.Text = screen.Text .. "\nReste dans mon réservoir : " .. fuelTank

Teste ! Contrôle : l’écran affiche 10 (100 – 90 = 10). Enlève les parenthèses autour de trips * fuelPerTrip : le résultat est-il le même ? Pourquoi ? (Indice : sans parenthèses, le calcul se fait de gauche à droite !)

QCM :

  1. Pourquoi met-on des parenthèses autour de trips * fuelPerTrip ?
    • a) Pour que la multiplication se fasse avant la soustraction.
    • b) Pour décorer le code.
    • c) Pour afficher le texte.

Étape 8 : La boucle for (compteur et décompte)

But : afficher un compteur de 1 à 10, puis un décompte de 10 à 1.

Explication : une boucle for répète un bloc de code. for i = 1, 10 do signifie : « pour i allant de 1 à 10 ». La variable i change à chaque tour ! Et for i = 10, 1, -1 compte à l’envers grâce au pas de -1.

-- ÉTAPE 8a : Compteur de 1 à 10
task.wait(2)
local baseText = screen.Text .. "\n"
for i = 1, 10 do
    screen.Text = baseText .. "Attente : " .. i .. " seconde(s)"
    task.wait(0.3)
end

-- ÉTAPE 8b : Décompte de 10 à 1
for i = 10, 1, -1 do                       -- pas de -1 : on compte à l'envers !
    screen.Text = baseText .. "Attente : " .. i .. " seconde(s)"
    task.wait(0.3)
end

Teste ! Contrôle : le chiffre affiché monte 1, 2, 3… 10 puis redescend 10, 9… 1. Remarque : on a préparé baseText avant la boucle pour ne pas coller 10 fois la phrase !

QCM :

  1. Combien de fois le code dans for i = 1, 10 do s’exécute-t-il ?
    • a) 10 fois.
    • b) 1 fois.
    • c) 11 fois.
  2. Que fait le -1 dans for i = 10, 1, -1 do ?
    • a) Il fait diminuer i de 1 à chaque tour (décompte).
    • b) Il répète la boucle à l’infini.
    • c) Il affiche -1 à l’écran.

Étape 9 : Les tableaux (ton inventaire en liste)

But : ranger les valeurs de l’inventaire dans un tableau et les afficher une par une.

Explication : un tableau ({ }) range plusieurs valeurs dans une seule variable. pairs parcourt le tableau et donne l’index (i, la position) et la valeur (item).

-- ÉTAPE 9 : Affiche l'inventaire avec une boucle
task.wait(2)
screen.Text = baseText .. "\n"
local inventory = {sword, laser, lamp}      -- tableau avec les 3 valeurs

for i, item in pairs(inventory) do
    screen.Text = screen.Text .. "Objet " .. i .. " : " .. item .. "\n"
end

Teste ! Contrôle : l’écran affiche 3 lignes : « Objet 1 : 10 », « Objet 2 : 50 », « Objet 3 : 2 ».

QCM :

  1. Que vaut inventory[1] ?
    • a) Le nombre de cases.
    • b) La première valeur du tableau (10, la valeur de sword).
    • c) Une erreur.
  2. Dans for i, item in pairs(inventory) do, que vaut item ?
    • a) La valeur de la case courante du tableau.
    • b) Le nom du tableau.
    • c) Toujours 1.

Étape 10 : Le tableau double (objet ET valeur)

But : afficher le nom de chaque objet et sa valeur. Un tableau dans un tableau !

Explication : chaque case d’un tableau peut contenir… un autre tableau ! {{"sword", 10}, {"laser", 50}}. Pour lire la case 1 d’un sous-tableau : item[1].

-- ÉTAPE 10 : Affiche les noms et les valeurs de l'inventaire
task.wait(2)
screen.Text = baseText .. "\n"
inventory = {{"sword", sword}, {"laser", laser}, {"lamp", lamp}}   -- tableau double

for i, item in pairs(inventory) do
    screen.Text = screen.Text .. i .. ") " .. item[1] .. " : " .. item[2] .. "\n"
end
baseText = screen.Text     -- on mémorise pour la suite

Teste ! Contrôle : l’écran affiche : « 1) sword : 10 », « 2) laser : 50 », « 3) lamp : 2 ». Le nom ET la valeur !

QCM :

  1. Que vaut item[2] dans la boucle ?
    • a) Le nom de l’objet.
    • b) La position dans le tableau.
    • c) La valeur (le nombre de points) de l’objet courant.
  2. Comment s’appelle un tableau qui contient d’autres tableaux ?
    • a) Un tableau double (imbriqué).
    • b) Une boucle.
    • c) Une propriété.

Étape 11 : La boucle while (la jauge d’énergie)

But : faire monter une jauge d’énergie jusqu’à 100, barre par barre.

Explication : la boucle while (« tant que ») répète un code tant qu’une condition est vraie. Ici : tant que l’énergie est inférieure au maximum, on ajoute 10.

-- ÉTAPE 11 : Charge la jauge d'énergie
task.wait(2)
local energy = 10                 -- énergie de départ
local maxEnergy = 100             -- énergie maximale
screen.Text = screen.Text .. "Energie : "
while energy < maxEnergy do       -- TANT QUE energy < 100
    energy = energy + 10          -- ajouter 10
    screen.Text = screen.Text .. "▄"    -- une barre de plus (Alt+220)
    task.wait(0.3)
end

Teste ! Contrôle : les barres « ▄ » s’ajoutent une par une, puis s’arrêtent d’elles-mêmes quand l’énergie atteint 100. La boucle s’est toute seule arrêtée : c’est la condition qui l’a décidée !

QCM :

  1. Quand la boucle while s’arrête-t-elle ?
    • a) Quand la condition devient fausse (ici, quand energy atteint 100).
    • b) Après 10 secondes.
    • c) Jamais.
  2. Quelle est la différence avec la boucle for ?
    • a) Aucune.
    • b) for répète un nombre de fois connu ; while répète jusqu’à une condition.
    • c) while ne marche que sur les écrans.

Étape 12 : Transformer le cube (taille et couleur)

But : faire grandir, rétrécir, changer la couleur du cube magique… en boucle !

Explication : la Size d’une Part est un Vector3 (largeur, hauteur, profondeur) : on peut l’additionner ou la soustraire avec += et -=. La couleur est faite avec Color3.fromRGB : si les 3 nombres sont égaux, on obtient un gris (0 = noir, 255 = blanc) !

-- ÉTAPE 12a : Fais grandir le cube, puis rétrécir
task.wait(2)
for i = 1, 10 do
    part.Size += Vector3.new(i, i, i)    -- le cube grossit de plus en plus fort !
    task.wait(0.3)
end
for i = 1, 10 do
    part.Size -= Vector3.new(i, i, i)   -- et il rétrécit
    task.wait(0.3)
end

-- ÉTAPE 12b : Du noir au blanc (le gris)
for i = 0, 255 do
    part.Color = Color3.fromRGB(i, i, i)    -- gris qui s'éclaircit
    task.wait(0.01)
end

-- ÉTAPE 12c : Une couleur vivante (du noir au rouge)
for i = 0, 255 do
    part.Color = Color3.fromRGB(i, 160, 160)   -- rouge qui monte, vert/bleu fixes
    task.wait(0.01)
end

Teste ! Contrôle : le cube grossit de plus en plus vite (car i augmente à chaque tour !), rétrécit, passe du noir au gris clair, puis prend une teinte rouge. Modifie le 160 : que se passe-t-il avec Color3.fromRGB(i, 0, 0) ?

QCM :

  1. Pourquoi le cube grandit-il de plus en plus vite dans la première boucle ?
    • a) Parce que task.wait raccourcit.
    • b) Parce que i augmente à chaque tour, et on ajoute Vector3.new(i, i, i).
    • c) Parce qu’il est magique.
  2. Que donne Color3.fromRGB(100, 100, 100) ?
    • a) Un gris moyen.
    • b) Du rouge pur.
    • c) Du blanc pur.

Étape 13 : Invisible puis visible (la transparence)

But : faire disparaître le cube puis le faire réapparaître.

Explication : la propriété Transparency va de 0 (opaque) à 1 (invisible). Deux boucles while parfaites pour ça !

-- ÉTAPE 13a : Rend le cube transparent (disparition)
while part.Transparency < 1 do
    part.Transparency += 0.1    -- un peu plus transparent...
    task.wait(0.1)
end

-- ÉTAPE 13b : Rend-le opaque (réapparition)
task.wait(2)
while part.Transparency > 0 do
    part.Transparency -= 0.1    -- un peu plus opaque...
    task.wait(0.1)
end

Teste ! Contrôle : le cube s’efface progressivement puis réapparaît tout doucement, comme un fantôme !

QCM :

  1. Quand une Part est-elle complètement invisible ?
    • a) Quand Transparency = 0.
    • b) Quand Transparency = 1.
    • c) Quand Transparency = 255.
  2. Que fait part.Transparency += 0.1 ?
    • a) Il ajoute 0.1 à la transparence actuelle (avec +=).
    • b) Il remplace la transparence par 0.1.
    • c) Il change la taille.

Étape 14 : Le clonage : construis une pyramide inversée !

But : cloner le cube 10 fois pour construire une pyramide inversée : chaque nouvel étage est plus large que le précédent, posé au-dessus.

Explication : part:Clone() crée une copie exacte de la Part. On la modifie ensuite (position, taille) puis on l’attache au dossier avec .Parent. Sans .Parent, l’objet reste invisible ! Attention aussi : chaque copie doit avoir un nom différent (« Part1 », « Part2″…) pour pouvoir la retrouver ensuite.

-- ÉTAPE 14 : Construis la pyramide inversée
task.wait(3)
screen.Text = baseText                     -- on nettoie l'écran
for i = 1, 10 do
    local newPart = part:Clone()           -- une copie du cube
    newPart.Position += Vector3.new(0, newPart.Size.Y * i, 0)   -- posée plus haut à chaque fois
    newPart.Size += Vector3.new(i * 3, 0, i * 3)                -- de plus en plus large !
    newPart.Parent = folder               -- ATTACHE la copie au monde
    newPart.Name = "Part" .. i             -- nom unique : "Part1", "Part2"...
    task.wait(1)
end

Teste ! Contrôle : un nouveau bloc apparaît chaque seconde, posé au-dessus du précédent, et de plus en plus large : la pyramide pointe vers le bas ! Regarde dans l’Explorer : les Part1 à Part10 sont bien dans Folder.

QCM :

  1. Que fait part:Clone() ?
    • a) Il crée une copie identique de la Part.
    • b) Il supprime la Part.
    • c) Il change la couleur.
  2. Que se passe-t-il si on oublie newPart.Parent = folder ?
    • a) Le jeu plante.
    • b) La copie existe en mémoire mais n’apparaît pas dans le jeu.
    • c) Rien, c’est la même chose.
  3. Pourquoi newPart.Name = "Part" .. i ?
    • a) Pour donner un nom unique à chaque copie et la retrouver plus tard.
    • b) Pour la détruire.
    • c) Pour changer sa couleur.

Étape 15 : La destruction : démolis la pyramide à l’envers !

But : détruire les blocs du haut vers le bas (ordre inverse).

Explication : :Destroy() supprime définitivement un objet. Pour retrouver chaque bloc par son nom, on utilise FindFirstChild : "Part" .. i fabrique le nom à chercher !

-- ÉTAPE 15 : Détruis les blocs dans l'ordre inverse
task.wait(3)
for i = 10, 1, -1 do                                   -- du dernier au premier !
    local block = folder:FindFirstChild("Part" .. i)   -- retrouve le bloc par son nom
    if block then
        block:Destroy()                                -- ... et détruis-le !
    end
    task.wait(1)
end
task.wait(3)

Teste ! Contrôle : les blocs disparaissent un par un, du haut vers le bas. À la fin, l’Explorer ne contient plus que TableauPart et Part !

  1. Que fait folder:FindFirstChild("Part" .. i) ?
    • a) Il détruit la Part.
    • b) Il cherche dans le dossier l’objet nommé « Part1 », « Part2″… selon la valeur de i.
    • c) Il crée une Part.
  2. Pourquoi la boucle va de 10 à 1 avec un pas de -1 ?
    • a) Pour aller plus vite.
    • b) Par hasard.
    • c) Pour détruire les blocs dans l’ordre inverse de leur création (du haut vers le bas).
  3. Pourquoi vérifie-t-on if block then avant de détruire ?
    • a) Pour éviter une erreur si le bloc n’existe pas (il retourne nil).
    • b) Pour changer sa couleur.
    • c) Ce n’est pas utile.

Résumé des concepts découverts

ConceptDescriptionExemple
PropriétésLes réglages d’un objet.screen.Text, part.Color
Variable texte / nombreUne boîte étiquetée contenant une valeur.local firstName = "patrick"
Concaténation ..Coller des textes (et nombres) ensemble."J'ai " .. age
\nSaut de ligne dans un texte.screen.Text .. "\n"
Color3.fromRGBFabriquer une couleur (rouge, vert, bleu : 0-255).Color3.fromRGB(85, 255, 255)
Calculs‘+, -, *, / et les parenthèses.fuelTank + fuelBought - (trips * fuelPerTrip)
Boucle forRépéter un nombre de fois connu (avec pas négatif pour décompter).for i = 10, 1, -1 do
Boucle whileRépéter tant qu’une condition est vraie.while energy < maxEnergy do
TableauUne liste de valeurs.{sword, laser, lamp}
Tableau doubleUn tableau de tableaux (nom + valeur).{{"sword", 10}}
pairsParcourir un tableau (index + valeur).for i, item in pairs(inventory) do
‘+= et -=Ajouter / retirer à une valeur.part.Size += Vector3.new(i, i, i)
Clone()Copier un objet.part:Clone()
Destroy()Supprimer un objet.block:Destroy()
FindFirstChildRetrouver un objet par son nom.folder:FindFirstChild("Part" .. i)

Défi final :

  1. Personnalise l’écran : affiche ton prénom, ton âge, ton inventaire imaginé (5 objets dans un tableau double !).
  2. Crée un escalier : au lieu d’une pyramide inversée, fais des marches qui montent en décalant aussi sur l’axe X (Vector3.new(i * 5, i * 2, 0)).

Félicitations ! Tu viens de découvrir presque toutes les bases de la programmation Lua : variables, conditions, boucles, tableaux, objets et propriétés. Tous les grands jeux Roblox sont construits avec ces mêmes briques !

Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Afficher un Message de Chat lors d’une Collision avec une Part

Apprendre à afficher une bulle de chat au-dessus de la tête du joueur quand il touche une Part, avec un cooldown pour éviter le spam. Ce tutoriel explique :

  1. Le service Chat et la fonction Chat:Chat().
  2. Le tableau de cooldown (avec une nouveauté : ici la clé est le personnage, pas le joueur !).
  3. La détection de collision avec Touched.
  4. Au passage : corriger les noms de variables pour qu’ils soient en anglais (bonne pratique de programmeur !).

Introduction : À quoi sert ce script ?

Dans beaucoup de jeux, on veut féliciter le joueur quand il atteint une zone :

  • « Bien joué ! » quand il touche la ligne d’arrivée.
  • « Bravo ! » quand il trouve un secret.

Dans ce script, quand un joueur touche la Part (la ligne d’arrivée par exemple), une bulle verte s’affiche au-dessus de sa tête avec le message « bien jouer ».

Mais il y a un piège ! L’événement Touched se déclenche des dizaines de fois par seconde tant que le joueur touche la Part (une fois par partie du corps qui la touche : pied, jambe, bras…). Sans protection, on verrait des dizaines de bulles de chat en même temps ! C’est pour cela qu’on utilise un cooldown de 10 secondes.

Préparation dans Roblox Studio

  1. Créer la Part :
    • Dans Workspace, ajoute une Part (ex: une ligne d’arrivée plate et large).
    • Positionne-la au sol dans ton niveau.
  2. Ajouter le script DANS la Part (important !) :
    • Clic droit sur la Part → Insert Object → Script.
    • Le script devient enfant de la Part (c’est ce que script.Parent utilise).
  3. Coller le code corrigé ci-dessus dans ce script.
  4. Tester :
    • Lance le jeu et marche sur la Part :
      • Une bulle verte « Bien joué ! » apparaît au-dessus de ta tête.
    • Reste sur la Part ou re-touche-la : rien ne se passe pendant 10 secondes (le cooldown !).
    • Après 10 secondes, retouche-la : la bulle réapparaît !

Script commenté :

-- Récupérer la Part parente du script (la Part qui déclenche le message)
local wellDonePart = script.Parent

-- Récupérer le service Chat pour afficher des bulles de dialogue
local chatService = game:GetService("Chat")

-- Tableau de cooldown : [character] = true si le personnage est en cooldown
local cooldowns = {}

-- Détecter quand quelque chose touche la Part
wellDonePart.Touched:Connect(function(hit)
    -- Récupérer le personnage (Model) qui touche la Part
    local character = hit.Parent
    if not character then return end

    -- VÉRIFIER LE COOLDOWN : si ce personnage a déjà déclenché le message, on arrête
    if cooldowns[character] then return end

    -- Trouver la tête du personnage (là où la bulle de chat apparaît)
    local head = character:FindFirstChild("Head")
    if not head then return end

    -- Afficher une bulle de chat verte au-dessus de la tête avec le message
    chatService:Chat(head, "Bien joué !", Enum.ChatColor.Green)

    -- ACTIVER LE COOLDOWN pour ce personnage pendant 10 secondes
    cooldowns[character] = true
    task.wait(10)   -- Attendre 10 secondes
    cooldowns[character] = false  -- Réautoriser le message
end)

Explications Détaillées

1. script.Parent : le script est DANS la Part !

local wellDonePart = script.Parent
  • Grande nouveauté par rapport aux tutoriels précédents : ici, le script est placé directement dans la Part (dans l’arborescence : Part > Script), pas dans ServerScriptService.
  • script.Parent = l’objet parent du script, c’est-à-dire la Part elle-même !
  • Avantage : le script fonctionne n’importe où où tu copies la Part. Tu peux dupliquer la Part, le script fonctionne tout seul dans chaque copie !

2. Le service Chat et la fonction Chat:Chat()

local chatService = game:GetService("Chat")
chatService:Chat(head, "Bien joué !", Enum.ChatColor.Green)
  • game:GetService("Chat") : service Roblox qui gère les bulles de dialogue.
  • chatService:Chat(...) : affiche une bulle de message au-dessus d’une partie du corps. Elle prend 3 paramètres :
ParamètreValeur iciRôle
1. La partieheadLà où la bulle apparaît (au-dessus de la tête du personnage).
2. Le message"Bien joué !"Le texte affiché dans la bulle.
3. La couleurEnum.ChatColor.GreenLa bulle est verte (il existe aussi Blue, Red, White…).
  • Différence avec le chat normal : Chat:Chat() ne fait qu’afficher une bulle au-dessus du personnage (comme quand un joueur parle dans le jeu). Ce n’est pas un message envoyé dans la fenêtre de chat.

3. Le tableau de cooldown… avec le PERSONNAGE comme clé !

local cooldowns = {}
if cooldowns[character] then return end
cooldowns[character] = true
task.wait(10)
cooldowns[character] = false
  • Rappel des tutoriels précédents : un cooldown, c’est un temps d’attente pour éviter qu’une action se déclenche trop souvent.
  • La nouveauté ici : la clé du tableau n’est ni player, ni userId, mais character (le Model du personnage) !
  • Pourquoi character et pas player ?
    • On n’a pas besoin du joueur : on a déjà son personnage (hit.Parent), et c’est lui qu’on vérifie.
    • Ça économise une étape (pas besoin de Players:GetPlayerFromCharacter).
  • ⚠️ Mais attention, il y a un piège avec ce choix ! Regarde la fin du cooldown :luaCopiercooldowns[character] = false -- Ici on met falseDans les tutoriels précédents, on mettait nil (on supprimait l’entrée). Ici on met false… mais la vérification if cooldowns[character] fonctionne quand même ! Pourquoi ? Parce que la condition vérifie simplement que la valeur n’est ni false ni nil. Les deux solutions marchent :
    • cooldowns[character] = nil → l’entrée n’existe plus (table plus propre).
    • cooldowns[character] = false → l’entrée existe mais vaut false (accepté par le if).
  • Autre piège subtil : quand le joueur meurt et respawn, son personnage est détruit et un nouveau Model est créé. L’ancienne entrée cooldowns[ancienCharacter] reste dans le tableau avec false… mais elle ne gêne pas (elle ne sera plus jamais utilisée). Dans un vrai jeu, on nettoierait avec Players.PlayerRemoving comme dans les tutoriels précédents !

4. La détection de collision avec Touched

wellDonePart.Touched:Connect(function(hit)
    local character = hit.Parent
    if not character then return end
  • hit : la partie du corps (ou objet) qui touche la Part.
  • hit.Parent : le Model du personnage (si un pied touche la Part, hit = le pied, hit.Parent = le personnage entier).
  • if not character then return end : si hit n’a pas de parent (impossible normalement), on arrête.
  • ⚠️ Petit trou dans le filet : ce script vérifie seulement si hit.Parent existe. Une Part libre qui roule sur la ligne d’arrivée a aussi un Parent… mais elle n’a pas de Head, donc la vérification if not head then return end la rejette ! C’est la recherche de la Head qui filtre les vrais joueurs.

5. Pourquoi chercher la Head ?

local head = character:FindFirstChild("Head")
if not head then return end
  • La bulle de chat apparaît au-dessus de la partie qu’on donne en paramètre.
  • On donne la tête pour que la bulle apparaisse au-dessus de la tête du joueur (comme dans le tutoriel des messages cliquables !).
  • Si le personnage n’a pas de tête (ex: un objet qui touche la Part), on arrête : pas de tête, pas de bulle !

QCM : Teste ta compréhension !

Sur le service Chat :

  1. Que fait chatService:Chat(head, "Bien joué !", Enum.ChatColor.Green) ?
    • a) Affiche une bulle verte « Bien joué ! » au-dessus de la tête du personnage.
    • b) Envoie un message dans la fenêtre de chat globale.
    • c) Change la couleur du personnage en vert.
  1. À quoi sert le paramètre Enum.ChatColor.Green ?
    • a) À définir la couleur de la bulle de chat.
    • b) À définir la couleur de la Part.
    • c) À définir la vitesse du personnage.

Sur script.Parent :

  1. Que vaut script.Parent si le script est placé DANS une Part ?
    • a) La Part qui contient le script.
    • b) Le Workspace.
    • c) Le joueur.
  1. Quel est l’avantage de placer le script dans la Part avec script.Parent ?
    • a) On peut dupliquer la Part, et chaque copie fonctionne toute seule.
    • b) Le script va plus vite.
    • c) Le message est plus long.

Sur le cooldown :

  1. Pourquoi un cooldown est-il indispensable ici ?
    • a) L’événement Touched se déclenche des dizaines de fois par seconde, on aurait des dizaines de bulles.
    • b) Pour économiser la batterie.
    • c) Pour changer la couleur du message.
  1. Quelle est la clé du tableau cooldowns dans ce script ?
    • a) Le personnage (character).
    • b) Le pseudo du joueur.
    • c) Le nom de la Part.
  1. Que se passe-t-il pendant les 10 secondes de cooldown si le joueur re-touche la Part ?
    • a) La fonction s’arrête immédiatement à if cooldowns[character] then return end, aucune bulle ne s’affiche.
    • b) La bulle s’affiche quand même.
    • c) Le jeu plante.
  1. Ce script remet le cooldown avec cooldowns[character] = false. Quelle autre écriture était aussi possible (comme dans les tutoriels précédents) ?
    • a) cooldowns[character] = nil (supprimer l’entrée du tableau).
    • b) cooldowns[character] = "stop".
    • c) cooldowns = {} (vider tout le tableau, ce qui supprimerait les cooldowns des AUTRES joueurs !).

Sur la détection de collision :

  1. Que vaut hit quand un joueur touche la Part avec son pied ?
    • a) Le pied (la Part du corps qui touche).
    • b) Le personnage entier.
    • c) Le joueur.
  1. Pourquoi la vérification if not head then return end est-elle utile, en plus de chercher la tête ?
    • a) Elle filtre les objets qui ne sont pas des personnages (une Part qui roule sur la ligne d’arrivée n’a pas de Head).
    • b) Elle accélère le script.
    • c) Elle change la couleur de la bulle.

Idées pour aller plus loin

1. Personnaliser le message avec un attribut (rappel du tutoriel des messages cliquables !)

  • Objectif : Chaque copie de la Part affiche son propre message, sans modifier le script.
  • Comment faire ?
    • Ajoute un attribut Message (type String) sur la Part, puis :luaCopierlocal message = wellDonePart:GetAttribute("Message") or "Bien joué !" chatService:Chat(head, message, Enum.ChatColor.Green)
    • Duplique la Part, change juste l’attribut : "Niveau 1 terminé !", "Zone secrète trouvée !"…

2. Créer un mini-jeu : une course avec score

  • Objectif : La Part donne des points à chaque passage.
  • Comment faire ?luaCopierlocal Players = game:GetService("Players") local player = Players:GetPlayerFromCharacter(character) if player then local leaderstats = player:FindFirstChild("leaderstats") if leaderstats then leaderstats.Score.Value += 1 end end

3. Ajouter un effet visuel et sonore (rappel du tutoriel des particules !)

  • Comment faire ?luaCopierlocal emitter = wellDonePart:FindFirstChildOfClass("ParticleEmitter") local sound = wellDonePart:FindFirstChildOfClass("Sound") if emitter then emitter.Enabled = true end if sound then sound:Play() end task.delay(2, function() if emitter then emitter.Enabled = false end end)

4. Nettoyer le tableau quand le joueur quitte (bonne pratique !)

  • Objectif : Éviter que le tableau cooldowns garde des entrées inutiles.
  • Comment faire ? Comme dans le tutoriel flip/flop, utilise un cooldown basé sur le joueur (player.UserId comme clé, comme le tutoriel des checkpoints) + un nettoyage :luaCopierlocal Players = game:GetService("Players") local player = Players:GetPlayerFromCharacter(character) if not player then return end if cooldowns[player.UserId] then return end cooldowns[player.UserId] = true task.delay(10, function() cooldowns[player.UserId] = nil end) Players.PlayerRemoving:Connect(function(leavingPlayer) cooldowns[leavingPlayer.UserId] = nil end)
    • Bonus : avec task.delay au lieu de task.wait, la fonction Touched ne reste pas bloquée 10 secondes et peut traiter d’autres joueurs !

Résumé des concepts clés

ConceptDescriptionExemple
script.ParentL’objet qui contient le script (ici, la Part).local wellDonePart = script.Parent
game:GetService("Chat")Service pour afficher des bulles de dialogue.chatService:Chat(head, message, color)
Chat:Chat()Affiche une bulle au-dessus d’une partie, avec un message et une couleur.Enum.ChatColor.Green
TouchedÉvénement déclenché à chaque contact avec la Part (très fréquent !).wellDonePart.Touched:Connect(...)
hit.ParentLe personnage (Model) qui touche la Part.local character = hit.Parent
FindFirstChild("Head")Cherche la tête (et filtre les non-personnages).if not head then return end
Tableau de cooldown[character] = true pendant 10 secondes pour bloquer le spam.if cooldowns[character] then return end
task.wait(10)Attend 10 secondes avant de réautoriser le message.À la fin du cooldown
Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Déplacer des Ascenseurs avec TweenService

Apprendre à créer des ascenseurs sans boucle Heartbeat, grâce au service TweenService ! Ce tutoriel est progressif :

  1. Script 1 : animer un seul ascenseur avec un Tween.
  2. Script 2 : généraliser avec une fonction createElevator pour gérer plusieurs ascenseurs différents avec le même code.

Tu vas découvrir que parfois, Roblox propose un mécanisme intégré qui remplace des dizaines de lignes de code écrites à la main !

Introduction : C’est quoi un Tween ?

Un Tween, c’est une animation automatique : tu dis à Roblox :

  • où aller (la position cible),
  • en combien de temps (la durée),
  • comment (le style : fluide, rebondissant, linéaire…),

…et Roblox fait tout le travail, frame par frame, à ta place !

Comparaison avec le tutoriel précédent :

TechniqueTutoriel précédent (Heartbeat)Ce tutoriel (TweenService)
Qui déplace la Part ?Ton code, à chaque frame.Roblox, automatiquement.
Lignes de codeBeaucoup (calculs, direction, elapsed…).Très peu !
Vitesse fluide / accélérationÀ coder soi-même.Intégrée (EasingStyle) !
Transport du joueurOui (avec AssemblyLinearVelocity).Oui (Roblox gère la vélocité du Tween).

SCRIPT 1 : Animer un seul ascenseur

Préparation dans Roblox Studio

  1. Dans Workspace, crée un dossier MovementsFolder.
  2. Ajoute deux Part nommées Part et Part02 (plates et larges : Size = Vector3.new(20, 1, 20)).
  3. Crée un Script dans ServerScriptService et colle le Script 1. Teste :
    • L’ascenseur 1 monte en 15 s, redescend, remonte… à l’infini, en douceur.
    • Monte dessus : tu es transporté (TweenService gère la physique pour toi !).
  4. Remplace par le Script 2. Teste :
    • L’ascenseur 1 monte (10 s), l’ascenseur 2 recule de 200 studs sur l’axe X.
    • Les deux animations tournent en même temps, chacune dans son sens.

Voici le premier script :

-- Récupérer le dossier et les deux ascenseurs
local folder = workspace:WaitForChild("MovementsFolder")
local elevator01 = folder:WaitForChild("Part")
local elevator02 = folder:WaitForChild("Part02")

-- Récupérer le service d'animations TweenService
local tweenService = game:GetService("TweenService")

-- Décrire les règles de l'animation avec TweenInfo
local tweenInfo = TweenInfo.new(
    15,                          -- Durée : 15 secondes pour monter
    Enum.EasingStyle.Sine,       -- Style : démarre doucement, accélère, ralentit (comme un vrai ascenseur !)
    Enum.EasingDirection.InOut,  -- Direction : fluide au début ET à la fin
    -1,                          -- Répétition : -1 = INFINIE (jamais arrêtée)
    true                         -- Reverser : à l'arrivée, l'animation REPART EN ARRIÈRE (descente !)
)

-- Créer le Tween : on anime elevator01 vers une position 100 studs plus haut
local tween = tweenService:Create(
    elevator01,                   -- Objet à animer
    tweenInfo,                    -- Les règles de l'animation
    {                             -- Propriétés à modifier (la destination) :
        Position = Vector3.new(  -- La nouvelle Position = position actuelle + 100 en Y
            elevator01.Position.X,      -- X : inchangé
            elevator01.Position.Y + 100, -- Y : +100 studs (on monte !)
            elevator01.Position.Z       -- Z : inchangé
        )
    }
)

-- Lancer l'animation !
tween:Play()

Explications du Script 1

1. TweenInfo.new : les règles du jeu

TweenInfo.new accepte 5 paramètres (dans cet ordre, attention !) :

ParamètreValeur iciSignification
1. Durée15Le trajet dure 15 secondes.
2. StyleEnum.EasingStyle.SineL’ascenseur démarre doucement et freine à l’arrivée (pas de secousse !).
3. DirectionEnum.EasingDirection.InOutL’effet de style s’applique au début ET à la fin.
4. Répétitions‘-1‘-1 = répétition à l’infini. (0 = une fois, 2 = trois allers…)
5. ReversertrueAprès chaque montée, l’animation rejoue en sens inverse : c’est la descente automatique !

2. Les styles d’animation (EasingStyle) — à expérimenter !

StyleEffet
LinearVitesse constante, comme un tapis roulant.
SineDoux au départ et à l’arrivée — parfait pour un ascenseur.
Quad, CubicAccélération de plus en plus marquée.
BounceRebondit à l’arrivée comme un ballon !
ElasticÉlastique, dépasse la cible puis revient.
BackPart légèrement en arrière avant d’avancer.

3. La cible : une table de propriétés

{Position = Vector3.new(...)}
  • Le 3ème argument de Create est une table qui contient les propriétés à animer et leur valeur finale.
  • Ici, on anime seulement la Position, mais on peut animer Color, Transparency, Size… tout à la fois !
  • Astuce : la cible est calculée à partir de la position actuelle (elevator01.Position.Y + 100) : le script fonctionne où que soit placée la Part dans ton niveau !

4. La magie Reverser = true

  • Montée (15 s) → descente (15 s) → montée → descente… à l’infini, sans aucune boucle while ni variable direction !
  • Compare avec le tutoriel précédent : des dizaines de lignes remplacées par -1 et true !

5. N’oublie pas Anchored = true !

  • Dans le Script 1, la Part doit être ancrée dans Studio (case Anchored cochée), sinon elle tombe avant même que le Tween commence ! (Le Script 2 corrige cela dans le code.)

SCRIPT 2 : Gérer plusieurs ascenseurs différents

Problème du Script 1 : il n’anime que elevator01, et il faut copier-coller tout le code pour un 2ème ascenseur. Mauvaise pratique !

Solution (rappel des tutoriels précédents) : une fonction createElevator paramétrable + un tableau de fiches + une boucle pairs !

Voici le script commenté :

-- Récupérer le dossier et les deux ascenseurs
local folder = workspace:WaitForChild("MovementsFolder")
local elevator01 = folder:WaitForChild("Part")
local elevator02 = folder:WaitForChild("Part02")

-- Récupérer le service d'animations TweenService
local tweenService = game:GetService("TweenService")

-- Tableau des ascenseurs (une fiche de paramètres par ascenseur)
local elevators = {}

-- Fonction pour créer un ascenseur avec SES paramètres propres
-- Paramètres :
--   part : la Part à animer
--   movement : le déplacement à effectuer (Vector3)
--   duration : la durée de l'animation (secondes)
--   style : le style d'animation (EasingStyle)
--   direction : le sens de l'animation (EasingDirection)
--   repeatCount : le nombre de répétitions (-1 = infini)
--   reverser : retour à l'origine à la fin (true/false)
local function createElevator(part, movement, duration, style, direction, repeatCount, reverser)
    part.Anchored = true    -- Mouvement contrôlé, pas de gravité
    part.CanCollide = true  -- Le joueur peut monter dessus

    elevators[part] = {
        movement = movement or Vector3.new(0, 100, 0),      -- Déplacement par défaut : +100 en Y
        duration = duration or 10,                          -- Durée par défaut : 10 s
        style = style or Enum.EasingStyle.Sine,             -- Style par défaut : Sine
        direction = direction or Enum.EasingDirection.InOut,-- Direction par défaut : InOut
        repeatCount = repeatCount or -1,                    -- Répétition par défaut : infinie
        reverser = reverser or true,                        -- Descente automatique par défaut
    }
end

-- Ascenseur 1 : monte de 100 studs (tous les autres paramètres par défaut)
createElevator(elevator01, Vector3.new(0, 100, 0))

-- Ascenseur 2 : recule de 200 studs sur l'axe X (un pont mobile horizontal !)
createElevator(elevator02, Vector3.new(-200, 0, 0))

-- Parcourir le tableau et créer + lancer UN TWEEN PAR ASCENSEUR
for part, properties in pairs(elevators) do
    -- Construire les règles de l'animation avec les paramètres de CET ascenseur
    local tweenInfo = TweenInfo.new(
        properties.duration,
        properties.style,
        properties.direction,
        properties.repeatCount,
        properties.reverser
    )

    -- Créer le Tween : cible = position actuelle + le déplacement demandé
    local tween = tweenService:Create(
        part,
        tweenInfo,
        {Position = part.Position + properties.movement}
    )

    -- Lancer l'animation
    tween:Play()
end

Explications du Script 2

1. La fonction createElevator : une fiche par ascenseur

  • Même architecture que le tutoriel précédent (les tableaux de cooldowns, de checkpoints…) :
    • Clé = la Part.
    • Valeur = une fiche de paramètres (table).
  • Rappel : movement or Vector3.new(0, 100, 0) = si le paramètre n’est pas fourni, on prend la valeur par défaut.

2. Deux ascenseurs, deux comportements, UN code

createElevator(elevator01, Vector3.new(0, 100, 0))    -- Monte de 100 studs
createElevator(elevator02, Vector3.new(-200, 0, 0))   -- Recule de 200 studs
  • Ascenseur 1 : vertical (comme un vrai ascenseur).
  • Ascenseur 2 : horizontal (comme un pont mobile ou un tapis géant).
  • La cible est calculée dans la boucle : part.Position + properties.movement → fonctionne pour n’importe quelle Part, n’importe où !

3. La boucle pairs : créer et lancer tous les Tweens

for part, properties in pairs(elevators) do
    local tweenInfo = TweenInfo.new(...)
    local tween = tweenService:Create(part, tweenInfo, {Position = ...})
    tween:Play()
end
  • On parcourt toutes les fiches, et pour chacune on construit son TweenInfo personnalisé.
  • Chaque Tween tourne indépendamment : l’ascenseur 1 mettra 10 s pour monter, l’ascenseur 2 aussi mais dans l’autre sens, chacun son rythme, sans se gêner !
  • Ajouter un 3ème ascenseur = 1 seule ligne :luaCopierlocal elevator03 = folder:WaitForChild("Part03") createElevator(elevator03, Vector3.new(0, 0, 150), 20, Enum.EasingStyle.Bounce) -- Va-et-vient avec rebonds !

QCM : Teste ta compréhension !

Sur TweenService et TweenInfo :

  1. À quoi sert TweenService ?
    • a) À créer des animations automatiques de propriétés (position, couleur, transparence…).
    • b) À détecter les collisions.
    • c) À afficher des messages dans le chat.
  1. Que signifie -1 en 4ème paramètre de TweenInfo.new ?
    • a) L’animation se répète à l’infini.
    • b) L’animation dure -1 seconde.
    • c) L’animation est supprimée.
  1. Que fait le paramètre Reverser = true ?
    • a) Après l’aller, l’animation rejoue en sens inverse : descente automatique !
    • b) La Part tourne sur elle-même.
    • c) La Part devient invisible.
  1. Quel EasingStyle donne un mouvement doux au départ et à l’arrivée, comme un vrai ascenseur ?
    • a) Enum.EasingStyle.Sine
    • b) Enum.EasingStyle.Bounce
    • c) Enum.EasingStyle.Linear
  1. Quel EasingStyle fait rebondir la Part à l’arrivée ?
    • a) Enum.EasingStyle.Bounce
    • b) Enum.EasingStyle.Sine
    • c) Enum.EasingStyle.InOut

Sur la création du Tween :

  1. Que contient le 3ème argument de tweenService:Create ?
    • a) Une table avec les propriétés à animer et leur valeur finale (la cible).
    • b) La durée de l’animation.
    • c) Le nom du joueur.
  1. Pourquoi calcule-t-on la cible avec part.Position + properties.movement ?
    • a) Pour que le script fonctionne où que soit placée la Part dans le niveau.
    • b) Pour aller plus vite.
    • c) Pour changer la couleur de la Part.
  1. Que faut-il penser à faire pour que la Part animée ne tombe pas à cause de la gravité ?
    • a) L’ancrer (part.Anchored = true, comme dans createElevator).
    • b) La peindre en rouge.
    • c) La rendre transparente.

Sur le Script 2 :

  1. Quel est l’avantage de la fonction createElevator par rapport au Script 1 ?
    • a) On peut créer autant d’ascenseurs différents qu’on veut sans dupliquer le code (1 ligne par ascenseur).
    • b) Les animations vont plus vite.
    • c) Il n’y a aucun avantage.
  1. Que fait movement = movement or Vector3.new(0, 100, 0) ?
    • a) Si aucun mouvement n’est fourni, on utilise le déplacement par défaut (+100 en Y).
    • b) On additionne les deux vecteurs.
    • c) On supprime le mouvement.
  1. Dans le Script 2, comment s’appellent les deux paramètres corrigés par rapport à l’original ?
    • a) movement (au lieu de mouvment) et repeatCount (au lieu de nbrRepeat).
    • b) vitesse et duree.
    • c) up et down.
  1. Comment ajouter un 3ème ascenseur qui avance sur l’axe Z en 20 secondes ?
    • a) createElevator(elevator03, Vector3.new(0, 0, 150), 20) — une seule ligne suffit !
    • b) Il faut recopier tout le script.
    • c) C’est impossible.

Comparaison : Heartbeat (tutoriel précédent) vs TweenService

CritèreBoucle HeartbeatTweenService
Quantité de codeBeaucoup (direction, elapsed, conditions…).Très peu.
Pause entre les trajets✅ Facile (task.delay + direction = 0).❌ Pas prévu par TweenInfo (à gérer soi-même avec tween.Completed).
Chaque ascenseur différent✅ Fiches de paramètres.✅ Fiches de paramètres aussi !
Styles d’animation (rebond, élastique…)❌ À coder entièrement à la main.✅ Intégrés (EasingStyle) !
Annuler / reprendreCompliqué (déconnecter, recalculer).✅ tween:Cancel(), tween:Pause() !
ConclusionPuissant et flexible (pauses, conditions).

Permet le déplacement du joueur sur la part dans un déplacement sur X ou Z
Simple et rapide pour des allers-retours classiques.

La leçon du programmeur : avant de coder un mouvement à la main, demande-toi si Roblox n’a pas déjà un outil intégré ! Pour des animations simples, TweenService gagne. Pour des comportements complexes (pauses, étages, conditions), la boucle Heartbeat reste reine.


Idées pour aller plus loin

1. Animer d’autres propriétés que la Position

  • Objectif : Faire clignoter ou changer la couleur des ascenseurs.
  • Comment faire ?luaCopiertweenService:Create(part, TweenInfo.new(2, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true), {Color = Color3.fromRGB(255, 0, 0)}):Play()On peut animer plusieurs propriétés en même temps : {Position = ..., Color = ..., Transparency = 0.5} !

2. Ajouter une pause entre les trajets

  • Objectif : Combiner TweenService avec la pause du tutoriel précédent.
  • Comment faire ? Utiliser l’événement tween.Completed (attention : avec Reverser, il ne se déclenche qu’à la toute fin) :luaCopierlocal info = TweenInfo.new(10, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, 0, true) -- 0 répétition local tween = tweenService:Create(part, info, {Position = part.Position + properties.movement}) tween.Completed:Connect(function() task.wait(3) -- Pause de 3 secondes à l'arrivée tween:Play() -- Et on repart ! end) tween:Play()

3. Contrôler le Tween en direct

  • Objectif : Arrêter ou mettre en pause l’ascenseur (ex: bouton d’arrêt d’urgence !).
  • Comment faire ?tween:Pause() -- Met en pause (l'animation reprendra où elle s'est arrêtée) tween:Play() -- Reprend tween:Cancel() -- Annule et remet la Part à sa position d'origine

4. Un son d’ascenseur à chaque départ

  • Comment faire ?luaCopierlocal sound = part:FindFirstChildOfClass("Sound") if sound then sound:Play() endAssocie-le à un ClickDetector (rappel du tutoriel des messages) pour un bouton « Appeler l’ascenseur » !

Résumé des concepts clés

ConceptDescriptionExemple
TweenServiceService qui anime automatiquement les propriétés.game:GetService("TweenService")
TweenInfo.newLes règles : durée, style, direction, répétitions, reverser.TweenInfo.new(15, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1, true)
EasingStyleLe « caractère » du mouvement (Sine, Bounce, Elastic…).Enum.EasingStyle.Bounce
‘-1 répétitions + true reverserLe combo magique : aller-retour infini en 2 paramètres !
tweenService:Create(objet, infos, cible)Prépare l’animation.{Position = part.Position + movement}
tween:Play()Lance l’animation.
Fiche de paramètres + pairsUn Tween personnalisé par ascenseur, ajoutable en 1 ligne.createElevator(part, ...)

Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Déplacer des parts dans toutes les directions

  • avec déplacement fluide de la part avec RunService.Heartbeat
  • avec déplacement du joueur sur la part avec AssemblyLinearVelocity

En utilisant les concepts appris dans les deux tutoriels précédents, tu vas apprendre à déplacer de manière fluide les parts avec RunService.Heartbeat tout en proposer un déplacement du joueur sur la part sur les axes X et Z avec AssemblyLinearVelocity. On va garder la Part ancrée pour un mouvement 100% contrôlé et précis, mais on informe le moteur physique de la vitesse réelle du déplacement

Rappel des deux tutoriels précédents :

TechniqueComment ça marcheLe joueur est transporté ?
Tutoriel 1 : part.Position
avec Anchored = true
On téléporte la PartNon ! Le joueur traverse la part
Tutoriel 2 : AssemblyLinearVelocity
avec Anchored = false
On confie le déplacement au moteur physique.Oui… mais la plateforme subit la gravité et pivote sous le poids du joueur

L’astuce de ce tutoriel : le meilleur des deux mondes !

On garde la Part ancrée (Anchored = true) pour un mouvement 100% contrôlé et précis, mais on informe le moteur physique de la vitesse réelle du déplacement avec :

part.AssemblyLinearVelocity = speed * direction
  • Pourquoi ça marche ?
    • Roblox utilise la vélocité d’une surface pour savoir comment déplacer les personnages debout dessus.
    • En donnant la bonne vélocité (même à une Part ancrée), Roblox « sait » que le sol bouge, et entraîne le joueur avec lui !
    • C’est exactement la formule de la physique : vitesse = distance / temps.
AspectTutoriel 1 (Position seule)Tutoriel 2 (Vélocité + non ancré)Ce tutoriel (hybride)
Précision du mouvement✅ Parfaite❌ La physique peut dévier✅ Parfaite
Transport du joueur❌ Non✅ Oui✅ Oui
Gravité / collisions parasites✅ Aucune (ancrée)❌ Oui (non ancrée)✅ Aucune (ancrée)

Apprendre à créer une fonction paramétrable pour déplacer une Part de manière fluide en utilisant RunService.Heartbeat. Ce tutoriel explique :

  1. Comment utiliser RunService.Heartbeat pour des mouvements fluides.
  2. Comment créer une fonction paramétrable pour contrôler le déplacement.
  3. Comment calculer la distance parcourue avec Magnitude.

Préparation dans Roblox Studio

Pour tester ce script, suis ces étapes :

  1. Créer un dossier :
    • Dans Workspace, crée un dossier nommé MovementsFolder.
  2. Ajouter une Part :
    • Ajoute une Part dans Folder.
    • Nomme-la Part (ou modifie le nom dans le script).
  3. Configurer la Part :
    • Assure-toi que la Part a une taille visible (ex: Size = Vector3.new(4, 4, 4)).
    • Place-la à une position centrale (ex: Position = Vector3.new(0, 10, 0)).
  4. Ajouter le script :
    • Crée un Script dans ServerScriptService et copie-y le code fourni.

Script pour faire monter :

local folder = workspace:WaitForChild("MovementsFolder")
local ascenseur = folder:WaitForChild("Part")

task.wait(3)
ascenseur.Position += Vector3.new(0, 10, 0)

Pourquoi utiliser RunService.Heartbeat ?

Dans Roblox, RunService.Heartbeat est un événement qui se déclenche à chaque frame (environ 60 fois par seconde). Cela permet de :

  • Créer des mouvements fluides (ex: une animation progressive).
  • Contrôler précisément le déplacement d’un objet.
  • Arrêter le mouvement quand une condition est remplie (ex: une distance parcourue).

Dans ce tutoriel, on va déplacer une Part sur l’axe Y jusqu’à ce qu’elle ait parcouru une distance donnée.

Faire montrer progressivement la part :

local RunService = game:GetService("RunService")

local folder = workspace:WaitForChild("MovementsFolder")
local ascenseur = folder:WaitForChild("Part")

ascenseur.Anchored = true
task.wait(5)


local connection
connection = RunService.Heartbeat:Connect(function(deltaTime)

	ascenseur.Position += Vector3.new(0, +10,0)

end)

Faire monter la parte en contrôlant sa vitesse de montée :

  • speed : vitesse du déplacement en stud
  • direction : direction du déplacement 1 monter -1 descendre
local runService = game:GetService("RunService")

local folder = workspace:WaitForChild("MovementsFolder")
local ascenseur = folder:WaitForChild("Part")

ascenseur.Anchored = true
task.wait(5)

local speed = 10
local direction = 1 -- 1 monter -1 descendre


local connection
connection = runService.Heartbeat:Connect(function(deltatime)
	ascenseur.Position += Vector3.new(0, (direction * speed * deltatime) , 0)
end)

Faire monter puis descendre la part en contrôlant la vitesse et la hauteur :

  • starty : point de départ sur l’axe Y
  • targety : point d’arrivée sur l’axe Y
local runService = game:GetService("RunService")

local folder = workspace:WaitForChild("MovementsFolder")
local ascenseur = folder:WaitForChild("Part")

ascenseur.Anchored = true
task.wait(5)

local speed = 10
local direction = 1 -- 1 monter -1 descendre

local startY = ascenseur.Position.Y
local targetY = startY + 50

local connection
connection = runService.Heartbeat:Connect(function(deltatime)
	ascenseur.Position += Vector3.new(0, (direction * speed * deltatime) , 0)
	
	if ascenseur.Position.Y >= targetY then
		direction = -1
	elseif ascenseur.Position.Y <= startY then
		direction = 1
	end
end)

Monter et descendre avec un arrêt :

  • pause_duration = temps de l’arrêt en seconde
local runService = game:GetService("RunService")

local folder = workspace:WaitForChild("MovementsFolder")
local ascenseur = folder:WaitForChild("Part")

ascenseur.Anchored = true
task.wait(5)

local speed = 10
local direction = 1 -- 1 monter -1 descendre

local startY = ascenseur.Position.Y
local targetY = startY + 50

local pause_duration = 5

local connection
connection = runService.Heartbeat:Connect(function(deltatime)
	ascenseur.Position += Vector3.new(0, (direction * speed * deltatime) , 0)
	
	if direction ~= 0 and ascenseur.Position.Y >= targetY then
		direction = 0
		task.delay(pause_duration, function()
			direction = -1
		end)
	
	elseif direction ~= 0 and ascenseur.Position.Y <= startY then
		direction = 0
		task.delay(pause_duration, function()
			direction = 1
		end)
	end
end)

Parcourir sur une certaine distance en transportant le joueur :

On garde la Part ancrée (Anchored = true) pour un mouvement 100% contrôlé et précis, mais on informe le moteur physique de la vitesse réelle du déplacement avec :

ascenseur.AssemblyLinearVelocity = Vector3.new(speed * direction, 5, 0)
  • Pourquoi ça marche ?
    • Roblox utilise la vélocité d’une surface pour savoir comment déplacer les personnages debout dessus.
    • En donnant la bonne vélocité (même à une Part ancrée), Roblox « sait » que le sol bouge, et entraîne le joueur avec lui !
  • .Magnitude : distance totale parcourue depuis le départ (Pythagore 3D : √(x² + y² + z²)).
local runService = game:GetService("RunService")

local folder = workspace:WaitForChild("MovementsFolder")
local ascenseur = folder:WaitForChild("Part")

local speed = 20
local direction = 1 -- 1 monter -1 descendre

local start = ascenseur.Position
local distance = 200

local pause_duration = 5

ascenseur.Anchored = true
task.wait(5)

local connection
connection = runService.Heartbeat:Connect(function(deltatime)
	ascenseur.Position += Vector3.new((direction * speed * deltatime),
		                           0 , 
		                           0)
	if direction == 0 then
		ascenseur.AssemblyLinearVelocity = Vector3.new(0, 0, 0)		
	else	
		ascenseur.AssemblyLinearVelocity = Vector3.new(speed * direction,
			                                       5, 
			                                       0)		
	end
	if direction ~= 0 and  and (ascenseur.Position - start).Magnitude > distance then
		direction = 0
		task.delay(pause_duration, function()
			direction = -1
		end)

	elseif direction ~= 0 and  and (ascenseur.Position - start).Magnitude < 1 then
		direction = 0
		task.delay(pause_duration, function()
			direction = 1
		end)
	end
end)

Parcourir sur une certaine distance dans toutes les directions :

local runService = game:GetService("RunService")

local folder = workspace:WaitForChild("MovementsFolder")
local ascenseur = folder:WaitForChild("Part")

local speed = Vector3.new(20, 20, 20)
local direction = 1 -- 1 monter -1 descendre

local start = ascenseur.Position
local distance = 200

local pause_duration = 5

ascenseur.Anchored = true
task.wait(5)

local connection
connection = runService.Heartbeat:Connect(function(deltatime)
	ascenseur.Position += speed * direction * deltatime

	if direction == 0 then
		ascenseur.AssemblyLinearVelocity = Vector3.new(0, 0, 0)		
	else	
		ascenseur.AssemblyLinearVelocity = Vector3.new(speed.X * direction,
			                                           5 , 
			                                           speed.Z * direction)		
	end
	if direction ~= 0 and  and (ascenseur.Position - start).Magnitude > distance then
		direction = 0
		task.delay(pause_duration, function()
			direction = -1
		end)

	elseif direction ~= 0 and  and (ascenseur.Position - start).Magnitude < 1 then
		direction = 0
		task.delay(pause_duration, function()
			direction = 1
		end)
	end
end)

Parcourir pendant un certain temps dans toutes les directions :

local runService = game:GetService("RunService")

local folder = workspace:WaitForChild("MovementsFolder")
local ascenseur = folder:WaitForChild("Part")

local speed = Vector3.new(20, 20, 20)
local direction = 1 -- 1 monter -1 descendre

local elapsed = 0
local duration = 10

local pause_duration = 5

ascenseur.Anchored = true
task.wait(5)

local connection
connection = runService.Heartbeat:Connect(function(deltatime)
	ascenseur.Position += speed * direction * deltatime

	if direction == 0 then
		ascenseur.AssemblyLinearVelocity = Vector3.new(0, 0, 0)		
	else	
		ascenseur.AssemblyLinearVelocity = Vector3.new(speed.X*direction,
                                                               5 , 
			                                       speed.Z*direction)		
	end
	
	elapsed += deltatime * direction
	if direction ~= 0 and elapsed >= duration then
		direction = 0
		task.delay(pause_duration, function()
			direction = -1
		end)

	elseif direction ~= 0 and elapsed <= 0 then
		direction = 0
		task.delay(pause_duration, function()
			direction = 1
		end)
	end
end)

Créer des Ascenseurs Automatiques en Aller-Retour avec Pause.

Apprendre à créer plusieurs ascenseurs qui montent, marquent une pause, redescendent, repartent… en boucle infinie ! Ce tutoriel explique :

  1. Comment stocker les paramètres de chaque ascenseur dans un tableau (une fiche d’identité par ascenseur !).
  2. La boucle for ... in pairs() pour gérer plusieurs ascenseurs en même temps avec un seul Heartbeat.
  3. La variable elapsed (temps écoulé) pour savoir où en est l’ascenseur.
  4. La direction (1, 0, -1) pour piloter montée, pause et descente.
  5. La protection contre la double connexion au Heartbeat.

Ce script réunit des briques vues dans les tutoriels précédents : Heartbeat, deltaTime, AssemblyLinearVelocity pour transporter les joueurs, et task.delay pour les pauses !

Comment fonctionne un ascenseur ?

Un vrai ascenseur fait un cycle :

  1. Montée (direction = 1)
  2. Pause à l’étage (direction = 0) — les portes s’ouvrent !
  3. Descente (direction = -1)
  4. Pause en bas (direction = 0)
  5. Et on recommence !

Le défi du jour : gérer deux ascenseurs avec des vitesses différentes (l’un monte en diagonale, l’autre avance sur l’axe X), mais avec une seule boucle Heartbeat pour tout le monde — c’est plus efficace que de créer une boucle par ascenseur !

-- Récupérer le service RunService pour utiliser Heartbeat
local RunService = game:GetService("RunService")

-- Récupérer le dossier et les deux ascenseurs
local folder = workspace:WaitForChild("MovementsFolder")
local elevator01 = folder:WaitForChild("Part")
local elevator02 = folder:WaitForChild("Part02")

-- Variable pour stocker la connexion à Heartbeat (UNE SEULE pour tous les ascenseurs)
local connection

-- Fonction qui démarre la boucle de mouvement pour TOUS les ascenseurs
-- Paramètre : elevators = le tableau contenant tous les ascenseurs et leurs paramètres
local function startMovement(elevators)
	-- PROTECTION : si une connexion existe déjà, on ne fait rien
	-- (évite de connecter DEUX fois Heartbeat, ce qui doublerait la vitesse !)
	if connection then return end

	connection = RunService.Heartbeat:Connect(function(deltaTime)
		-- Parcourir le tableau des ascenseurs avec pairs()
		-- Pour chaque ascenseur (part) et sa fiche de paramètres (properties) :
		for part, properties in pairs(elevators) do

			-- Ignorer si l'objet n'est pas une BasePart (sécurité)
			if not part:IsA("BasePart") then continue end
			
			if properties.direction ~= 0 then

				-- Déplacer l'ascenseur : position += vitesse * direction * temps
				-- direction = 1 (monte) : avance normalement
				-- direction = -1 (descend) : avance à l'envers (vitesse négative)
				-- direction = 0 (pause) : ne bouge plus (vitesse * 0 = 0)
				part.Position += properties.speed * properties.direction * deltaTime

				-- Donner la vélocité réelle pour TRANSPORTER LES JOUEURS
				-- (technique du tutoriel précédent : vitesse = déplacement / temps)
				if properties.direction == 0 then
					part.AssemblyLinearVelocity = Vector3.new(0, 0, 0)  -- Arrêt propre
				else
					part.AssemblyLinearVelocity = properties.speed *
                                                                                    properties.direction
				end

				-- Mise à jour du temps écoulé :
				-- si direction = 1 : elapsed augmente (montée)
				-- si direction = -1 : elapsed DIMINUE (descente)
				-- si direction = 0 : elapsed ne change plus (pause)
				properties.elapsed += deltaTime * properties.direction
			end
			-- ARRIVÉE EN HAUT : elapsed atteint la durée du trajet
			if properties.direction ~= 0 and properties.elapsed >= properties.duration then
				properties.direction = 0  -- Stop : pause en haut !
				task.delay(properties.pause_duration, function()
					properties.direction = -1  -- Après la pause : descente
				end)

				-- ARRIVÉE EN BAS : elapsed revient à 0
			elseif properties.direction ~= 0 and properties.elapsed <= 0 then
				properties.direction = 0  -- Stop : pause en bas !
				task.delay(properties.pause_duration, function()
					properties.direction = 1  -- Après la pause : montée
				end)
			end
		end
	end)
end

-- Attendre 5 secondes avant de démarrer
task.wait(5)

-- Tableau qui contiendra tous les ascenseurs et leurs paramètres
local elevators = {}

-- Fonction pour créer un ascenseur et l'enregistrer dans le tableau
-- Chaque ascenseur reçoit SA fiche de paramètres (sa "fiche d'identité")
local function createElevator(part, speed, duration, pauseDuration)
	part.Anchored = true    -- Mouvement 100% contrôlé par le script
	part.CanCollide = true  -- Le joueur peut monter dessus

	elevators[part] = {
		connection = nil,
		speed = speed or Vector3.new(20, 20, 20),  -- Vitesse par défaut si non fournie
		duration = duration or 10,                  -- Durée du trajet par défaut : 10 s
		pause_duration = pauseDuration or 3,        -- Durée de la pause par défaut : 3 s
		direction = 1,   -- Direction de départ : 1 = monte
		elapsed = 0,     -- Temps écoulé : on part du bas (0)
	}
end

-- Créer les deux ascenseurs :
-- Ascenseur 1 : vitesse par défaut (diagonale 20,20,20), durée et pause par défaut
createElevator(elevator01)

-- Ascenseur 2 : avance sur l'axe X seulement (Vector3.new(20, 0, 0))
createElevator(elevator02, Vector3.new(20, 0, 0))

-- Démarrer le mouvement de tous les ascenseurs
startMovement(elevators)

Explications Détaillées

1. La fiche d’identité : un tableau DANS un tableau !

elevators[part] = {
    speed = Vector3.new(20, 20, 20),
    duration = 10,
    pause_duration = 3,
    direction = 1,
    elapsed = 0,
}
  • elevators est un tableau associatif dont la clé est la Part :luaCopierelevators = { [Part] = { speed = ..., direction = ..., ... }, [Part02] = { speed = ..., direction = ..., ... } }
  • Chaque ascenseur a sa propre fiche avec ses paramètres. C’est la même technique que les tableaux cooldowns des tutoriels précédents : une clé unique par objet, une valeur dédiée.
  • L’astuce valeur or défaut :luaCopierspeed = speed or Vector3.new(20, 20, 20),
    • Si on donne une vitesse → on l’utilise.
    • Si on n’en donne pas (comme createElevator(elevator01)) → speed vaut nil, et nil or Vector3.new(20, 20, 20) prend la valeur par défaut.
    • C’est comme un jeu vidéo : si tu ne choisis pas la difficulté, le mode Normal s’active tout seul !

2. La boucle for ... in pairs() : gérer tous les ascenseurs d’un coup

for part, properties in pairs(elevators) do
    ...
end
  • Jusqu’ici, tu connaissais ipairs pour les listes numérotées (1, 2, 3…).
  • pairs parcourt un tableau associatif : il donne successivement chaque clé (part) et chaque valeur (properties).
  • Avantage énorme : un seul Heartbeat met à jour tous les ascenseurs à chaque frame. Si tu ajoutes un 3ème, 4ème… 10ème ascenseur, aucune ligne de la boucle ne change !

3. La direction : le chef d’orchestre (1, 0, -1)

La variable properties.direction peut valoir trois états :

directionSignificationEffet sur le mouvementEffet sur elapsed
1MontéeAvance à pleine vitesseAugmente
0PauseImmobile (vitesse × 0 = 0)Ne bouge plus
‘-1DescenteAvance « à l’envers »Diminue
  • La magie des signes : speed * direction * deltaTime :
    • direction = 1 → +speed (monte)
    • direction = -1 → -speed (descend !)
    • direction = 0 → rien (pause)
  • Un seul calcul gère les trois comportements !

4. elapsed : le chronomètre du trajet

properties.elapsed += deltaTime * properties.direction
  • elapsed compte le temps écoulé depuis le bas :
    • Pendant la montée : elapsed augmente (0 → 1 → 2 → … → duration).
    • Pendant la descente : elapsed diminue (duration → … → 2 → 1 → 0).
    • Pendant la pause : elapsed ne change pas (car direction = 0).
  • Deux conditions de retournement :luaCopierif properties.elapsed >= properties.duration then -- En HAUT elseif properties.elapsed <= 0 then -- En BAS
  • Pourquoi un chronomètre plutôt qu’une distance ? Avec des vitesses en Vector3 (diagonales possibles), mesurer un temps est plus simple et plus fiable que mesurer une distance 3D à chaque frame !

5. La pause avec task.delay

properties.direction = 0   -- Stop immédiat
task.delay(properties.pause_duration, function()
    properties.direction = -1  -- Dans 3 secondes : descente
end)
  • On coupe d’abord la direction (0), puis on programme la suite avec task.delay.
  • Pourquoi pas task.wait ? On est dans une boucle Heartbeat ! Un task.wait(3) bloquerait toute la boucle pendant 3 secondes… et tous les autres ascenseurs seraient figés aussi ! task.delay programme l’action sans rien bloquer.

6. La protection contre la double connexion

if connection then return end
  • Le danger : si on appelle startMovement(elevators) deux fois (erreur de copier-coller par exemple), on aurait deux boucles Heartbeat actives en même temps.
  • Résultat catastrophique : chaque frame déplacerait les ascenseurs deux fois → vitesse doublée, elapsed doublé, chaos total !
  • La protection : si connection existe déjà (pas nil), la fonction s’arrête immédiatement. Un seul Heartbeat possible, garanti. C’est une variante du principe du cooldown !

7. La vélocité pour transporter les joueurs (rappel !)

part.AssemblyLinearVelocity = properties.speed * properties.direction
  • Technique du script précédent : même si la Part est ancrée, Roblox lit sa vélocité pour entraîner les joueurs debout dessus.
  • En pause, on remet la vélocité à zéro (Vector3.new(0, 0, 0)) : arrêt propre, personne ne glisse.
  • Détail important : ici la vitesse est un Vector3 (ex: Vector3.new(20, 20, 20)), donc l’ascenseur 1 monte en diagonale et le 2 avance horizontalement — la même fonction gère les deux cas !

QCM : Teste ta compréhension !

Sur le tableau des ascenseurs :

  1. Que stocke elevators[part] ?
    • a) Une fiche de paramètres (vitesse, durée, pause, direction, temps écoulé) propre à chaque ascenseur.
    • b) Le nom du joueur.
    • c) La couleur de la Part.
  1. Que fait speed = speed or Vector3.new(20, 20, 20) ?
    • a) Si aucune vitesse n’est fournie, on utilise la vitesse par défaut Vector3.new(20, 20, 20).
    • b) On additionne les deux vitesses.
    • c) On supprime la vitesse.
  1. Quel est l’avantage de stocker tous les ascenseurs dans le tableau elevators ?
    • a) Une seule boucle Heartbeat gère tous les ascenseurs, même si on en ajoute 10 de plus.
    • b) Le jeu va plus vite.
    • c) Les ascenseurs sont plus colorés.

Sur la boucle pairs :

  1. Que fait for part, properties in pairs(elevators) do ?
    • a) Parcourt le tableau associatif et donne chaque clé (part) et sa valeur (properties).
    • b) Parcourt uniquement les nombres de 1 à 10.
    • c) Supprime le tableau.
  1. Quelle est la différence entre ipairs et pairs ?
    • a) ipairs parcourt les listes numérotées (1, 2, 3…), pairs parcourt les tableaux avec n’importe quelle clé (comme des Parts).
    • b) ipairs est plus rapide.
    • c) pairs ne fonctionne qu’avec des joueurs.

Sur la direction :

  1. Que se passe-t-il quand properties.direction = -1 ?
    • a) L’ascenseur descend (vitesse multipliée par -1) et elapsed diminue.
    • b) L’ascenseur s’arrête.
    • c) L’ascenseur double de vitesse.
  1. Pourquoi un seul calcul speed * direction * deltaTime gère-t-il montée, descente ET pause ?
    • a) Car multiplier par 1 donne la vitesse, par -1 l’inverse, et par 0 rien du tout.
    • b) Car Lua devine automatiquement quoi faire.
    • c) Car les ascenseurs sont intelligents.

Sur le chronomètre elapsed :

  1. À quoi sert la variable elapsed ?
    • a) À compter le temps écoulé depuis le bas, pour savoir quand faire demi-tour.
    • b) À compter les joueurs sur l’ascenseur.
    • c) À afficher l’heure.
  1. Que se passe-t-il quand elapsed >= duration ?
    • a) L’ascenseur est en haut : il se met en pause, puis repartira en descente.
    • b) Le jeu s’arrête.
    • c) L’ascenseur est détruit.

Sur les pauses et la protection :

  1. Pourquoi utilise-t-on task.delay plutôt que task.wait pour la pause ?
    • a) task.wait bloquerait la boucle Heartbeat et figerait TOUS les ascenseurs ; task.delay programme la suite sans rien bloquer.
    • b) task.delay est plus court à écrire.
    • c) task.wait n’existe plus.
  1. Que risque-t-on si on supprime la ligne if connection then return end et qu’on appelle startMovement deux fois ?
    • a) Deux boucles Heartbeat déplaceraient chaque ascenseur deux fois par frame : vitesse doublée et chaos.
    • b) Rien, c’est juste une décoration.
    • c) Le jeu planterait immédiatement.
  1. Pourquoi met-on AssemblyLinearVelocity à zéro quand direction = 0 ?
    • a) Pour un arrêt propre : le joueur ne glisse pas pendant la pause de l’ascenseur.
    • b) Pour refroidir les moteurs.
    • c) Pour changer la couleur de l’ascenseur.

Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Déplacer une Part avec la Physique Roblox

Apprendre à déplacer une Part en utilisant la physique de Roblox avec les propriétés AssemblyLinearVelocity et AssemblyAngularVelocity. Contrairement au tutoriel précédent où on modifiait directement la Position et l’Orientation, ici on utilise les vitesse linéaire et angulaire pour un mouvement plus réaliste et dynamique.

Introduction : Pourquoi utiliser la physique ?

Dans Roblox, les objets peuvent bouger naturellement grâce à la physique (gravité, collisions, etc.). En utilisant AssemblyLinearVelocity et AssemblyAngularVelocity, on peut :

  • Simuler des mouvements réalistes (ex: une balle qui roule, un objet qui glisse).
  • Créer des effets dynamiques (ex: une voiture qui accélère, un objet qui tourne).
  • Contrôler la vitesse et la rotation de manière fluide.

Dans ce tutoriel, on va lancer une Part dans différentes directions et la faire tourner en utilisant la physique.

Préparation dans Roblox Studio

Pour tester ce script, suis ces étapes :

  1. Créer un dossier pour les mouvements :
    • Dans Workspace, crée un dossier nommé Folder.
  2. Ajouter une Part :
    • Ajoute une Part dans Folder.
    • Nomme-la Part (ou modifie le nom dans le script).
  3. Configurer la Part :
    • Assure-toi que la Part a une taille visible (ex: Size = Vector3.new(4, 4, 4)).
    • Place-la à une position centrale (ex: Position = Vector3.new(0, 10, 0)).
  4. Ajouter le script :
    • Crée un Script dans ServerScriptService et copie-y le code fourni.
  5. Lancer le jeu :
    • Observe la Part :
      • Après 5 secondes, elle commence à se déplacer vers l’avant (Z).
      • Elle recule, tourne, monte, et se déplace en diagonale.

Voici le script commenté :

-- Récupérer le dossier "Folder" dans le Workspace
local folder = workspace:WaitForChild("Folder")

-- Récupérer la Part à déplacer dans le dossier
local movingPart = folder:WaitForChild("Part")

-- Désactiver l'ancrage pour permettre à la Part de bouger avec la physique
movingPart.Anchored = false

-- Attendre 5 secondes avant de commencer les mouvements
task.wait(5)

-- Appliquer une vitesse linéaire de 100 unités/seconde vers l'avant (axe Z)
movingPart.AssemblyLinearVelocity = Vector3.new(0, 0, 100)
task.wait(1)  -- Attendre 1 seconde

-- Appliquer une vitesse linéaire de -100 unités/seconde vers l'arrière (axe Z)
movingPart.AssemblyLinearVelocity = Vector3.new(0, 0, -100)
task.wait(1)

-- Appliquer une vitesse angulaire de 10 degrés/seconde autour de l'axe Y (rotation droite/gauche)
movingPart.AssemblyAngularVelocity = Vector3.new(0, 10, 0)
task.wait(1)

-- Appliquer une vitesse angulaire de -10 degrés/seconde autour de l'axe Y (rotation gauche/droite)
movingPart.AssemblyAngularVelocity = Vector3.new(0, -10, 0)
task.wait(1)

-- Appliquer une vitesse linéaire de 100 unités/seconde vers le haut (axe Y)
movingPart.AssemblyLinearVelocity = Vector3.new(0, 100, 0)
task.wait(1)

-- Appliquer une vitesse linéaire de 100 unités/seconde en diagonale (axe Y et Z)
movingPart.AssemblyLinearVelocity = Vector3.new(0, 100, 100)
task.wait(1)

-- Appliquer une vitesse angulaire de 10 degrés/seconde autour de l'axe Y (rotation)
movingPart.AssemblyAngularVelocity = Vector3.new(0, 10, 0)

Explications Détaillées

1. Désactiver l’ancrage (Anchored = false)

movingPart.Anchored = false
  • Anchored = false :
    • Permet à la Part de bouger librement sous l’effet de la physique (gravité, collisions, vitesses appliquées).
    • Sans cela, la Part resterait fixe et les vitesses n’auraient aucun effet.
  • Pourquoi est-ce important ?
    • Si Anchored = true, la Part ne pourrait pas bouger, même avec AssemblyLinearVelocity ou AssemblyAngularVelocity.

2. Déplacer une Part avec AssemblyLinearVelocity

movingPart.AssemblyLinearVelocity = Vector3.new(0, 0, 100)
  • AssemblyLinearVelocity :
    • Définit la vitesse linéaire de la Part (en unités par seconde).
    • La Part se déplace en ligne droite dans la direction spécifiée.
  • Vector3.new(X, Y, Z) :
    • Définit la vitesse selon les axes X, Y et Z.
    • Exemple : Vector3.new(0, 0, 100) signifie 100 unités/seconde vers l’avant (axe Z).
  • Effet de la physique :
    • La Part accélère dans la direction donnée jusqu’à ce qu’une autre force (ex: collision, gravité) ne l’arrête.
    • Si tu veux un mouvement constant, il faut réappliquer la vitesse régulièrement.

Exemples de vitesses linéaires :

CodeMouvement
Vector3.new(100, 0, 0)Déplacement vers la droite (axe X).
Vector3.new(-100, 0, 0)Déplacement vers la gauche (axe X).
Vector3.new(0, 100, 0)Déplacement vers le haut (axe Y).
Vector3.new(0, -100, 0)Déplacement vers le bas (axe Y).
Vector3.new(0, 0, 100)Déplacement vers l’avant (axe Z).
Vector3.new(0, 0, -100)Déplacement vers l’arrière (axe Z).
Vector3.new(100, 100, 0)Déplacement en diagonale (X et Y).

3. Faire tourner une Part avec AssemblyAngularVelocity

movingPart.AssemblyAngularVelocity = Vector3.new(0, 10, 0)
  • AssemblyAngularVelocity :
    • Définit la vitesse de rotation de la Part (en degrés par seconde).
    • La Part tourne autour des axes spécifiés.
  • Vector3.new(X, Y, Z) :
    • Définit la rotation autour des axes X, Y et Z (en degrés par seconde).
    • Exemple : Vector3.new(0, 10, 0) signifie 10 degrés/seconde autour de l’axe Y (rotation droite/gauche).

Exemples de vitesses angulaires :

CodeRotation
Vector3.new(10, 0, 0)Rotation vers l’avant (axe X).
Vector3.new(-10, 0, 0)Rotation vers l’arrière (axe X).
Vector3.new(0, 10, 0)Rotation vers la droite (axe Y).
Vector3.new(0, -10, 0)Rotation vers la gauche (axe Y).
Vector3.new(0, 0, 10)Rotation dans le sens horaire (axe Z).
Vector3.new(0, 0, -10)Rotation dans le sens anti-horaire (axe Z).

4. Combiner vitesse linéaire et angulaire

Dans le script, on utilise AssemblyLinearVelocity et AssemblyAngularVelocity pour :

  • Déplacer la Part dans une direction.
  • La faire tourner en même temps.

Exemple :

luaCopier

-- Déplacement en diagonale + rotation
movingPart.AssemblyLinearVelocity = Vector3.new(0, 100, 100)  -- Diagonale (Y et Z)
movingPart.AssemblyAngularVelocity = Vector3.new(0, 10, 0)   -- Rotation autour de Y
  • La Part monte et avance tout en tournant sur elle-même.

QCM : Teste ta compréhension !

Sur la physique et l’ancrage :

  1. Pourquoi utilise-t-on Anchored = false ?
    • a) Pour permettre à la Part de bouger avec la physique.
    • b) Pour empêcher la Part de bouger.
    • c) Pour rendre la Part invisible.
  1. Que se passe-t-il si on oublie de mettre Anchored = false ?
    • a) La Part ne bougera pas, même avec AssemblyLinearVelocity.
    • b) La Part disparaîtra.
    • c) La Part changera de couleur.

Sur AssemblyLinearVelocity :

  1. Que fait movingPart.AssemblyLinearVelocity = Vector3.new(0, 0, 100) ?
    • a) Déplace la Part de 100 unités/seconde vers l’avant (axe Z).
    • b) Déplace la Part de 100 unités/seconde vers le haut (axe Y).
    • c) Fait tourner la Part de 100 degrés/seconde.
  1. Que fait Vector3.new(0, 100, 100) pour AssemblyLinearVelocity ?
    • a) Déplace la Part de 100 unités/seconde vers le haut et l’avant (axes Y et Z).
    • b) Déplace la Part de 100 unités/seconde vers la droite (axe X).
    • c) Fait tourner la Part.

Sur AssemblyAngularVelocity :

  1. Que fait movingPart.AssemblyAngularVelocity = Vector3.new(0, 10, 0) ?
    • a) Fait tourner la Part de 10 degrés/seconde autour de l’axe Y (droite/gauche).
    • b) Fait tourner la Part de 10 degrés/seconde autour de l’axe X (avant/arrière).
    • c) Fait tourner la Part de 10 degrés/seconde autour de l’axe Z.
  1. Que fait Vector3.new(0, 0, 10) pour AssemblyAngularVelocity ?
    • a) Fait tourner la Part de 10 degrés/seconde autour de l’axe Z (sens horaire).
    • b) Fait tourner la Part de 10 degrés/seconde autour de l’axe X.
    • c) Déplace la Part vers l’avant.

Sur les délais :

  1. À quoi sert task.wait(1) dans ce script ?
    • a) Attendre 1 seconde pour laisser le temps à la physique de s’appliquer.
    • b) Accélérer le script.
    • c) Supprimer la Part.
  1. Que se passe-t-il si on supprime tous les task.wait() ?
    • a) Les mouvements seront trop rapides et la Part aura un comportement imprévisible.
    • b) La Part ne bougera pas.
    • c) La Part changera de couleur.

Comparaison avec le Tuto : https://123codage.com/deplacer-une-part/

MéthodePosition / OrientationAssemblyLinearVelocity / AssemblyAngularVelocity
Type de mouvementDéplacement instantané (téléportation).Déplacement progressif et réaliste (physique).
Effet sur la PartLa Part se téléporte directement à la nouvelle position.La Part accélère et se déplace avec la physique.
AnchoredDoit être true (sinon la Part tombe).Doit être false (pour permettre le mouvement physique).
Utilisation typiquePour des mouvements précis et contrôlés (ex: téléportation).Pour des mouvements naturels et dynamiques (ex: une balle qui roule).
Exemplepart.Position = Vector3.new(10, 0, 0)part.AssemblyLinearVelocity = Vector3.new(10, 0, 0)
Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Déplacer une Part

Apprendre à déplacer une Part dans Roblox en utilisant les propriétés Position et Orientation. Ce tutoriel explique :

  1. Comment accéder à une Part dans le Workspace.
  2. Comment déplacer une Part en modifiant sa Position.
  3. Comment faire tourner une Part en modifiant son Orientation.
  4. Comment utiliser task.wait() pour créer des délais entre les mouvements.

Introduction : Pourquoi déplacer une Part ?

Dans Roblox, les Part sont les éléments de base pour construire des objets. Savoir les déplacer et les faire tourner permet de :

  • Créer des animations (ex: une porte qui s’ouvre, un ascenseur qui monte).
  • Simuler des mouvements (ex: un objet qui se déplace automatiquement).
  • Contrôler des éléments du jeu (ex: une plateforme qui bouge).

Dans ce tutoriel, on va déplacer une Part dans différentes directions et la faire tourner autour des axes X, Y et Z.

Préparation dans Roblox Studio

Pour tester ce script, suis ces étapes :

  1. Créer un dossier pour les mouvements :
    • Dans Workspace, crée un dossier nommé MovementsFolder.
  2. Ajouter une Part :
    • Ajoute une Part dans MovementsFolder.
    • Nomme-la Part (ou modifie le nom dans le script).
  3. Ajouter le script :
    • Crée un Script dans ServerScriptService et copie-y le code fourni.
  4. Lancer le jeu :
    • Observe la Part :
      • Après 5 secondes, elle commence à se déplacer.
      • Elle se déplace sur les axes X, Y et Z, puis tourne autour des axes X, Y et Z.

Voici le script commenté sous ServerScriptService :

-- Récupérer le dossier "MovementsFolder" dans le Workspace
local movementsFolder = workspace:WaitForChild("MovementsFolder")

-- Récupérer la Part à déplacer dans le dossier
local movingPart = movementsFolder:WaitForChild("Part")

-- Empêcher la Part de bouger à cause des collisions ou de la gravité
movingPart.Anchored = true

-- Attendre 5 secondes avant de commencer les mouvements
task.wait(5)

-- Déplacer la Part de +50 unités sur l'axe X (droite)
movingPart.Position += Vector3.new(50, 0, 0)
task.wait(1)  -- Attendre 1 seconde

-- Déplacer la Part de -50 unités sur l'axe X (gauche, retourne à la position initiale)
movingPart.Position += Vector3.new(-50, 0, 0)
task.wait(1)

-- Déplacer la Part de +50 unités sur l'axe Z (avant)
movingPart.Position += Vector3.new(0, 0, 50)
task.wait(1)

-- Déplacer la Part de -50 unités sur l'axe Z (arrière, retourne à la position initiale)
movingPart.Position += Vector3.new(0, 0, -50)
task.wait(1)

-- Déplacer la Part de +50 unités sur l'axe Y (haut)
movingPart.Position += Vector3.new(0, 50, 0)
task.wait(1)

-- Déplacer la Part de -50 unités sur l'axe Y (bas, retourne à la position initiale)
movingPart.Position += Vector3.new(0, -50, 0)
task.wait(1)

-- Faire tourner la Part de +10 degrés autour de l'axe Y (rotation vers la droite)
movingPart.Orientation += Vector3.new(0, 10, 0)
task.wait(1)

-- Faire tourner la Part de -10 degrés autour de l'axe Y (rotation vers la gauche, retourne à l'orientation initiale)
movingPart.Orientation += Vector3.new(0, -10, 0)
task.wait(1)

-- Faire tourner la Part de +10 degrés autour de l'axe Z (rotation vers le haut)
movingPart.Orientation += Vector3.new(0, 0, 10)
task.wait(1)

-- Faire tourner la Part de -10 degrés autour de l'axe Z (rotation vers le bas, retourne à l'orientation initiale)
movingPart.Orientation += Vector3.new(0, 0, -10)
task.wait(1)

-- Faire tourner la Part de +10 degrés autour de l'axe X (rotation vers l'avant)
movingPart.Orientation += Vector3.new(10, 0, 0)
task.wait(1)

-- Faire tourner la Part de -10 degrés autour de l'axe X (rotation vers l'arrière, retourne à l'orientation initiale)
movingPart.Orientation += Vector3.new(-10, 0, 0)

Explications Détaillées

1. Accéder à une Part dans le Workspace

local movementsFolder = workspace:WaitForChild("MovementsFolder")
local movingPart = movementsFolder:WaitForChild("Part")
  • workspace:WaitForChild("MovementsFolder") :
    • Attend que le dossier MovementsFolder existe dans le Workspace.
    • Si le dossier n’existe pas encore, le script attend qu’il soit créé.
  • movementsFolder:WaitForChild("Part") :
    • Attend que la Part existe dans le dossier MovementsFolder.
    • Si la Part n’existe pas encore, le script attend qu’elle soit créée.
  • Pourquoi utiliser WaitForChild ?
    • Cela évite les erreurs si le dossier ou la Part ne sont pas encore chargés quand le script s’exécute.

2. Empêcher la Part de bouger (Anchored)

movingPart.Anchored = true
  • Anchored = true :
    • Empêche la Part de bouger à cause de la gravité ou des collisions.
    • Sans cela, la Part tomberait ou serait poussée par d’autres objets.
  • Pourquoi est-ce important ?
    • Si Anchored = false, la Part serait soumise à la physique (gravité, collisions), ce qui rendrait les mouvements imprévisibles.

3. Déplacer une Part avec Position

movingPart.Position += Vector3.new(50, 0, 0)
  • Position :
    • Propriété qui stocke la position de la Part dans l’espace 3D (coordonnées X, Y, Z).
    • Exemple : Vector3.new(50, 0, 0) signifie 50 unités sur l’axe X, 0 sur Y, 0 sur Z.
  • += :
    • Ajoute un Vector3 à la position actuelle.
    • Exemple : Si movingPart.Position = Vector3.new(0, 0, 0), alors après += Vector3.new(50, 0, 0), la nouvelle position sera Vector3.new(50, 0, 0).
  • Axes dans Roblox : Axe Direction Exemple de mouvement X Gauche/Droite Vector3.new(50, 0, 0) → Déplacement vers la droite Y Haut/Bas Vector3.new(0, 50, 0) → Déplacement vers le haut Z Avant/Arrière Vector3.new(0, 0, 50) → Déplacement vers l’avant

4. Faire tourner une Part avec Orientation

movingPart.Orientation += Vector3.new(0, 10, 0)
  • Orientation :
    • Propriété qui stocke la rotation de la Part autour des axes X, Y et Z (en degrés).
    • Exemple : Vector3.new(0, 10, 0) signifie 10 degrés autour de l’axe Y.
  • += :
    • Ajoute une rotation à l’orientation actuelle.
    • Exemple : Si movingPart.Orientation = Vector3.new(0, 0, 0), alors après += Vector3.new(0, 10, 0), la nouvelle orientation sera Vector3.new(0, 10, 0).
  • Axes de rotation : Axe Rotation Exemple X Inclinaison avant/arrière Vector3.new(10, 0, 0) → Rotation vers l’avant Y Rotation gauche/droite Vector3.new(0, 10, 0) → Rotation vers la droite Z Rotation autour de l’axe vertical Vector3.new(0, 0, 10) → Rotation dans le sens horaire

5. Créer des délais avec task.wait()

task.wait(5)  -- Attendre 5 secondes
  • task.wait(durée) :
    • Pause l’exécution du script pendant durée secondes.
    • Exemple : task.wait(1) attend 1 seconde avant de continuer.
  • Pourquoi utiliser task.wait() ?
    • Pour espacer les mouvements et les rendre visibles.
    • Sans délai, les mouvements seraient trop rapides pour être vus.

QCM : Teste ta compréhension !

Sur l’accès aux objets :

  1. Que fait workspace:WaitForChild("MovementsFolder") ?
    • a) Attend que le dossier MovementsFolder existe dans le Workspace.
    • b) Crée un nouveau dossier.
    • c) Supprime le dossier.
  1. Pourquoi utilise-t-on WaitForChild ?
    • a) Pour éviter les erreurs si l’objet n’existe pas encore.
    • b) Pour accélérer le script.
    • c) Pour changer la couleur de la Part.

Sur le déplacement :

  1. Que fait movingPart.Position += Vector3.new(50, 0, 0) ?
    • a) Déplace la Part de 50 unités vers la droite (axe X).
    • b) Déplace la Part de 50 unités vers le haut (axe Y).
    • c) Déplace la Part de 50 unités vers l’avant (axe Z).
  1. Que fait Vector3.new(0, 50, 0) ?
    • a) Crée un vecteur pour déplacer la Part de 50 unités vers le haut.
    • b) Crée un vecteur pour faire tourner la Part.
    • c) Crée un vecteur pour changer la couleur de la Part.
  1. Pourquoi utilise-t-on Anchored = true ?
    • a) Pour empêcher la Part de bouger à cause de la gravité ou des collisions.
    • b) Pour rendre la Part invisible.
    • c) Pour supprimer la Part.

Sur la rotation :

  1. Que fait movingPart.Orientation += Vector3.new(0, 10, 0) ?
    • a) Fait tourner la Part de 10 degrés autour de l’axe Y (droite/gauche).
    • b) Fait tourner la Part de 10 degrés autour de l’axe X (avant/arrière).
    • c) Fait tourner la Part de 10 degrés autour de l’axe Z (sens horaire/anti-horaire).
  1. Que fait Vector3.new(10, 0, 0) pour l’orientation ?
    • a) Fait tourner la Part de 10 degrés autour de l’axe X (avant/arrière).
    • b) Fait tourner la Part de 10 degrés autour de l’axe Y (droite/gauche).
    • c) Fait tourner la Part de 10 degrés autour de l’axe Z.

Sur les délais :

  1. À quoi sert task.wait(5) ?
    • a) Attendre 5 secondes avant de continuer le script.
    • b) Accélérer le script.
    • c) Supprimer la Part.
  1. Pourquoi utilise-t-on task.wait(1) entre chaque mouvement ?
    • a) Pour espacer les mouvements et les rendre visibles.
    • b) Pour économiser de la mémoire.
    • c) Pour changer la couleur de la Part.
Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Objectif : Créé un tableau de scores

Créer un script pour afficher un tableau de score au joueur sousServerScriptService :

Renommer le script leaderstats :

Saisir le code suivant pour un affichage d’une barre de scores :

local players = game:GetService("Players")

players.PlayerAdded:Connect(function(player)
     local leaderstats = Instance.new("Folder")
     leaderstats.Name = "leaderstats"
     leaderstats.Parent = player
end)

Si tu lances ton jeu une barre s’affiche :

Si le score ne s’affiche pas vérifie :

Rajoute à ton script un score à afficher :

local players = game:GetService("Players")

players.PlayerAdded:Connect(function(player)

     local leaderstats = Instance.new("Folder")
     leaderstats.Name = "leaderstats"
     leaderstats.Parent = player

     local score = Instance.new("IntValue")
     score.Parent = leaderstats
     score.Name = "Score"
     score.Value = 0

end)

Le score s’affiche si tu lances ton jeu :

Exemple d’un script pour augmenter le score si le joueur click sur un ClickDetector :

local clickDetector = script.Parent.ClickDetector

clickDetector.MouseClick:Connect(function(player)

      player:WaitForChild("leaderstats").Score.Value += 10

end)

Exemple d’un script pour augmenter le score si le joueur rentre en collision avec un part :

local myPart = script.Parent


myPart.Touched:Connect(function(hit)

	-- Vérifie si c'est un joueur qui a touché la part
	local joueur = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
	if joueur then
		-- Trouve le score du joueur dans Leaderstats
		local leaderstats = joueur:FindFirstChild("leaderstats")
		if leaderstats then
			local score = leaderstats:FindFirstChild("Score")
			if score then
				-- Incrémente le score
				score.Value = score.Value + 1
			end
		end
	end

end)

Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Gérer des Checkpoints non Ordonnés

Les checkpoints sont des points de sauvegarde dans un jeu. Quand un joueur touche un checkpoint, sa position est enregistrée. Si le joueur meurt ou revient dans la partie, il réapparaît au dernier checkpoint touché.

Dans ce script, les checkpoints ne sont pas ordonnés : le joueur peut les toucher dans n’importe quel ordre.

  1. Comment parcourir les checkpoints dans un dossier avec une boucle for.
  2. Comment stocker le dernier checkpoint pour chaque joueur.
  3. Comment détecter quand un joueur revient dans la partie et le téléporter à son dernier checkpoint.

Préparation dans Roblox Studio

Pour tester ce script, suis ces étapes :

  1. Créer un dossier pour les checkpoints :
    • Dans Workspace, crée un dossier nommé CheckpointsFolder.
  2. Ajouter des checkpoints :
    • Ajoute plusieurs Part dans CheckpointsFolder.
    • Nomme-les comme tu veux (ex: Checkpoint1, Checkpoint2, etc.).
    • Place-les à différents endroits dans ton jeu.
  3. Ajouter le script :
    • Crée un Script dans ServerScriptService et copie le code suivant.
    • Le script supprime automatiquement la Baseplate pour éviter les interférences.
    • Si tu veux garder un sol, ajoute une Part plate dans Workspace.

Voici le script commenté pour comprendre :

-- Récupérer les services et objets nécessaires
local Players = game:GetService("Players")  -- Service pour gérer les joueurs

-- Récupérer la Baseplate et le dossier des checkpoints
local baseplate = workspace:WaitForChild("Baseplate")
local checkpointsFolder = workspace:WaitForChild("CheckpointsFolder")

-- Tableau pour stocker le dernier checkpoint de chaque joueur
-- Format : playerCheckpoints[userId] = CFrame du dernier checkpoint
local playerCheckpoints = {}

-- Supprimer la Baseplate (pour éviter les interférences)
baseplate:Destroy()

-- Fonction appelée quand un personnage est ajouté (ex: quand un joueur respawn)
local function onCharacterAdded(character, userId)
    -- Récupérer le dernier checkpoint du joueur
    local storedCFrame = playerCheckpoints[userId]

    -- Si le joueur n'a pas de checkpoint enregistré, on ne fait rien
    if not storedCFrame then
        return
    end

    -- Attendre que le HumanoidRootPart existe (partie principale du personnage)
    local humanoidRootPart = character:WaitForChild("HumanoidRootPart")

    -- Ajouter un petit décalage aléatoire pour éviter que les joueurs spawnent au même endroit
    local offset = Vector3.new(math.random(-1, 1), 4, math.random(-1, 1))

    -- Téléporter le joueur à son dernier checkpoint + décalage
    humanoidRootPart.CFrame = storedCFrame + offset
end

-- Fonction appelée quand un joueur touche un checkpoint
local function onTouched(checkpoint, hit)
    -- Récupérer le personnage qui a touché le checkpoint
    local character = hit.Parent

    -- Vérifier si c'est bien un personnage avec un Humanoid (un joueur)
    if not character or not character:FindFirstChildOfClass("Humanoid") then
        return
    end

    -- Récupérer le joueur associé au personnage
    local player = Players:GetPlayerFromCharacter(character)
    if not player then
        return
    end

    -- Récupérer l'ID unique du joueur
    local userId = player.UserId

    -- Connecter l'événement CharacterAdded UNE SEULE FOIS par joueur
    -- Cela évite de connecter plusieurs fois le même événement
    if not playerCheckpoints[userId] then
        player.CharacterAdded:Connect(function(newCharacter)
            onCharacterAdded(newCharacter, userId)
        end)
    end

    -- Stocker le CFrame (position + rotation) du checkpoint pour ce joueur
    playerCheckpoints[userId] = checkpoint.CFrame
end

-- Parcourir tous les checkpoints dans le dossier CheckpointsFolder
for _, checkpoint in ipairs(checkpointsFolder:GetChildren()) do
    -- Vérifier si l'objet est une BasePart (ex: une Part)
    if not checkpoint:IsA("BasePart") then continue end

    -- Configurer le checkpoint
    checkpoint.Anchored = true          -- Empêcher le checkpoint de bouger
    checkpoint.CanCollide = true        -- Permettre les collisions
    checkpoint.Transparency = 0.5       -- Rendre le checkpoint semi-transparent
    checkpoint.Material = Enum.Material.Neon  -- Matériau Neon pour un effet lumineux
    checkpoint.BrickColor = BrickColor.new("Bright blue")  -- Couleur bleue vive

    -- Connecter l'événement Touched pour détecter quand un joueur touche le checkpoint
    checkpoint.Touched:Connect(function(hit)
        onTouched(checkpoint, hit)
    end)
end

Le script supprime automatiquement la Baseplate.

1. La boucle for pour parcourir les checkpoints

for _, checkpoint in ipairs(checkpointsFolder:GetChildren()) do
    if not checkpoint:IsA("BasePart") then continue end
    -- Configurer le checkpoint...
end
  • checkpointsFolder:GetChildren() :
    • Récupère tous les enfants directs du dossier CheckpointsFolder.
    • Retourne une liste de tous les objets dans ce dossier.
  • ipairs(...) :
    • Parcourt la liste des enfants un par un.
    • _ est une variable temporaire (non utilisée) qui représente l’index (position dans la liste).
    • checkpoint est l’objet actuel dans la boucle (ex: une Part).
  • if not checkpoint:IsA("BasePart") then continue end :
    • Vérifie si checkpoint est une BasePart (ex: une Part).
    • Si ce n’est pas le cas (ex: un Folder, un Script), on passe à l’itération suivante avec continue.
  • Pourquoi utiliser GetChildren ?
    • On veut trouver tous les checkpoints dans le dossier CheckpointsFolder.
    • GetChildren est plus rapide que GetDescendants car on ne cherche que les enfants directs (pas les petits-enfants).

2. Stocker le checkpoint pour le joueur

local playerCheckpoints = {}  -- Tableau pour stocker les checkpoints

-- Dans la fonction onTouched :
playerCheckpoints[userId] = checkpoint.CFrame
  • playerCheckpoints :
    • C’est un tableau associatif où :
      • Clé = userId (l’ID unique du joueur).
      • Valeur = CFrame du dernier checkpoint touché par le joueur.
  • checkpoint.CFrame :
    • Le CFrame est une propriété qui stocke la position et la rotation d’un objet.
    • Quand un joueur touche un checkpoint, on stocke son CFrame dans playerCheckpoints[userId].
  • Pourquoi utiliser userId comme clé ?
    • Chaque joueur a un ID unique (UserId).
    • Cela permet de stocker un checkpoint différent pour chaque joueur.

3. Détecter quand le joueur revient dans la partie

-- Dans la fonction onTouched :
if not playerCheckpoints[userId] then
    player.CharacterAdded:Connect(function(newCharacter)
        onCharacterAdded(newCharacter, userId)
    end)
end

-- Fonction onCharacterAdded :
local function onCharacterAdded(character, userId)
    local storedCFrame = playerCheckpoints[userId]
    if not storedCFrame then return end

    local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
    local offset = Vector3.new(math.random(-1, 1), 4, math.random(-1, 1))
    humanoidRootPart.CFrame = storedCFrame + offset
end
  • player.CharacterAdded:Connect(...) :
    • Événement déclenché quand un joueur respawn (réapparaît après être mort ou avoir rejoint le jeu).
    • On connecte une seule fois cet événement par joueur (grâce à if not playerCheckpoints[userId]).
  • onCharacterAdded :
    • Cette fonction est appelée quand un joueur réapparaît.
    • Elle téléporte le joueur à son dernier checkpoint (storedCFrame).
    • offset : Ajoute un petit décalage aléatoire pour éviter que plusieurs joueurs spawnent au même endroit.
  • WaitForChild("HumanoidRootPart") :
    • Attend que le HumanoidRootPart (partie centrale du personnage) existe avant de le téléporter.
    • Cela évite les erreurs si le personnage n’est pas encore complètement chargé.

QCM : Teste ta compréhension !

Sur la boucle for et GetChildren :

  1. Que fait checkpointsFolder:GetChildren() ?
    • a) Récupère tous les enfants directs du dossier CheckpointsFolder.
    • b) Récupère tous les descendants (enfants, petits-enfants, etc.) du dossier.
    • c) Supprime tous les enfants du dossier.
  1. Pourquoi utilise-t-on if not checkpoint:IsA("BasePart") then continue end ?
    • a) Pour ignorer les objets qui ne sont pas des BasePart (ex: des Folder).
    • b) Pour supprimer les checkpoints.
    • c) Pour changer la couleur des checkpoints.
  1. Que contient la variable checkpoint dans la boucle for ?
    • a) Un objet (ex: une Part) dans le dossier CheckpointsFolder.
    • b) Un nombre.
    • c) Un joueur.

Sur le stockage des checkpoints :

  1. À quoi sert playerCheckpoints[userId] = checkpoint.CFrame ?
    • a) Stocker la position et la rotation du checkpoint pour un joueur.
    • b) Supprimer le checkpoint.
    • c) Changer la taille du checkpoint.
  1. Pourquoi utilise-t-on userId comme clé dans playerCheckpoints ?
    • a) Parce que chaque joueur a un ID unique, ce qui permet de stocker un checkpoint différent pour chaque joueur.
    • b) Parce que c’est plus rapide.
    • c) Parce que c’est obligatoire.
  1. Que fait playerCheckpoints[userId] si userId n’existe pas dans le tableau ?
    • a) Retourne nil.
    • b) Crée une erreur.
    • c) Retourne 0.

Sur la détection du retour du joueur :

  1. À quoi sert player.CharacterAdded:Connect(...) ?
    • a) Exécuter une fonction quand un joueur réapparaît dans la partie.
    • b) Créer un nouveau personnage.
    • c) Supprimer le personnage.
  1. Pourquoi utilise-t-on WaitForChild("HumanoidRootPart") ?
    • a) Pour attendre que le HumanoidRootPart existe avant de le téléporter.
    • b) Pour supprimer le HumanoidRootPart.
    • c) Pour changer la couleur du personnage.
  1. Que fait humanoidRootPart.CFrame = storedCFrame + offset ?
    • a) Téléporte le joueur à son dernier checkpoint avec un petit décalage aléatoire.
    • b) Fait grandir le joueur.
    • c) Supprime le joueur.
  1. Pourquoi ajoute-t-on un offset aléatoire ?
    • a) Pour éviter que plusieurs joueurs spawnent exactement au même endroit.
    • b) Pour rendre le jeu plus difficile.
    • c) Pour changer la taille du joueur.

Idées pour aller plus loin

1. Ajouter un effet visuel aux checkpoints

  • Objectif : Faire clignoter les checkpoints quand un joueur les touche.

2. Ajouter un son quand un checkpoint est touché

  • Objectif : Jouer un son quand un joueur touche un checkpoint.