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.