- 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 :
| Technique | Comment ça marche | Le joueur est transporté ? |
|---|---|---|
Tutoriel 1 : part.Position avec Anchored = true | On téléporte la Part | Non ! 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
Partancré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.
| Aspect | Tutoriel 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 :
- Comment utiliser
RunService.Heartbeatpour des mouvements fluides. - Comment créer une fonction paramétrable pour contrôler le déplacement.
- Comment calculer la distance parcourue avec
Magnitude.
Préparation dans Roblox Studio
Pour tester ce script, suis ces étapes :
- Créer un dossier :
- Dans
Workspace, crée un dossier nomméMovementsFolder.
- Dans
- Ajouter une
Part:- Ajoute une
PartdansFolder. - Nomme-la
Part(ou modifie le nom dans le script).
- Ajoute une
- Configurer la
Part:- Assure-toi que la
Parta une taille visible (ex:Size = Vector3.new(4, 4, 4)). - Place-la à une position centrale (ex:
Position = Vector3.new(0, 10, 0)).
- Assure-toi que la
- Ajouter le script :
- Crée un
ScriptdansServerScriptServiceet copie-y le code fourni.
- Crée un
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
Partancré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 :
- Comment stocker les paramètres de chaque ascenseur dans un tableau (une fiche d’identité par ascenseur !).
- La boucle
for ... in pairs()pour gérer plusieurs ascenseurs en même temps avec un seulHeartbeat. - La variable
elapsed(temps écoulé) pour savoir où en est l’ascenseur. - La direction (
1,0,-1) pour piloter montée, pause et descente. - 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 :
- Montée (direction = 1)
- Pause à l’étage (direction = 0) — les portes s’ouvrent !
- Descente (direction = -1)
- Pause en bas (direction = 0)
- 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,
}
elevatorsest un tableau associatif dont la clé est laPart: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
cooldownsdes 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)) →speedvautnil, etnil 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
ipairspour les listes numérotées (1, 2, 3…). pairsparcourt un tableau associatif : il donne successivement chaque clé (part) et chaque valeur (properties).- Avantage énorme : un seul
Heartbeatmet à 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 :
direction | Signification | Effet sur le mouvement | Effet sur elapsed |
|---|---|---|---|
1 | Montée | Avance à pleine vitesse | Augmente |
0 | Pause | Immobile (vitesse × 0 = 0) | Ne bouge plus |
‘-1 | Descente | Avance « à 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
elapsedcompte le temps écoulé depuis le bas :- Pendant la montée :
elapsedaugmente (0 → 1 → 2 → … →duration). - Pendant la descente :
elapseddiminue (duration→ … → 2 → 1 → 0). - Pendant la pause :
elapsedne change pas (cardirection = 0).
- Pendant la montée :
- Deux conditions de retournement :luaCopier
if 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 avectask.delay. - Pourquoi pas
task.wait? On est dans une boucleHeartbeat! Untask.wait(3)bloquerait toute la boucle pendant 3 secondes… et tous les autres ascenseurs seraient figés aussi !task.delayprogramme 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 bouclesHeartbeatactives en même temps. - Résultat catastrophique : chaque frame déplacerait les ascenseurs deux fois → vitesse doublée,
elapseddoublé, chaos total ! - La protection : si
connectionexiste déjà (pasnil), la fonction s’arrête immédiatement. Un seulHeartbeatpossible, 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
Partest 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 :
- 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.
- 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.
- a) Si aucune vitesse n’est fournie, on utilise la vitesse par défaut
- 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 :
- 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.
- a) Parcourt le tableau associatif et donne chaque clé (
- Quelle est la différence entre
ipairsetpairs?- a)
ipairsparcourt les listes numérotées (1, 2, 3…),pairsparcourt les tableaux avec n’importe quelle clé (comme des Parts). - b)
ipairsest plus rapide. - c)
pairsne fonctionne qu’avec des joueurs.
- a)
Sur la direction :
- Que se passe-t-il quand
properties.direction = -1?- a) L’ascenseur descend (vitesse multipliée par -1) et
elapseddiminue. - b) L’ascenseur s’arrête.
- c) L’ascenseur double de vitesse.
- a) L’ascenseur descend (vitesse multipliée par -1) et
- Pourquoi un seul calcul
speed * direction * deltaTimegè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 :
- À 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.
- 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 :
- Pourquoi utilise-t-on
task.delayplutôt quetask.waitpour la pause ?- a)
task.waitbloquerait la boucle Heartbeat et figerait TOUS les ascenseurs ;task.delayprogramme la suite sans rien bloquer. - b)
task.delayest plus court à écrire. - c)
task.waitn’existe plus.
- a)
- Que risque-t-on si on supprime la ligne
if connection then return endet qu’on appellestartMovementdeux 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.
- Pourquoi met-on
AssemblyLinearVelocityà zéro quanddirection = 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.
