Dans ce tutoriel, tu vas apprendre à programmer un système de construction comme dans Minecraft avec Roblox et le langage Lua.
Le joueur pourra créer des blocs pour construire des structures de blocs et détruire des blocs.
Tu découvriras comment utiliser la souris, le clavier et les scripts pour interagir avec les objets dans Roblox Studio.
Ce projet permet d’apprendre les bases importantes de la programmation de jeux vidéo : événements, conditions, coordonnées et objets 3D, les scripts en local et les scripts sur le serveur.
À la fin du tutoriel, tu auras créé un véritable système de construction interactif dans ton propre jeu Roblox.
Dans un premier temps, tu vas récupérer les actions du joueur comme les clics de souris et les touches du clavier dans LocalScript.
Dans un deuxième temps, ces informations seront ensuite envoyées par un système ROBLOX de RemoteEvent au serveur afin de vérifier et traiter les demandes du joueur.
Dans un troisième temps, tu créeras un Script sur le serveur pour créer ou détruir les blocs dans le monde du jeu pour que tous les joueurs puissent voir les modifications en même temps.
Tu découvriras ainsi comment fonctionne la communication entre le joueur et le serveur dans un vrai jeu multijoueur Roblox.
Récupération des événements souris et clavier
Sous StarterPlayer puis StarterCharacterScripts crée un Folder que tu renommes Minecraft, puis un LocalScript que tu renommes MCInput :

Le joueur interagit avec le monde du jeu comme dans Minecraft grâce à la souris et au clavier. Un clic sur le bouton gauche de la souris permet de construire un nouveau bloc dans le jeu. Un clic sur le bouton droit de la souris permet de détruire un bloc existant. Cependant, pour laisser la possibilité d’utiliser la souris pour les déplacements du joueur et de la caméra, la construction ou la destruction ne sera possible uniquement si le joueur maintient en même temps la touche Majuscule gauche (Left Shift) du clavier.
Dans MCInput, saisie cette programmation pour récupérer les événements clavier et souris :
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
RunService.RenderStepped:Connect(function()
-- Appuyer sur la touche la touche majuscule pour que la souris soit prise en compteif
if not UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) then return end
-- Touche gauche de la souris pour construire
if UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) then
print("Mouse left pressed")
-- Touche droite de la souris pour détruire
elseif UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) then
print("Mouse right pressed")
end
end)
Lance le jeu et vérifie la prise en compte des boutons gauche et droit de la souris :

Comment remonter les actions du local vers le serveur
Dans Roblox, les actions de la souris sont détectées dans le LocalScript, car elles appartiennent uniquement au joueur qui utilise le jeu. Pour que le serveur puisse créer ou détruire les blocs, nous devons lui envoyer les informations des clics de souris. Nous allons utiliser des RemoteEvent, un système de communication entre le joueur et le serveur dans Roblox. Le LocalScript enverra par exemple le clic gauche et l’action demandée (construire). Le serveur recevra ensuite ces événements et appliquera les modifications dans le monde du jeu pour que tous les joueurs puissent les voir.
Deux événements sont remontés construire et détruire.
Crée deux Folder sous ReplicatedStorage Minecraft puis Remotes.
Sous le folder Remotes crée deux RemoteEvent MCBuild et MCDestroy :

Puis sous ServerScriptService crée le Folder Maincraft puis crée un Script MCSystem afin d’exécuter les ordres reçues par le LocalScript du joueur via les RemoteEvent :

Saisie ce code dans MCSystem :
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- Références
local minecraft = ReplicatedStorage:WaitForChild("Minecraft")
local remotes = minecraft:WaitForChild("Remotes")
-- Remote : poser un bloc
remotes.MCBuild.OnServerEvent:Connect(function(player, data)
print("Action Build")
end)
-- Remote : détruire un bloc
remotes.MCDestroy.OnServerEvent:Connect(function(player, data)
print("Action Destroy")
end)
Modifie ton LocalScript MCInput pour envoyer les actions :
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local minecraft = ReplicatedStorage:WaitForChild("Minecraft")
local remotes = minecraft:WaitForChild("Remotes")
RunService.RenderStepped:Connect(function()
-- Appuyer sur la touche la touche majuscule pour que la souris soit prise en compte
if not UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) then return end
-- Touche gauche de la souris pour construire
if UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) then
print("Mouse left pressed")
remotes.MCBuild:FireServer()
-- Touche droite de la souris pour détruire
elseif UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) then
print("Mouse right pressed")
remotes.MCDestroy:FireServer()
end
end)
Lance ton jeu et vérifie que le serveur reçoit bien les deux actions :

Lorsque le joueur maintient un bouton de la souris appuyé, Roblox envoie les événements plusieurs fois de suite très rapidement.
Sans protection, le serveur pourrait alors créer ou détruire un très grand nombre de blocs en quelques secondes. Cela peut provoquer des bugs, ralentir le jeu ou permettre à un joueur de construire trop vite.
Nous allons donc mettre en place un mécanisme de limitation, appelé souvent “cooldown” ou temporisation, pour attendre un petit délai entre deux actions.
Ainsi, le serveur traitera les demandes de manière plus propre et le jeu restera fluide pour tous les joueurs.
Modifie le script serveur MCSystem :
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Debris = game:GetService("Debris")
-- Références
local minecraft = ReplicatedStorage:WaitForChild("Minecraft")
local remotes = minecraft:WaitForChild("Remotes")
-- Système anti rebond via un Folder temporaire dans le Character du player
local function withCooldown(character, actionName, callback)
if character:FindFirstChild(actionName) then return end
local tag = Instance.new("Folder")
tag.Name = actionName
tag.Parent = character
Debris:AddItem(tag, COOLDOWN_DURATION)
callback()
end
-- Remote : poser un bloc
remotes.MCBuild.OnServerEvent:Connect(function(player, data)
local character = player.Character
withCooldown(character, "Build", function()
print("Action Build")
end)
end)
-- Remote : détruire un bloc
remotes.MCDestroy.OnServerEvent:Connect(function(player, data)
local character = player.Character
withCooldown(character, "Destroying", function()
print("Action Destroy")
end)
end)
Tu remarques que dans la sortie, tu as une seule action pour plusieurs événements souris :

Récupération de la position du bloc à créer
Maintenant, nous devons récupérer la position de la souris dans le monde du jeu et non sa position sur l’écran de l’ordinateur.
En effet, pour construire ou détruire des blocs, le jeu doit savoir exactement quel endroit du monde 3D le joueur vise avec sa souris.
Le LocalScript va donc utiliser un système appelé “Raycast” pour projeter un rayon invisible depuis la caméra vers la scène du jeu.
Grâce à cela, nous pourrons obtenir les coordonnées précises du point visé dans l’espace ainsi que l’objet touché par la souris, par exemple un bloc ou la baseplate.
Ces informations seront ensuite envoyées au serveur pour créer ou supprimer les blocs au bon endroit.
Modifie le script serveur MCSystem :
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Debris = game:GetService("Debris")
-- Références
local minecraft = ReplicatedStorage:WaitForChild("Minecraft")
local remotes = minecraft:WaitForChild("Remotes")
-- Système anti rebond via un Folder temporaire dans le Character du player
local function withCooldown(character, actionName, callback)
if character:FindFirstChild(actionName) then return end
local tag = Instance.new("Folder")
tag.Name = actionName
tag.Parent = character
Debris:AddItem(tag, COOLDOWN_DURATION)
callback()
end
-- Valide les données communes aux deux remotes
local function parseData(player, data)
local character = player.Character
if not character then return nil end
if not character:FindFirstChildOfClass("Humanoid") then return nil end
local position3D = data[1]
local direction = data[2]
local partTouchee = data[3]
if not partTouchee then return nil end
print("Position3D:", position3D, "Direction:", direction, "Part touchée:", partTouchee.Name)
return character, position3D, direction, partTouchee
end
-- Remote : poser un bloc
remotes.MCBuild.OnServerEvent:Connect(function(player, data)
local character, position3D, direction = parseData(player, data)
if not character then return end
withCooldown(character, "Build", function()
print("Action Build")
end)
end)
-- Remote : détruire un bloc
remotes.MCDestroy.OnServerEvent:Connect(function(player, data)
local character, _, _, partTouchee = parseData(player, data)
if not character then return end
withCooldown(character, "Destroying", function()
print("Action Destroy")
end)
end)
Lance ton jeu et vérifie que le serveur reçoit la position pointée par la souris, le direction et la part touchée.

Création d’un bloc Minecraft
Pour créer un nouveau bloc dans le jeu, nous allons utiliser un modèle de bloc préparé dans le ReplicatedStorage. Le serveur va créer une copie de ce bloc, appelée un “clone”, puis le placer dans un dossier Minecraft créé dans le Workspace pour ranger proprement tous les blocs Minecraft.
Le bloc doit être ensuite aligné automatiquement sur une grille afin que toutes les constructions restent bien organisées comme dans Minecraft.
Après sa création, le bloc descend jusqu’à toucher un autre bloc ou la Baseplate. Dès qu’il entre en contact avec un support, nous activerons la propriété Anchored pour le bloquer définitivement et éviter qu’il continue à bouger.
Crée un part qui sera ta brique de base Minecraft :

Fais en sorte que le part soit cubique :

Déplace ton bloc de base sous ReplicatedStorage et renomme le MCBlock :

Puis continue à modifier le script serveur MCSystem pour créer les blocs :
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Debris = game:GetService("Debris")
-- Références
local minecraft = ReplicatedStorage:WaitForChild("Minecraft")
local remotes = minecraft:WaitForChild("Remotes")
local blockTemplate = minecraft:WaitForChild("MCBlock")
-- Configuration
local GRID_SIZE = blockTemplate.Size.X
local ANCHOR_DELAY = 0.5 -- délai avant tentative d'ancrage
local VELOCITY_THRESHOLD = 0.1 -- seuil de vitesse pour ancrage
local COOLDOWN_DURATION = 0.5 -- durée du cooldown build/destroy
local FOLDERMC = "Minecraft"
-- Système anti rebond via un Folder temporaire dans le Character du player
local function withCooldown(character, actionName, callback)
if character:FindFirstChild(actionName) then return end
local tag = Instance.new("Folder")
tag.Name = actionName
tag.Parent = character
Debris:AddItem(tag, COOLDOWN_DURATION)
callback()
end
-- Valide les données communes aux deux remotes
local function parseData(player, data)
local character = player.Character
if not character then return nil end
if not character:FindFirstChildOfClass("Humanoid") then return nil end
local position3D = data[1]
local direction = data[2]
local partTouchee = data[3]
if not partTouchee then return nil end
print("Position3D:", position3D, "Direction:", direction, "Part touchée:", partTouchee.Name)
return character, position3D, direction, partTouchee
end
-- Aligne une position sur la grille en fonction du cube de référence
local function snapToGrid(position)
local function snap(v) return math.floor(v / GRID_SIZE + 0.5) * GRID_SIZE end
return Vector3.new(snap(position.X), snap(position.Y), snap(position.Z))
end
-- Ancrage progressif d'un bloc
local function anchorBlock(block)
if block.Anchored then return end
task.wait(ANCHOR_DELAY)
repeat task.wait(0.1) until
not block or
not block.Parent or
block.AssemblyLinearVelocity.Magnitude <= VELOCITY_THRESHOLD
if not block or not block.Parent then return end
block.Anchored = true
block.AssemblyLinearVelocity = Vector3.zero
block.AssemblyAngularVelocity = Vector3.zero
end
-- Récupère ou crée le folder Minecraft dans le workspace
local function getOrCreateFolderMC()
local folder = workspace:FindFirstChild(FOLDERMC)
if not folder then
folder = Instance.new("Folder")
folder.Name = FOLDERMC
folder.Parent = workspace
end
return folder
end
local folderMinecraft = getOrCreateFolderMC()
-- Remote : poser un bloc
remotes.MCBuild.OnServerEvent:Connect(function(player, data)
local character, position3D, direction = parseData(player, data)
if not character then return end
withCooldown(character, "Build", function()
local newPosition = snapToGrid(position3D + direction * GRID_SIZE)
local newBlock = blockTemplate:Clone()
newBlock.Position = newPosition
newBlock.Anchored = false
newBlock.Parent = folderMinecraft
local connection
connection = newBlock.Touched:Connect(function(otherPart)
if not otherPart.Anchored and otherPart.Name ~= "Baseplate" then return end
connection:Disconnect() -- un seul ancrage
anchorBlock(newBlock)
end)
end)
end)
-- Remote : détruire un bloc
remotes.MCDestroy.OnServerEvent:Connect(function(player, data)
local character, _, _, partTouchee = parseData(player, data)
if not character then return end
withCooldown(character, "Destroying", function()
print("Action Destroy")
end)
end)
Destruction d’un bloc
Dans le script serveur MCSystem modifie juste l’action MCDestroy :
-- Remote : détruire un bloc
remotes.MCDestroy.OnServerEvent:Connect(function(player, data)
local character, _, _, partTouchee = parseData(player, data)
if not character then return end
withCooldown(character, "Destroying", function()
if partTouchee:IsA("BasePart") and partTouchee.Name == blockTemplate.Name then
partTouchee:Destroy()
end
end)
end)
Si tout fonctionne correctement, tu peux supprimer les « print » de ton code ou les mettre en commentaire :
-- Valide les données communes aux deux remotes
local function parseData(player, data)
local character = player.Character
if not character then return nil end
if not character:FindFirstChildOfClass("Humanoid") then return nil end
local position3D = data[1]
local direction = data[2]
local partTouchee = data[3]
if not partTouchee then return nil end
--print("Position3D:", position3D, "Direction:", direction, "Part touchée:", partTouchee.Name)
return character, position3D, direction, partTouchee
end
