Catégories
Jeu vidéo ROBLOX

Comment déplacer des parts

Présenter Roblox Studio, l’objectif du jeu, et faire le lien entre les espaces vectoriels en maths et les objets 3D dans Roblox.
Concepts clés : Espace vectoriel, coordonnées 3D, vecteurs.

Explications

1. Qu’est-ce qu’un espace vectoriel ? (Rappel maths)

En mathématiques, un espace vectoriel est un ensemble où on peut :

  • Additionner des vecteurs (ex: (x1, y1, z1) + (x2, y2, z2) = (x1+x2, y1+y2, z1+z2)).
  • Multiplier un vecteur par un nombre (ex: 2 * (x, y, z) = (2x, 2y, 2z)).
  • Représenter des points dans l’espace avec des coordonnées (x, y, z).

Exemple concret en 2D :
Si tu as un point à (2, 3) et que tu le déplaces de (1, -1), il arrive à (3, 2).

2. Roblox comme espace vectoriel

Dans Roblox, tout est un vecteur :

  • La position d’un objet est un vecteur (X, Y, Z) (en studs, l’unité de mesure de Roblox).
  • Le déplacement d’un objet utilise des vecteurs (ex: part.Position = part.Position + Vector3.new(1, 0, 0) déplace l’objet de 1 stud vers la droite).
  • Les directions sont aussi des vecteurs (ex: Vector3.new(0, 1, 0) = vers le haut).

Comparaison maths ↔ Roblox :

Concept en mathsÉquivalent dans RobloxExemple
Vecteur (x, y, z)Vector3.new(x, y, z)Vector3.new(1, 0, 0) = 1 stud vers la droite
Addition de vecteursvector1 + vector2Vector3.new(1, 0, 0) + Vector3.new(0, 1, 0) = Vector3.new(1, 1, 0)
Position d’un pointpart.Positionpart.Position = Vector3.new(5, 2, 0)
Déplacementpart.Position += vectorpart.Position += Vector3.new(0, 1, 0) (monte de 1 stud)

3. Objectif du jeu

Tu vas créer un jeu où :

  1. Le joueur entre dans une zone de jeu (hitbox).
  2. Une interface apparaît avec des boutons pour déplacer un bloc dans les 3 dimensions (haut/bas, gauche/droite, avant/arrière).
  3. Le bloc doit toucher des clés de couleurs spécifiques dans l’ordre pour ouvrir une porte.
  4. Quand toutes les clés sont collectées, la porte s’ouvre et le joueur gagne !

Pourquoi c’est intéressant ?

  • Tu vas visualiser les vecteurs en 3D.
  • Tu vas manipuler des coordonnées comme en maths, mais de manière interactive.
  • Tu vas comprendre comment les jeux vidéo utilisent les maths pour les déplacements.

QCM / Questions

1. Qu’est-ce qu’un vecteur en maths ?

  • a) Un nombre.
  • b) Une flèche avec une direction et une longueur (ex: (x, y, z)).
  • c) Une couleur.

2. Dans Roblox, comment représente-t-on un vecteur en 3D ?

  • a) Vector2.new(x, y)
  • b) Position.new(x, y, z)
  • c) Vector3.new(x, y, z)

3. Si un objet est à la position (2, 3, 0) et qu’on ajoute Vector3.new(1, -1, 0), où sera-t-il ?

  • a) (3, 2, 0)
  • b) (1, 2, 0)
  • c) (2, 4, 0)

Documentation Roblox Lua :


Étape 1 : Comprendre le jeu avant de coder

But : Visualiser le jeu et comprendre son fonctionnement.
Concepts clés : Logique de jeu, objets 3D, vecteurs.

Explications

Scénario du jeu :

  1. Le joueur entre dans une zone de jeu (hitbox).
  2. Une interface avec 6 boutons apparaît :
    • Haut / Bas : Déplace le bloc sur l’axe Y (vertical).
    • Gauche / Droite : Déplace le bloc sur l’axe Z (profondeur).
    • Avant / Arrière : Déplace le bloc sur l’axe X (horizontal).
  3. Le bloc doit toucher des clés colorées dans un ordre précis (ex: rouge → bleu → vert).
  4. Quand toutes les clés sont touchées, la porte s’ouvre !

Objets nécessaires :

ObjetRôlePosition exemple
HitBoxZone où le joueur peut interagirZone invisible
Part (bloc mobile)Bloc à déplacer pour toucher les clésPart fluo
WallPorte à ouvrirMur de séparation
Key1, Key2, Key3Clés à collecter (couleurs : rouge, bleu, vert)Parts de couleurs à mettre sur le mur
ScreenGuiInterface avec les boutonsDans StarterGui

À toi de jouer !

  1. Dans Roblox Studio, crée la structure suivante :
    • Un dossier VectorSpace dans Workspace.
    • Dans ce dossier, ajoute :
      • Une Part nommée HitBox (taille : 10, 1, 10, ancrée, transparence : 0.5).
      • Une Part nommée Part (le bloc mobile, taille : 2, 2, 2, couleur : rouge, ancrée).
      • Une Part nommée Wall (la porte, taille : 10, 5, 1, ancrée, couleur : gris).
      • 3 Parts nommées Key1, Key2, Key3 (tailles : 1, 1, 1, couleurs : rouge, bleu, vert, ancrées).
  2. Positionne les objets comme dans le tableau ci-dessus.
  3. Teste : Déplace-toi dans la HitBox et imagine que tu dois déplacer le bloc rouge pour toucher les clés.

QCM / Questions

1. Quel axe utilise-t-on pour monter/descendre ?

  • a) X
  • b) Y
  • c) Z

2. Quel axe utilise-t-on pour aller à gauche/droite ?

  • a) X
  • b) Y
  • c) Z

3. Quel axe utilise-t-on pour avancer/reculer ?

  • a) X
  • b) Y
  • c) Z

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

But : Organiser les objets du jeu dans l’Explorateur pour faciliter la programmation.
Concepts clés : Hiérarchie des objets, organisation.

Explications

Dans Roblox, les objets sont organisés comme un arbre :

  • Workspace : Contient tous les objets visibles dans le jeu (hitbox, bloc mobile, clés, mur).
  • ServerScriptService : Contient les scripts côté serveur (pour la logique du jeu).
  • StarterGui : Contient les interfaces utilisateur (boutons pour déplacer le bloc).
  • ReplicatedStorage : Contient les modèles réutilisables (non utilisé ici, mais utile pour cloner des objets).

Structure recommandée :

Workspace
├── VectorSpace (Folder)
│   ├── HitBox (Part)
│   ├── Part (Part, bloc mobile)
│   ├── Wall (Part, porte)
│   ├── Key1 (Part, clé rouge)
│   ├── Key2 (Part, clé bleue)
│   └── Key3 (Part, clé verte)
StarterGui
└── ScreenGui (Folder)
    └── VectorSpace (Frame)
        ├── UpButton (TextButton)
        ├── DownButton (TextButton)
        ├── LeftButton (TextButton)
        ├── RightButton (TextButton)
        ├── FrontButton (TextButton)
        └── BackButton (TextButton)
ServerScriptService
└── VectorSpaceScript (Script)

À toi de jouer !

  1. Crée la structure ci-dessus dans Roblox Studio.
  2. Pour l’interface :
    • Dans StarterGui, ajoute un ScreenGui.
    • Dans le ScreenGui, ajoute un Frame nommé VectorSpace (taille : 300, 200).
    • Dans le Frame, ajoute 6 TextButtons (noms : UpButton, DownButton, etc.).
    • Positionne les boutons comme ceci :
      • Haut : En haut au centre.
      • Bas : En bas au centre.
      • Gauche : À gauche au centre.
      • Droite : À droite au centre.
      • Avant : En bas à gauche.
      • Arrière : En bas à droite.
  3. Teste : Vérifie que tous les objets sont bien placés.

QCM / Questions

Où doit-on placer les boutons de déplacement ?

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

Quel objet contient le bloc mobile (Part) ?

  • a) Workspace > VectorSpace
  • b) StarterGui
  • c) ServerScriptService

Étape 3 : Détecter l’arrivée du joueur dans la hitbox

But : Détecter quand un joueur entre dans la zone de jeu et activer l’interface.
Concepts clés : Événements, fonctions, détection de collision.

Explications

Pour détecter quand un joueur entre dans la HitBox, on utilise l’événement .Touched.
Quand un joueur touche la hitbox :

  1. On vérifie que c’est bien un personnage de joueur (et pas un autre objet).
  2. On active l’interface pour ce joueur.
  3. On stocke l’heure d’entrée pour éviter les bugs (ex: si le joueur entre/sort rapidement).

Pourquoi utiliser .Touched ?

  • C’est l’événement standard pour détecter les collisions dans Roblox.
  • Il se déclenche quand une partie du personnage (bras, jambes, tête) touche la hitbox.

Code Lua

Crée un Script nommé VectorSpaceScript dans ServerScriptService et copie ce code :

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

-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5  -- Délai pour éviter les faux positifs à la sortie

-- ==================== RÉFÉRENCES WORKSPACE ====================
local vectorSpaceGameFolder = Workspace:WaitForChild("VectorSpace")
local hitbox = vectorSpaceGameFolder:WaitForChild("HitBox")
local movablePart = vectorSpaceGameFolder:WaitForChild("Part")

-- ==================== ÉTAT DU JEU ====================
local playerEntryTimestamps = {}  -- [Player] = tick() au moment de l'entrée dans la zone

-- ==================== FONCTIONS UTILITAIRES ====================
-- Récupère le joueur à partir d'une partie touchée
local function getPlayerFromTouchedPart(hit)
	local character = hit.Parent
	if not character then return nil end

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

	return Players:GetPlayerFromCharacter(character)
end

-- ==================== ÉVÉNEMENTS DE COLLISION ====================
hitbox.Touched:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end

	-- Anti-spam : ignore si le joueur est déjà dans la zone
	if playerEntryTimestamps[player] then return end
	playerEntryTimestamps[player] = tick()

	print(player.Name .. " est entré dans la zone !")
end)

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

	-- Vérifie que le joueur est bien dans la zone
	if not playerEntryTimestamps[player] then return end

	-- Attend un délai pour éviter les faux positifs (ex: si le joueur touche encore la hitbox avec une autre partie du corps)
	task.wait(EXIT_CONFIRM_DELAY)

	-- Vérifie qu'aucune partie du personnage ne touche encore la hitbox
	local character = player.Character
	local stillTouching = false
	if character then
		for _, part in pairs(hitbox:GetTouchingParts()) do
			if part.Parent == character then
				stillTouching = true
				break
			end
		end
	end

	if stillTouching then return end
	if not playerEntryTimestamps[player] then return end
	
	-- Le joueur a vraiment quitté la zone
	playerEntryTimestamps[player] = nil
	print(player.Name .. " a quitté la zone.")
end)

Explications du code

LigneExplication
Players = game:GetService("Players")Récupère le service Players pour accéder aux joueurs.
hitbox.Touched:Connect(...)Écoute l’événement Touched sur la hitbox.
getPlayerFromTouchedPart(hit)Vérifie si l’objet qui a touché est un personnage de joueur.
playerEntryTimestamps[player] = tick()Stocke l’heure d’entrée du joueur dans la zone.
hitbox.TouchEnded:Connect(...)Écoute l’événement TouchEnded pour détecter quand le joueur quitte la hitbox.
hitbox:GetTouchingParts()Récupère toutes les parties qui touchent encore la hitbox.
task.wait(EXIT_CONFIRM_DELAY)Attend 0.5 seconde pour éviter les faux positifs.

À toi de jouer !

  1. Copie le code dans le Script VectorSpaceScript.
  2. Teste :
    • Entre dans la hitbox avec ton personnage.
    • Ouvre la fenêtre de sortie (Affichage > Sortie) pour voir les messages.
  3. Modifie :
    • Change EXIT_CONFIRM_DELAY à 1 pour voir l’effet.
    • Ajoute un print pour afficher le nombre de joueurs dans la zone.

QCM / Questions

1. À quoi sert hitbox.Touched:Connect(...) ?
a) À détruire la hitbox.
b) À exécuter du code quand un objet touche la hitbox.
c) À changer la couleur de la hitbox.
Réponse : b)

2. Pourquoi utilise-t-on playerEntryTimestamps[player] ?
a) Pour stocker l’heure d’entrée du joueur dans la zone.
b) Pour changer le nom du joueur.
c) Pour supprimer le joueur.
Réponse : a)

3. Que fait hitbox:GetTouchingParts() ?
a) Récupère toutes les parties qui touchent la hitbox.
b) Détruit la hitbox.
c) Change la taille de la hitbox.
Réponse : a)

Pour aller plus loin

  • Affiche un message à l’écran quand le joueur entre dans la zone (utilise un ScreenGui et un TextLabel).
  • Joue un son quand le joueur entre dans la zone.

Documentation Roblox Lua :


Étape 4 : Créer une interface utilisateur pour déplacer le bloc

But : Afficher une interface avec des boutons pour déplacer le bloc dans les 3 dimensions.
Concepts clés : ScreenGui, TextButton.

Explications

Pour créer une interface utilisateur (GUI), on utilise :

  • Un ScreenGui : Conteneur pour les éléments d’interface.
  • Un Frame : Cadre pour regrouper les boutons.
  • Des TextButtons : Boutons cliquables.
  • Un PlayerGuit : Dossier propre à chaque joueur qui contient les interfaces graphiques

Pourquoi un LocalScript ?

  • Les interfaces utilisateur sont spécifiques à chaque joueur (chacun voit la sienne).
  • Récupération du dossier PlayerGui côté serveur (pour un joueur spécifique), évitant d’utiliser un LocalScript qui s’exécute côté client. Cela permet notamment de centraliser la gestion de l’interface et de décider quand elle apparaît.

Code Lua

  1. Modifie le code du script serveur : :
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")

-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5  -- Délai pour éviter les faux positifs à la sortie

-- ==================== RÉFÉRENCES WORKSPACE ====================
local vectorSpaceGameFolder = Workspace:WaitForChild("VectorSpace")
local hitbox = vectorSpaceGameFolder:WaitForChild("HitBox")
local movablePart = vectorSpaceGameFolder:WaitForChild("Part")

-- ==================== ÉTAT DU JEU ====================
local playerEntryTimestamps = {}  -- [Player] = tick() au moment de l'entrée dans la zone

-- ==================== FONCTIONS UTILITAIRES ====================
-- Récupère le joueur à partir d'une partie touchée
local function getPlayerFromTouchedPart(hit)
	local character = hit.Parent
	if not character then return nil end

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

	return Players:GetPlayerFromCharacter(character)
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiElements(player)
	local playerGui = player:WaitForChild("PlayerGui")
	local screenGui = playerGui:WaitForChild("ScreenGui")
	local vectorSpaceFrame = screenGui:WaitForChild("VectorSpace")

	return vectorSpaceFrame
end

-- ==================== ÉVÉNEMENTS DE COLLISION ====================
hitbox.Touched:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end

	-- Anti-spam : ignore si le joueur est déjà dans la zone
	if playerEntryTimestamps[player] then return end
	playerEntryTimestamps[player] = tick()
	
	local vectorSpaceFrame = getPlayerGuiElements(player)
	if not vectorSpaceFrame then return end
	
	vectorSpaceFrame.Visible = true
	
end)

hitbox.TouchEnded:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end
	if not playerEntryTimestamps[player] then return end
	
	-- Attend un délai pour éviter les faux positifs (ex: si le joueur touche encore la hitbox avec une autre partie du corps)
	task.wait(EXIT_CONFIRM_DELAY)
	-- Vérifie qu'aucune partie du personnage ne touche encore la hitbox
	local character = player.Character
	local stillTouching = false
	if character then
		for _, part in pairs(hitbox:GetTouchingParts()) do
			if part.Parent == character then
				stillTouching = true
				break
			end
		end
	end

	if stillTouching then return end

	if not playerEntryTimestamps[player] then return end
	
	-- Le joueur a vraiment quitté la zone
	playerEntryTimestamps[player] = nil
	
	local vectorSpaceFrame = getPlayerGuiElements(player)
	if not vectorSpaceFrame then return end

	vectorSpaceFrame.Visible = false
	
end)
-- Interface invisible au lancement du joueur
Players.PlayerAdded:Connect(function(player)
	-- Aucune action nécessaire pour les joueurs déjà connectés
	local vectorSpaceFrame = getPlayerGuiElements(player)
	if not vectorSpaceFrame then return end

	vectorSpaceFrame.Visible = false
end)

Pour l’instant, l’interface est masquée par défaut. On va la lier au Script serveur dans l’étape suivante.

À toi de jouer !

  1. Modifie le script serveur dans ServerScriptService.
  2. Teste :
    • Lance le jeu : l’interface ne doit pas être visible.
  3. Personnalise l’interface :
    • Change la couleur de fond du Frame (BackgroundColor3).
    • Change le texte des boutons (Text).
    • Change la taille des boutons (Size).

Pour aller plus loin

  • Ajoute des icônes aux boutons (utilise ImageLabel).
  • Ajoute un son quand un bouton est cliqué.

Documentation Roblox Lua :


Étape 6 : Lier l’interface au Script serveur et déplacer le bloc

But : Déplacer le bloc avec les boutons et vérifier les collisions possibles.
Concepts clés :

Explications

Pour déplacer le bloc, on utilise des vecteurs :

  • Haut/Bas : Vector3.new(0, MOVE_STEP, 0) (axe Y).
  • Gauche/Droite : Vector3.new(0, 0, MOVE_STEP) (axe Z).
  • Avant/Arrière : Vector3.new(MOVE_STEP, 0, 0) (axe X).

Pour détecter les collisions, puisque les blocs sont Anchored et ROBLOX ne gère pas les collisions entre les blocs Anchored :

  • Raycast
  • Blockcast

Code Lua

1. Modifie le Script serveur (VectorSpaceScript)

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

-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5  -- Délai pour éviter les faux positifs à la sortie
local MOVE_STEP = 1  -- Distance parcourue par clic (en studs)

-- ==================== RÉFÉRENCES WORKSPACE ====================
local vectorSpaceGameFolder = Workspace:WaitForChild("VectorSpace")
local hitbox = vectorSpaceGameFolder:WaitForChild("HitBox")
local movablePart = vectorSpaceGameFolder:WaitForChild("Part")
local wall = vectorSpaceGameFolder:WaitForChild("Wall")

-- ==================== ÉTAT DU JEU ====================
local playerEntryTimestamps = {}  -- [Player] = tick() au moment de l'entrée dans la zone
local playerButtonConnections = {}  -- [Player] = liste des connexions de boutons

-- ==================== RAYCAST (empêche de traverser les murs) ====================
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {hitbox}

-- ==================== DÉTECTION DE COLLISION ====================
-- Touched ne se déclenche pas entre deux parts Anchored : on vérifie donc manuellement les chevauchements
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
overlapParams.FilterDescendantsInstances = {hitbox, movablePart}

-- ==================== FONCTIONS UTILITAIRES ====================
-- Récupère le joueur à partir d'une partie touchée
local function getPlayerFromTouchedPart(hit)
	local character = hit.Parent
	if not character then return nil end

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

	return Players:GetPlayerFromCharacter(character)
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiElements(player)
	local playerGui = player:WaitForChild("PlayerGui")
	local screenGui = playerGui:WaitForChild("ScreenGui")
	local vectorSpaceFrame = screenGui:WaitForChild("VectorSpace")

	return vectorSpaceFrame
end

-- Déplace la Part si le raycast ne détecte aucun obstacle sur le trajet
local function moveMovablePart(movablePart, axisVector, directionSign)
	if not movablePart then return end

	local moveOffset = axisVector * directionSign
	local rayOrigin = movablePart.Position + ((movablePart.Size / 2) * directionSign)

	local result = 	workspace:Raycast(rayOrigin, moveOffset, raycastParams) 
	if result then return end  -- Un obstacle bloque le trajet

	movablePart.Position = movablePart.Position + moveOffset
	print("Bloc déplacé à :", movablePart.Position)
end

-- Nettoie les connexions de boutons d'un joueur
local function disconnectPlayerButtons(player)
	local connections = playerButtonConnections[player]
	if not connections then return end
	for _, connection in ipairs(connections) do
		connection:Disconnect()
	end
	playerButtonConnections[player] = nil
end

--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiButtons(vectorSpaceFrame)

	local upButton = vectorSpaceFrame:WaitForChild("UpButton")
	local downButton = vectorSpaceFrame:WaitForChild("DownButton")
	local leftButton = vectorSpaceFrame:WaitForChild("LeftButton")
	local rightButton = vectorSpaceFrame:WaitForChild("RightButton")
	local backButton = vectorSpaceFrame:WaitForChild("BackButton")
	local frontButton = vectorSpaceFrame:WaitForChild("FrontButton")

	return upButton, downButton, leftButton, rightButton, backButton, frontButton
end
-- Met en place les contrôles de déplacement pour un joueur
local function setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
	if not player then return end
	if not vectorSpaceFrame then return end
	-- On récupère les boutons du joueur 
	local upButton, downButton, leftButton, rightButton, backButton, frontButton = getPlayerGuiButtons(vectorSpaceFrame)
	-- On nettoie d'éventuelles anciennes connexions avant d'en recréer
	disconnectPlayerButtons(player)

	local connections = {}

	table.insert(connections, downButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), -1)
	end))

	table.insert(connections, upButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), 1)
	end))

	table.insert(connections, leftButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), -1)
	end))

	table.insert(connections, rightButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), 1)
	end))

	table.insert(connections, backButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), 1)
	end))

	table.insert(connections, frontButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), -1)
	end))

	playerButtonConnections[player] = connections
end


-- ==================== ÉVÉNEMENTS DE COLLISION ====================
hitbox.Touched:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end

	-- Anti-spam : ignore si le joueur est déjà dans la zone
	if playerEntryTimestamps[player] then return end
	playerEntryTimestamps[player] = tick()
	
	local vectorSpaceFrame = getPlayerGuiElements(player)
	if not vectorSpaceFrame then return end
	
	vectorSpaceFrame.Visible = true
	
	setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
	
end)

hitbox.TouchEnded:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end
	if not playerEntryTimestamps[player] then return end
	
	-- Attend un délai pour éviter les faux positifs (ex: si le joueur touche encore la hitbox avec une autre partie du corps)
	task.wait(EXIT_CONFIRM_DELAY)
	-- Vérifie qu'aucune partie du personnage ne touche encore la hitbox
	local character = player.Character
	local stillTouching = false
	if character then
		for _, part in pairs(hitbox:GetTouchingParts()) do
			if part.Parent == character then
				stillTouching = true
				break
			end
		end
	end

	if stillTouching then return end

	if not playerEntryTimestamps[player] then return end
	
	-- Le joueur a vraiment quitté la zone
	playerEntryTimestamps[player] = nil
	
	local vectorSpaceFrame = getPlayerGuiElements(player)
	if not vectorSpaceFrame then return end

	vectorSpaceFrame.Visible = false
	
end)
-- Interface invisible au lancement du joueur
Players.PlayerAdded:Connect(function(player)
	-- Aucune action nécessaire pour les joueurs déjà connectés
	local vectorSpaceFrame = getPlayerGuiElements(player)
	if not vectorSpaceFrame then return end

	vectorSpaceFrame.Visible = false
end)

Explications du code

Script serveur :

LigneExplication
moveMovablePart(axisVector, directionSign)Déplace le bloc dans la direction spécifiée, si aucun obstacle ne bloque le chemin.
RaycastVérifie s’il y a un obstacle entre la position actuelle et la nouvelle position.
moveEvent.OnServerEvent:Connect(...)Écoute les demandes de déplacement envoyées par le client.

À toi de jouer !

  1. Copie le code dans le Script serveur
  2. Teste :
    • Entre dans la hitbox : l’interface doit apparaître.
    • Clique sur les boutons : le bloc doit se déplacer.
    • Quitte la hitbox : l’interface doit disparaître.
  3. Modifie :
    • Change MOVE_STEP à 2 pour que le bloc se déplace plus vite.
    • Ajoute un print dans la fonction moveMovablePart pour afficher la nouvelle position.

QCM / Questions

1. Pourquoi utilise-t-on Raycast dans moveMovablePart ?

  • a) Pour vérifier s’il y a un obstacle entre la position actuelle et la nouvelle position.
  • b) Pour changer la couleur du bloc.
  • c) Pour détruire le bloc.

Pour aller plus loin

  • Ajoute un effet visuel quand le bloc se déplace (ex: une traînée de particules).
  • Limite les déplacements pour que le bloc ne sorte pas de la hitbox.

Documentation Roblox Lua :


Étape 7 : Détecter les collisions avec les clés (Raycast et OverlapParams)

But : Détecter quand le bloc mobile touche une clé, et gérer la logique de collecte.
Concepts clés : OverlapParams, GetPartsInPart, conditions, tableaux.

Explications

Problème :

  • L’événement .Touched ne fonctionne pas entre deux Parts ancrées (comme notre bloc mobile et les clés).
  • On doit donc vérifier manuellement si le bloc touche une clé après chaque déplacement.

Solution :

  1. Utiliser workspace:GetPartsInPart(movablePart, overlapParams) pour récupérer toutes les parties qui touchent le bloc mobile.
  2. Parcourir les parties retournées et vérifier si l’une d’elles est une clé.
  3. Si c’est une clé, vérifier sa couleur et la comparer à la prochaine couleur attendue (KEY_COLORS[1]).
  4. Si la couleur correspond, supprimer la clé et passer à la suivante.

Pourquoi GetPartsInPart ?

  • Cette fonction retourne toutes les parties qui chevauchent (overlap) une partie donnée.
  • On peut filtrer les résultats avec OverlapParams pour exclure certaines parties (ex: la hitbox).

Code Lua

Ajoute ce code au Script serveur (VectorSpaceScript) :

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

-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5  -- Délai pour éviter les faux positifs à la sortie
local MOVE_STEP = 1  -- Distance parcourue par clic (en studs)
-- Couleurs des clés à collecter, dans l'ordre où elles doivent être ramassées
local KEY_COLORS = {
	Color3.fromRGB(255, 0, 0),  -- Rouge
	Color3.fromRGB(0, 0, 255),   -- Bleu
	Color3.fromRGB(0, 255, 0),  -- Vert
}

-- ==================== RÉFÉRENCES WORKSPACE ====================
local vectorSpaceGameFolder = Workspace:WaitForChild("VectorSpace")
local hitbox = vectorSpaceGameFolder:WaitForChild("HitBox")
local movablePart = vectorSpaceGameFolder:WaitForChild("Part")
local wall = vectorSpaceGameFolder:WaitForChild("Wall")

-- ==================== ÉTAT DU JEU ====================
local playerEntryTimestamps = {}  -- [Player] = tick() au moment de l'entrée dans la zone
local playerButtonConnections = {}  -- [Player] = liste des connexions de boutons

-- ==================== RAYCAST (empêche de traverser les murs) ====================
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {hitbox}

-- ==================== DÉTECTION DE COLLISION ====================
-- Touched ne se déclenche pas entre deux parts Anchored : on vérifie donc manuellement les chevauchements
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
overlapParams.FilterDescendantsInstances = {hitbox}

-- ==================== FONCTIONS UTILITAIRES ====================
-- Récupère le joueur à partir d'une partie touchée
local function getPlayerFromTouchedPart(hit)
	local character = hit.Parent
	if not character then return nil end

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

	return Players:GetPlayerFromCharacter(character)
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiElements(player)
	local playerGui = player:WaitForChild("PlayerGui")
	local screenGui = playerGui:WaitForChild("ScreenGui")
	local vectorSpaceFrame = screenGui:WaitForChild("VectorSpace")

	return vectorSpaceFrame
end
-- Ouvre la porte
local function openWall()
	if wall then
		wall:Destroy()
		movablePart:Destroy()
		movablePart = nil
		print("La porte s'ouvre !")
	end
end
-- Vérifie si le bloc mobile touche une clé, et déclenche la logique de collecte
local function checkMovablePartCollisions()
	if not movablePart then return end
	print("test de collision")
	local touchingParts = Workspace:GetPartsInPart(movablePart, overlapParams)
	print(touchingParts)
	for _, touchingPart in ipairs(touchingParts) do
		print(touchingPart.Name)
		-- On ne réagit qu'à la clé actuellement attendue (la première de la liste)
		if touchingPart.Name == "Key" and touchingPart.Color == KEY_COLORS[1] then
			touchingPart:Destroy()  -- Supprime la clé
			table.remove(KEY_COLORS, 1)  -- Retire la couleur de la liste

			if #KEY_COLORS > 0 then
				-- Il reste des clés à collecter : on indique la prochaine couleur attendue
				movablePart.Color = KEY_COLORS[1]
				print("Prochaine clé :", KEY_COLORS[1])
			else
				-- Toutes les clés sont collectées : victoire !
				print("Toutes les clés ont été collectées !")
				openWall()
			end
			break  -- On sort de la boucle car on a trouvé une clé
		end
	end
end


-- Déplace la Part si le raycast ne détecte aucun obstacle sur le trajet
local function moveMovablePart(movablePart, axisVector, directionSign)
	if not movablePart then return end

	local moveOffset = axisVector * directionSign
	local rayOrigin = movablePart.Position + ((movablePart.Size / 2) * directionSign)

	local result = 	workspace:Raycast(rayOrigin, moveOffset, raycastParams) 
	
	if result then return end  -- Un obstacle bloque le trajet

	movablePart.Position = movablePart.Position + moveOffset
	checkMovablePartCollisions()
end

-- Nettoie les connexions de boutons d'un joueur
local function disconnectPlayerButtons(player)
	local connections = playerButtonConnections[player]
	if not connections then return end
	for _, connection in ipairs(connections) do
		connection:Disconnect()
	end
	playerButtonConnections[player] = nil
end

--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiButtons(vectorSpaceFrame)

	local upButton = vectorSpaceFrame:WaitForChild("UpButton")
	local downButton = vectorSpaceFrame:WaitForChild("DownButton")
	local leftButton = vectorSpaceFrame:WaitForChild("LeftButton")
	local rightButton = vectorSpaceFrame:WaitForChild("RightButton")
	local backButton = vectorSpaceFrame:WaitForChild("BackButton")
	local frontButton = vectorSpaceFrame:WaitForChild("FrontButton")

	return upButton, downButton, leftButton, rightButton, backButton, frontButton
end
-- Met en place les contrôles de déplacement pour un joueur
local function setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
	if not player then return end
	if not vectorSpaceFrame then return end
	-- On récupère les boutons du joueur 
	local upButton, downButton, leftButton, rightButton, backButton, frontButton = getPlayerGuiButtons(vectorSpaceFrame)
	-- On nettoie d'éventuelles anciennes connexions avant d'en recréer
	disconnectPlayerButtons(player)

	local connections = {}

	table.insert(connections, downButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), -1)
	end))

	table.insert(connections, upButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), 1)
	end))

	table.insert(connections, leftButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), -1)
	end))

	table.insert(connections, rightButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), 1)
	end))

	table.insert(connections, backButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), 1)
	end))

	table.insert(connections, frontButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), -1)
	end))

	playerButtonConnections[player] = connections
end


-- ==================== ÉVÉNEMENTS DE COLLISION ====================
hitbox.Touched:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end

	-- Anti-spam : ignore si le joueur est déjà dans la zone
	if playerEntryTimestamps[player] then return end
	playerEntryTimestamps[player] = tick()
	
	local vectorSpaceFrame = getPlayerGuiElements(player)
	if not vectorSpaceFrame then return end
	
	vectorSpaceFrame.Visible = true
	
	setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
	
end)

hitbox.TouchEnded:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end
	if not playerEntryTimestamps[player] then return end
	
	-- Attend un délai pour éviter les faux positifs (ex: si le joueur touche encore la hitbox avec une autre partie du corps)
	task.wait(EXIT_CONFIRM_DELAY)
	-- Vérifie qu'aucune partie du personnage ne touche encore la hitbox
	local character = player.Character
	local stillTouching = false
	if character then
		for _, part in pairs(hitbox:GetTouchingParts()) do
			if part.Parent == character then
				stillTouching = true
				break
			end
		end
	end

	if stillTouching then return end

	if not playerEntryTimestamps[player] then return end
	
	-- Le joueur a vraiment quitté la zone
	playerEntryTimestamps[player] = nil
	
	local vectorSpaceFrame = getPlayerGuiElements(player)
	if not vectorSpaceFrame then return end

	vectorSpaceFrame.Visible = false
	
end)
-- Interface invisible au lancement du joueur
Players.PlayerAdded:Connect(function(player)
	-- Aucune action nécessaire pour les joueurs déjà connectés
	local vectorSpaceFrame = getPlayerGuiElements(player)
	if not vectorSpaceFrame then return end

	vectorSpaceFrame.Visible = false
end)

Explications du code

LigneExplication
KEY_COLORS = {Color3.fromRGB(255, 0, 0), ...}Définit l’ordre des couleurs des clés à collecter.
workspace:GetPartsInPart(movablePart, overlapParams)Récupère toutes les parties qui touchent le bloc mobile.
touchingPart.Name == "Key"Vérifie si la partie touchée est une clé.
touchingPart.Color == KEY_COLORS[1]Vérifie si la couleur de la clé correspond à la prochaine couleur attendue.
table.remove(KEY_COLORS, 1)Retire la première couleur de la liste (car la clé a été collectée).
movablePart.Color = KEY_COLORS[1]Change la couleur du bloc pour indiquer la prochaine clé à trouver.
openWall()Détruit le mur et le bloc mobile (victoire).

À toi de jouer !

  1. Copie le code dans le Script serveur.
  2. Teste :
    • Déplace le bloc pour toucher la première clé (rouge) : elle doit disparaître, et le bloc doit devenir bleu.
    • Touche la clé bleue, puis la verte : le mur doit s’ouvrir !
  3. Modifie :
    • Change l’ordre des couleurs dans KEY_COLORS (ex: {Color3.fromRGB(0, 255, 0), Color3.fromRGB(255, 0, 0)}).
    • Ajoute une 4ème clé avec une nouvelle couleur (ex: jaune).

QCM / Questions

1. Pourquoi utilise-t-on workspace:GetPartsInPart ?

  • a) Parce que .Touched ne fonctionne pas entre deux Parts ancrées.
  • b) Parce que c’est plus rapide.
  • c) Parce que c’est plus facile à coder.

2. Que fait table.remove(KEY_COLORS, 1) ?

  • a) Ajoute une couleur à la liste.
  • b) Retire la première couleur de la liste.
  • c) Change la couleur du bloc.

3. Pourquoi utilise-t-on OverlapParams ?

  • a) Pour filtrer les parties à détecter (ex: exclure la hitbox).
  • b) Pour changer la taille du bloc.
  • c) Pour détruire le bloc.

Pour aller plus loin

  • Ajoute un son quand une clé est collectée.
  • Affiche un message à l’écran quand une clé est collectée (utilise un TextLabel dans le ScreenGui).

Documentation Roblox Lua :

table.remove

GetPartsInPart

OverlapParams

Color3.fromRGB


Étape 8 : Finaliser le jeu (nettoyage et optimisations)

But : Finaliser le jeu en ajoutant des optimisations et en nettoyant les connexions.
Concepts clés : Gestion de la mémoire, nettoyage des connexions, optimisation.

Explications

Pour éviter les fuites mémoire (problèmes de performance), il faut :

  1. Déconnecter les connexions quand un joueur quitte la zone ou le jeu.
  2. Nettoyer les références aux objets détruits (ex: movablePart = nil après sa destruction).
  3. Gérer les cas d’erreur (ex: si un joueur quitte pendant qu’il est dans la zone).

Code Lua (Script serveur final)

Voici le script complet avec toutes les optimisations :

luaCopier

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

-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5  -- Délai pour éviter les faux positifs à la sortie
local MOVE_STEP = 1  -- Distance parcourue par clic (en studs)
-- Couleurs des clés à collecter, dans l'ordre où elles doivent être ramassées
local KEY_COLORS = {
	Color3.fromRGB(255, 0, 0),  -- Rouge
	Color3.fromRGB(0, 0, 255),   -- Bleu
	Color3.fromRGB(0, 255, 0),  -- Vert
}

-- ==================== RÉFÉRENCES WORKSPACE ====================
local vectorSpaceGameFolder = Workspace:WaitForChild("VectorSpace")
local hitbox = vectorSpaceGameFolder:WaitForChild("HitBox")
local movablePart = vectorSpaceGameFolder:WaitForChild("Part")
local wall = vectorSpaceGameFolder:WaitForChild("Wall")

-- ==================== ÉTAT DU JEU ====================
local playerEntryTimestamps = {}  -- [Player] = tick() au moment de l'entrée dans la zone
local playerButtonConnections = {}  -- [Player] = liste des connexions de boutons

-- ==================== RAYCAST (empêche de traverser les murs) ====================
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {hitbox}

-- ==================== DÉTECTION DE COLLISION ====================
-- Touched ne se déclenche pas entre deux parts Anchored : on vérifie donc manuellement les chevauchements
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
overlapParams.FilterDescendantsInstances = {hitbox}

-- ==================== FONCTIONS UTILITAIRES ====================
-- Récupère le joueur à partir d'une partie touchée
local function getPlayerFromTouchedPart(hit)
	local character = hit.Parent
	if not character then return nil end

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

	return Players:GetPlayerFromCharacter(character)
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiElements(player)
	local playerGui = player:WaitForChild("PlayerGui")
	local screenGui = playerGui:WaitForChild("ScreenGui")
	local vectorSpaceFrame = screenGui:WaitForChild("VectorSpace")

	return vectorSpaceFrame
end
-- Cache l'interface pour tous les joueurs
local function setInterfaceVisibility(player, etat)
	if not player then return end
	local vectorSpaceFrame = getPlayerGuiElements(player)	
	vectorSpaceFrame.Visible = etat
	return vectorSpaceFrame
end
-- Ouvre la porte
local function openWall()

	if wall then
		wall:Destroy()
	end
	if movablePart then
		movablePart:Destroy()
		movablePart = nil
	end
	print("La porte s'ouvre !")

	-- Cache l'interface pour tous les joueurs
	for player, _ in pairs(playerEntryTimestamps) do
		setInterfaceVisibility(player, false)	
	end
end
-- Vérifie si le bloc mobile touche une clé, et déclenche la logique de collecte
local function checkMovablePartCollisions()
	if not movablePart then return end

	local touchingParts = Workspace:GetPartsInPart(movablePart, overlapParams)

	for _, touchingPart in ipairs(touchingParts) do
		-- On ne réagit qu'à la clé actuellement attendue (la première de la liste)
		if touchingPart.Name == "Key" and touchingPart.Color == KEY_COLORS[1] then
			touchingPart:Destroy()  -- Supprime la clé
			table.remove(KEY_COLORS, 1)  -- Retire la couleur de la liste

			if #KEY_COLORS > 0 then
				-- Il reste des clés à collecter : on indique la prochaine couleur attendue
				movablePart.Color = KEY_COLORS[1]
			else
				-- Toutes les clés sont collectées : victoire !
				openWall()
			end
			break  -- On sort de la boucle car on a trouvé une clé
		end
	end
end


-- Déplace la Part si le raycast ne détecte aucun obstacle sur le trajet
local function moveMovablePart(movablePart, axisVector, directionSign)
	if not movablePart then return end

	local moveOffset = axisVector * directionSign
	local rayOrigin = movablePart.Position + ((movablePart.Size / 2) * directionSign)

	local result = 	workspace:Raycast(rayOrigin, moveOffset, raycastParams) 
	
	if result then return end  -- Un obstacle bloque le trajet

	movablePart.Position = movablePart.Position + moveOffset
	checkMovablePartCollisions()
end

-- Nettoie les connexions de boutons d'un joueur
local function disconnectPlayerButtons(player)
	local connections = playerButtonConnections[player]
	if not connections then return end
	for _, connection in ipairs(connections) do
		connection:Disconnect()
	end
	playerButtonConnections[player] = nil
end

--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiButtons(vectorSpaceFrame)

	local upButton = vectorSpaceFrame:WaitForChild("UpButton")
	local downButton = vectorSpaceFrame:WaitForChild("DownButton")
	local leftButton = vectorSpaceFrame:WaitForChild("LeftButton")
	local rightButton = vectorSpaceFrame:WaitForChild("RightButton")
	local backButton = vectorSpaceFrame:WaitForChild("BackButton")
	local frontButton = vectorSpaceFrame:WaitForChild("FrontButton")

	return upButton, downButton, leftButton, rightButton, backButton, frontButton
end
-- Met en place les contrôles de déplacement pour un joueur
local function setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
	if not player then return end
	if not vectorSpaceFrame then return end
	-- On récupère les boutons du joueur 
	local upButton, downButton, leftButton, rightButton, backButton, frontButton = getPlayerGuiButtons(vectorSpaceFrame)
	-- On nettoie d'éventuelles anciennes connexions avant d'en recréer
	disconnectPlayerButtons(player)

	local connections = {}

	table.insert(connections, downButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), -1)
	end))

	table.insert(connections, upButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), 1)
	end))

	table.insert(connections, leftButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), -1)
	end))

	table.insert(connections, rightButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), 1)
	end))

	table.insert(connections, backButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), 1)
	end))

	table.insert(connections, frontButton.MouseButton1Click:Connect(function()
		moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), -1)
	end))

	playerButtonConnections[player] = connections
end


-- ==================== ÉVÉNEMENTS DE COLLISION ====================
hitbox.Touched:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end

	-- Anti-spam : ignore si le joueur est déjà dans la zone
	if playerEntryTimestamps[player] then return end
	playerEntryTimestamps[player] = tick()
	
	local vectorSpaceFrame = setInterfaceVisibility(player, true)
	
	setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
	
end)

hitbox.TouchEnded:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end
	if not playerEntryTimestamps[player] then return end
	
	-- Attend un délai pour éviter les faux positifs (ex: si le joueur touche encore la hitbox avec une autre partie du corps)
	task.wait(EXIT_CONFIRM_DELAY)
	-- Vérifie qu'aucune partie du personnage ne touche encore la hitbox
	local character = player.Character
	local stillTouching = false
	if character then
		for _, part in pairs(hitbox:GetTouchingParts()) do
			if part.Parent == character then
				stillTouching = true
				break
			end
		end
	end

	if stillTouching then return end

	if not playerEntryTimestamps[player] then return end
	
	-- Le joueur a vraiment quitté la zone
	playerEntryTimestamps[player] = nil
	
	setInterfaceVisibility(player, false)
	
	disconnectPlayerButtons(player)
	
end)
-- Interface invisible au lancement du joueur
Players.PlayerAdded:Connect(function(player)
	-- Aucune action nécessaire pour les joueurs déjà connectés
	setInterfaceVisibility(player, false)
end)

-- Nettoyage quand un joueur quitte
Players.PlayerRemoving:Connect(function(player)
	playerEntryTimestamps[player] = nil
	disconnectPlayerButtons(player)
	setInterfaceVisibility(player, false)
end)

Explications des optimisations

OptimisationExplication
playerButtonConnections[player] = nilNettoie les connexions pour éviter les fuites mémoire.
if not movablePart then return endÉvite les erreurs si le bloc a été détruit.
setInterfaceVisibility(player, false)Optimise la visibilité de l’écran du joueur
openWall() cache l’interface pour tous les joueursAméliore l’expérience utilisateur.

À toi de jouer !

  1. Copie le script final dans VectorSpaceScript.
  2. Teste le jeu complet :
    • Entre dans la hitbox.
    • Déplace le bloc pour toucher les clés dans l’ordre.
    • Vérifie que la porte s’ouvre et que l’interface disparaît.
  3. Modifie :
    • Ajoute une 4ème clé avec une nouvelle couleur (ex: jaune).
    • Change MOVE_STEP à 0.5 pour des déplacements plus précis.

QCM / Questions

1. Pourquoi utilise-t-on playerButtonConnections[player] = nil ?

  • a) Pour éviter les fuites mémoire.
  • b) Pour changer le nom du joueur.
  • c) Pour détruire le joueur.

Pour aller plus loin

  • Ajoute un chronomètre : Limite le temps pour collecter toutes les clés.
  • Ajoute un système de score : Calcul le temps mis par le joueur pour ouvrir la porte.
  • Ajoute des obstacles : Des murs que le bloc ne peut pas traverser.