Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Personnalise ton bloc !

Introduction

Dans ce tutoriel, tu vas apprendre à programmer en Lua avec Roblox Studio en construisant un mini-jeu simple : un menu qui permet au joueur de personnaliser un bloc (une « Part ») dans le monde du jeu.

La règle de base du jeu est simple :

Le joueur appuie sur une touche pour ouvrir un menu. Dans ce menu, il peut choisir la couleur, la transparence et le matériau d’un bloc posé dans le monde.

C’est un point de départ. À la fin du tutoriel, ce sera à toi d’imaginer comment faire évoluer ce jeu (ajouter des règles, des objectifs, des points, etc.).

Tu vas construire ce projet étape par étape. Prends le temps de tester ton code à chaque étape avant de passer à la suivante : si quelque chose ne marche pas, ce n’est pas grave, c’est normal, et c’est même comme ça qu’on apprend le mieux à programmer.

Tu vas apprendre à construire une interface pour ton joueur.


Étape 1 : LocalScript ou Script ? Qui fait quoi ?

But : Comprendre la différence entre le code qui tourne sur ton ordinateur (le client) et le code qui tourne sur l’ordinateur central du jeu (le serveur), avant d’écrire la moindre ligne de code.

Concepts clés : Client / Serveur, LocalScript, Script

Explications

Dans un jeu Roblox, il y a toujours deux « mondes » qui communiquent :

  • Le serveur : c’est l’ordinateur central qui héberge la partie pour tout le monde. Un Script placé dans ServerScriptService (ou ailleurs côté serveur) s’exécute sur le serveur. Ce qu’il fait est vu par tous les joueurs.
  • Le client : c’est ton propre ordinateur (ou celui d’un autre joueur). Un LocalScript ne s’exécute que sur l’ordinateur du joueur concerné, et seulement si ce script se trouve dans certains dossiers précis : StarterPlayerScripts, StarterGui, StarterPack, PlayerGui, ou ReplicatedFirst.

Exemple concret pour notre jeu : le menu qui s’affiche à l’écran (les boutons, la barre de transparence) est une interface personnelle — chaque joueur voit son propre menu, indépendamment des autres. C’est donc un travail pour un LocalScript.

En revanche, si un jour tu veux que tous les joueurs voient le bloc changer de couleur (pas seulement celui qui a cliqué), il faudra qu’un Script côté serveur applique réellement le changement. On appelle ça la réplication, et on utilisera pour cela un RemoteEvent.

Documentation Roblox :

À toi de jouer !

  1. Ouvre Roblox Studio, crée un nouveau lieu vide (Baseplate).
  2. Dans l’Explorer, regarde les dossiers ServerScriptService, StarterPlayer > StarterPlayerScripts, et StarterGui. Repère bien où chacun se trouve, tu vas t’en servir tout au long du tutoriel.

QCM

1. Un LocalScript placé dans Workspace (en dehors du personnage) va-t-il s’exécuter ?

  • a) Oui, toujours.
  • b) Non, un LocalScript ne s’exécute que dans certains dossiers précis.
  • c) Oui, mais seulement pour l’hôte de la partie.

2. Si je veux que tous les joueurs voient un bloc changer de couleur, je dois utiliser :

  • a) Un LocalScript uniquement.
  • b) Un Script serveur (éventuellement avec un RemoteEvent).
  • c) Un print().

Étape 2 : Construire le menu avec trois Frames

But : Construire dans l’interface (ScreenGui) la structure visuelle de base : un cadre principal, et trois zones à l’intérieur pour les trois réglages (couleur, transparence, matériau).

Concepts clés : ScreenGui, Frame, hiérarchie des objets (parent/enfant)

Explications

En Roblox, l’interface (l’UI) se construit comme une arborescence d’objets, un peu comme des poupées russes : chaque objet est enfant d’un autre. Le point de départ obligatoire pour toute UI à l’écran est un ScreenGui.

Pour notre menu, on va créer cette hiérarchie dans l’Explorer de Studio :

StarterGui
 └─ ScreenGui
     └─ Frame                  -- le cadre principal du menu (fond, bordure)
         └─ CloseButton        -- bouton pour fermer le menu
         └─ ColorsPicker (Frame)     -- zone 1 : choix de la couleur
         └─ SliderBar (Frame)        -- zone 2 : réglage de la transparence
             └─ Fill (Frame)
             └─ Knob (Frame)
         └─ MaterialPicker (Frame)   -- zone 3 : choix du matériau

Chaque zone (ColorsPicker, SliderBar, MaterialPicker) est un Frame séparé : ça permet de les positionner indépendamment les uns des autres à l’intérieur du menu, et de les remplir chacun avec le contenu qui leur correspond (des boutons pour les deux premiers, une barre et un curseur pour le slider).

Documentation Roblox :

À toi de jouer !

  1. Crée cette hiérarchie directement dans l’Explorer de Studio (clic droit > Insert Object).
  2. Pour l’instant, laisse les Frame vides et de couleur unie, juste pour voir leur emplacement à l’écran. On les remplira dans les étapes suivantes.
  3. Observation attendue : tu dois voir trois rectangles distincts à l’intérieur du cadre principal quand tu lances le Play Test.

Questions

1. Pourquoi sépare-t-on les trois réglages en trois Frame différents plutôt que de tout mettre dans un seul cadre ?


Étape 3 : Adapter l’écran à tous les terminaux — Scale vs Offset

But : Comprendre comment Roblox positionne les éléments d’interface pour qu’ils s’adaptent aussi bien à un écran de PC, de tablette que de smartphone.

Concepts clés : Scale, Offset, UDim2

Explications

Le problème : un écran de smartphone ne fait pas la même taille en pixels qu’un écran de PC. Si tu places un bouton « à 500 pixels du bord gauche », il sera bien placé sur un grand écran, mais complètement hors de l’écran sur un petit téléphone !

Pour résoudre ça, chaque taille et position en Roblox UI utilise un UDim2, qui combine deux façons de mesurer pour chaque axe :

  • Scale : un pourcentage de la taille du parent (de 0 à 1). Exemple : 0.5 = la moitié de la largeur du parent, peu importe l’écran. C’est ce qui s’adapte automatiquement à toutes les tailles d’écran.
  • Offset : une valeur fixe en pixels, en plus du Scale. Exemple : 10 pixels de décalage. Utile pour des détails précis (une petite marge, une icône de taille fixe) qui ne doivent pas changer selon l’écran.

Un UDim2 s’écrit comme ceci : UDim2.new(scaleX, offsetX, scaleY, offsetY).

Exemple : UDim2.new(0.5, 10, 0.5, 0) veut dire : la moitié de la largeur du parent, plus 10 pixels, et la moitié de la hauteur du parent.

Règle pratique : pour que ton menu s’adapte à tous les écrans, utilise surtout le Scale pour la taille et la position globale du menu, et réserve l’Offset pour de petits ajustements (comme on l’a fait pour compenser la largeur du Knob du slider, tu t’en souviens peut-être).

Documentation Roblox :

À toi de jouer !

  1. Sélectionne ton Frame principal, et regarde sa propriété Size dans les Properties.
  2. Essaie de la régler en Scale pur, par exemple {0.4, 0, 0.6, 0}, puis teste le Play Test en redimensionnant la fenêtre de Studio (ou en simulant un écran de tablette avec le Device Emulator). Observe comment le menu s’adapte.
  3. Remets ensuite un peu d’Offset, par exemple {0.4, 20, 0.6, 0}, et observe la différence.

QCM

1. Que vaut la largeur d’un objet avec Size = UDim2.new(0.5, 0, 1, 0) si son parent fait 800 pixels de large ?

  • a) 500 pixels
  • b) 400 pixels
  • c) 800 pixels

2. Pourquoi n’utilise-t-on pas uniquement l’Offset pour positionner toute l’interface ?

  • a) Parce que l’Offset n’existe pas vraiment dans Roblox.
  • b) Parce qu’une position fixe en pixels ne s’adapte pas aux écrans de tailles différentes.
  • c) Parce que c’est interdit par Roblox.

Étape 4 : Les coordonnées X et Y — un repère comme en maths

But : Comprendre comment repérer un point ou une taille sur l’écran, en faisant le lien avec le repère orthonormé vu en cours de mathématiques.

Concepts clés : Axes X / Y, Position, Size

Explications

Tu connais déjà le repère orthonormé en maths : un axe horizontal X, un axe vertical Y, et une origine (0, 0). En Roblox UI, c’est presque pareil, avec une différence importante :

  • L’origine (0, 0) est en haut à gauche de l’écran (et non au centre comme en maths).
  • L’axe X augmente vers la droite, comme en maths.
  • L’axe Y augmente vers le bas (et non vers le haut !). C’est le piège classique à retenir.
(0,0) ────────────► X
  │
  │        écran
  │
  ▼
  Y

La propriété Position d’un Frame place son coin (ou son centre, selon l’AnchorPoint) à des coordonnées (X, Y) par rapport à son parent. La propriété Size fonctionne pareil, mais donne une largeur et une hauteur plutôt qu’un point.

Le rôle de l’AnchorPoint : c’est le point de référence de l’objet lui-même. Avec AnchorPoint = (0, 0) (par défaut), c’est le coin haut-gauche de l’objet qui est placé à Position. Avec AnchorPoint = (0.5, 0.5), c’est le centre de l’objet — c’est exactement pourquoi on l’a utilisé pour le Knob du slider : ça permet de centrer précisément un objet sur un point donné, plutôt que de raisonner avec son coin.

Documentation Roblox :

À toi de jouer !

  1. Prends ton CloseButton et change son AnchorPoint de (0, 0) à (1, 0), sans toucher à Position. Observe : le bouton doit maintenant se placer par rapport à son coin haut-droit.
  2. Essaie de placer un bouton exactement au centre de son parent : AnchorPoint = (0.5, 0.5) et Position = UDim2.new(0.5, 0, 0.5, 0).

Questions

1. Si je veux qu’un objet soit collé au bord bas-droit de son parent, quel AnchorPoint dois-je utiliser ?


Étape 5 : Premier LocalScript — afficher et cacher l’interface

But : Écrire ton tout premier script : ouvrir le menu avec la touche X, et le fermer en cliquant sur un bouton.

Concepts clés : Variables, WaitForChild, événements (Event), fonctions, if/then

Explications

Un événement est quelque chose qui se produit à un moment précis (un clic, un appui sur une touche…) et auquel on peut « réagir » avec du code. En Lua Roblox, on se connecte à un événement avec :Connect(maFonction) : à chaque fois que l’événement se déclenche, maFonction est appelée automatiquement.

Ici, on utilise deux événements :

  • TextButton.MouseButton1Click : se déclenche quand on clique sur un bouton.
  • UserInputService.InputBegan : se déclenche à chaque fois qu’une touche du clavier (ou un clic, ou un appui tactile) est pressée.

On utilise aussi WaitForChild("NomDeLObjet") : ça demande au script d’attendre que l’objet existe avant de continuer, ce qui évite une erreur si l’interface n’a pas encore fini de se charger.

Code Lua

--[[
    GESTION ECRAN DU JOUEUR POUR PERSONNALISER UN BLOC
    -----------------------------------------------
    Affiche ou cache le Frame principal.
    Appui sur X -> ouvre l'écran.
    Clic sur CloseButton -> ferme l'écran.

    LocalScript à placer dans StarterPlayerScripts.
]]

-- On regroupe la touche utilisée dans une variable : si on veut la
-- changer plus tard, on ne modifie qu'une seule ligne.
local KEY_CODE_OPEN = Enum.KeyCode.X

local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")

local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")

local screenGui = playerGui:WaitForChild("ScreenGui")
local frame = screenGui:WaitForChild("Frame")
local close = frame:WaitForChild("CloseButton")

-- Le menu est fermé au démarrage
frame.Visible = false

-- Fonction qui va chercher le Humanoid du personnage actuel du joueur.
-- On la met dans une fonction car le personnage peut réapparaître
-- (respawn) plusieurs fois pendant la partie.
local function getHumanoid()
    local character = player.Character or player.CharacterAdded:Wait()
    return character:FindFirstChildOfClass("Humanoid") or character:WaitForChild("Humanoid")
end

local humanoid = getHumanoid()

-- À chaque fois que le personnage réapparaît, on remet humanoid à jour
player.CharacterAdded:Connect(function()
    humanoid = getHumanoid()
end)

-- Au clic sur le bouton, on ferme le menu
close.MouseButton1Click:Connect(function()
    frame.Visible = false
end)

-- Fonction appelée à CHAQUE touche pressée, sur tout le clavier
local function onKeyPress(input, gameProcessed)
    if not humanoid then return end

    -- gameProcessed est vrai si Roblox a déjà "consommé" la touche
    -- (par exemple si on est en train d'écrire dans une zone de texte).
    -- On l'ignore dans ce cas pour éviter les conflits.
    if input.KeyCode == KEY_CODE_OPEN and not gameProcessed then
        frame.Visible = true
    end
end

UserInputService.InputBegan:Connect(onKeyPress)

Documentation Roblox :

À toi de jouer !

  1. Recopie ce script dans un LocalScript, dans StarterPlayer > StarterPlayerScripts.
  2. Lance le Play Test, appuie sur la touche X : le menu doit apparaître. Clique sur CloseButton : il doit disparaître.
  3. Modification à essayer : change KEY_CODE_OPEN pour une autre touche, par exemple Enum.KeyCode.E, et vérifie que ça fonctionne toujours.

QCM

1. À quoi sert WaitForChild ?

  • a) À supprimer un objet.
  • b) À créer un nouvel objet
  • c) À attendre qu’un objet existe avant de continuer le script.

2. Pourquoi récupère-t-on le Humanoid dans une fonction plutôt qu’une seule fois au début du script ?

  • a) Parce que le personnage du joueur peut réapparaître (respawn), et l’ancien Humanoid ne serait plus valide.
  • b) Parce que Roblox l’exige.
  • c) Parce que ça n’a aucune importance.

Étape 6 : Choisir la couleur d’un bloc

But : Générer automatiquement un bouton par couleur disponible, et changer la couleur du bloc au clic.

Concepts clés : Boucle for, tables (listes), Instance.new, Color3

Explications

Une table en Lua, c’est une liste d’éléments qu’on peut parcourir. Ici, on stocke une liste de couleurs disponibles, chacune avec un nom et une valeur Color3 (rouge, vert, bleu).

Une boucle for permet de répéter une action pour chaque élément d’une liste, sans avoir à recopier le code à la main pour chaque couleur. C’est un des grands intérêts de la programmation : éviter de répéter du code.

Instance.new("TextButton") crée un nouvel objet directement par le script, comme si tu l’avais glissé dans l’Explorer à la main — sauf que là, c’est automatique pour chaque couleur de la liste.

Code Lua

--[[
    SÉLECTEUR DE COULEURS
    -----------------------------------------------
    Génère un bouton par couleur. Un clic applique la couleur
    à la Part cible.

    Hiérarchie attendue : ScreenGui > Frame > ColorsPicker (Frame)
]]

local Players = game:GetService("Players")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")

local screenGui = playerGui:WaitForChild("ScreenGui")
local frame = screenGui:WaitForChild("Frame")
local picker = frame:WaitForChild("ColorsPicker")

-- On range les boutons proprement en grille, sans les positionner un par un
local gridLayout = picker:FindFirstChildOfClass("UIGridLayout")
if not gridLayout then
    gridLayout = Instance.new("UIGridLayout")
    gridLayout.CellSize = UDim2.new(0, 80, 0, 100)
    gridLayout.CellPadding = UDim2.new(0, 8, 0, 8)
    gridLayout.Parent = picker
end

-- Le bloc que l'on va modifier
local targetPart = workspace:WaitForChild("MaPart")

-- La liste des couleurs proposées : une table de tables
-- (chaque élément est lui-même une petite table avec un nom et une couleur)
local colors = {
    {Name = "Gris", Color = Color3.fromRGB(200, 200, 200)},
    {Name = "Rouge", Color = Color3.fromRGB(255, 0, 0)},
    {Name = "Vert", Color = Color3.fromRGB(0, 255, 0)},
    {Name = "Bleu", Color = Color3.fromRGB(0, 0, 255)},
}

-- On parcourt la liste avec une boucle for, et on crée un bouton par couleur
for _, color in ipairs(colors) do
    local button = Instance.new("TextButton")
    button.Name = color.Name
    button.Size = UDim2.new(0, 80, 0, 100)
    button.Text = ""
    button.AutoButtonColor = false
    button.BackgroundColor3 = color.Color
    button.Parent = picker

    local label = Instance.new("TextLabel")
    label.Size = UDim2.new(1, 0, 0, 20)
    label.Position = UDim2.new(0, 0, 1, -20)
    label.BackgroundTransparency = 1
    label.Text = color.Name
    label.TextColor3 = Color3.fromRGB(255, 255, 255)
    label.TextScaled = true
    label.Font = Enum.Font.SourceSansBold
    label.Parent = button

    -- Au clic, on applique la couleur correspondante
    button.MouseButton1Click:Connect(function()
        targetPart.Color = color.Color
    end)
end

Documentation Roblox :

À toi de jouer !

  1. Recopie ce script dans un LocalScript, dans StarterPlayerScripts (ou dans ScreenGui).
  2. Teste : chaque couleur de la liste doit apparaître comme un bouton, et cliquer dessus doit changer la couleur du bloc.
  3. Modification à essayer : ajoute une cinquième couleur à la table colors (par exemple {Name = "Jaune", Color = Color3.fromRGB(255, 255, 0)}) et vérifie qu’un bouton apparaît automatiquement, sans avoir touché au reste du script.

QCM

1. Pourquoi utilise-t-on une boucle for plutôt que de créer chaque bouton à la main ?

  • a) Pour éviter de répéter le même code pour chaque couleur, et pouvoir facilement en ajouter/enlever.
  • b) Parce que Roblox l’interdit autrement.
  • c) Parce que ça va plus vite à l’exécution.

2. Que se passe-t-il si on ajoute un élément à la table colors ?

  • a) Rien, il faut aussi modifier la boucle.
  • b) Le script plante.
  • c) Un nouveau bouton est automatiquement créé au prochain lancement.

Étape 7 : Régler la transparence avec un slider

But : Construire un curseur (slider) que le joueur peut glisser pour régler la transparence du bloc entre 0 et 1.

Concepts clés : Calculs de position, UserInputService.InputChanged, variables booléennes (true/false), math.clamp

Explications

Un booléen est une variable qui ne peut valoir que true (vrai) ou false (faux) — ici, dragging nous dit si le joueur est en train de faire glisser le curseur ou non.

Le calcul le plus important de ce script transforme la position de la souris à l’écran (en pixels) en une valeur comprise entre 0 et 1, appelée ici alpha. Cette valeur correspond exactement à ce qu’attend la propriété Transparency d’une Part : 0 = complètement opaque, 1 = complètement invisible.

math.clamp(valeur, min, max) force une valeur à rester dans un intervalle donné — indispensable ici pour ne pas dépasser 0 ou 1 si le joueur fait glisser la souris en dehors de la barre.

Code Lua

--[[
    SLIDER -> Transparence d'une Part (0 à 1)
    -----------------------------------------------
    Hiérarchie attendue : ScreenGui > Frame > SliderBar (Frame)
        SliderBar > Fill (Frame), Knob (Frame)
]]

local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")

local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")

local screenGui = playerGui:WaitForChild("ScreenGui")
local frame = screenGui:WaitForChild("Frame")
local sliderBar = frame:WaitForChild("SliderBar")
local knob = sliderBar:WaitForChild("Knob")
local fill = sliderBar:FindFirstChild("Fill")

local targetPart = workspace:WaitForChild("MaPart")

local dragging = false

-- Le Knob doit être centré sur lui-même pour que le calcul soit juste
knob.AnchorPoint = Vector2.new(0.5, 0.5)

local function updateSlider(inputPosition)
    local barPos = sliderBar.AbsolutePosition.X
    local barWidth = sliderBar.AbsoluteSize.X
    local knobWidth = knob.AbsoluteSize.X

    -- On retire une demi-largeur de Knob de chaque côté pour qu'il ne
    -- déborde jamais de la barre aux extrémités
    local minX = barPos + knobWidth / 2
    local maxX = barPos + barWidth - knobWidth / 2
    if maxX <= minX then
        minX, maxX = barPos, barPos + barWidth
    end

    local clampedX = math.clamp(inputPosition, minX, maxX)
    local alpha = (clampedX - minX) / (maxX - minX)

    knob.Position = UDim2.new(alpha, knobWidth * (0.5 - alpha), knob.Position.Y.Scale, knob.Position.Y.Offset)

    if fill then
        fill.Size = UDim2.new(alpha, 0, fill.Size.Y.Scale, fill.Size.Y.Offset)
    end

    targetPart.Transparency = alpha
    return alpha
end

knob.InputBegan:Connect(function(input)
    if input.UserInputType == Enum.UserInputType.MouseButton1
        or input.UserInputType == Enum.UserInputType.Touch then
        dragging = true
    end
end)

UserInputService.InputEnded:Connect(function(input)
    if input.UserInputType == Enum.UserInputType.MouseButton1
        or input.UserInputType == Enum.UserInputType.Touch then
        dragging = false
    end
end)

UserInputService.InputChanged:Connect(function(input)
    if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement
        or input.UserInputType == Enum.UserInputType.Touch) then
        updateSlider(input.Position.X)
    end
end)

Documentation Roblox :

À toi de jouer !

  1. Crée SliderBar, avec Fill et Knob à l’intérieur, dans le Frame principal.
  2. Recopie le script, teste en faisant glisser le Knob : le bloc doit devenir de plus en plus transparent.
  3. Modification à essayer : affiche le pourcentage actuel dans un TextLabel à côté du slider (indice : utilise la valeur alpha retournée par updateSlider, et convertis-la en pourcentage avec math.floor(alpha * 100)).

QCM

1. Que fait math.clamp(alpha, 0, 1) si alpha vaut 1.3 ?

  • a) Il renvoie 1, la valeur maximale autorisée.
  • b) Il renvoie 1.3 sans rien changer.
  • c) Il provoque une erreur.

2. Pourquoi le Knob doit-il avoir un AnchorPoint de (0.5, 0.5) ?

  • a) Pour des raisons esthétiques uniquement.
  • b) Ce n’est pas nécessaire, c’est une erreur dans le script
  • c) Pour que le calcul de position centre bien le Knob par rapport au calcul d’alpha, sans qu’il déborde des bords de la barre.

Étape 8 : Choisir le matériau d’un bloc, avec un aperçu 3D

But : Générer un bouton par matériau, avec un véritable aperçu 3D du rendu du matériau (bois, métal, verre…) directement dans le bouton.

Concepts clés : ViewportFrame, WorldModel, Enum.Material, CFrame

Explications

Un ViewportFrame est un objet d’interface un peu spécial : c’est une fenêtre vers une petite scène 3D indépendante, avec sa propre caméra et ses propres objets, sans lien avec le vrai monde du jeu. C’est ce qui permet d’afficher un petit cube avec le matériau réellement appliqué, plutôt qu’une simple image.

On utilise un WorldModel pour contenir les objets 3D du ViewportFrame, une PointLight pour bien voir le relief du matériau, et une Camera pour définir sous quel angle on regarde le cube.

Code Lua

--[[
    SÉLECTEUR DE MATÉRIAU AVEC APERÇU 3D
    -----------------------------------------------
    Hiérarchie attendue : ScreenGui > Frame > MaterialPicker (Frame)
]]

local Players = game:GetService("Players")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")

local screenGui = playerGui:WaitForChild("ScreenGui")
local frame = screenGui:WaitForChild("Frame")
local picker = frame:WaitForChild("MaterialPicker")

local gridLayout = picker:FindFirstChildOfClass("UIGridLayout")
if not gridLayout then
    gridLayout = Instance.new("UIGridLayout")
    gridLayout.CellSize = UDim2.new(0, 80, 0, 100)
    gridLayout.CellPadding = UDim2.new(0, 8, 0, 8)
    gridLayout.Parent = picker
end

local targetPart = workspace:WaitForChild("MaPart")

local materials = {
    Enum.Material.Plastic,
    Enum.Material.Wood,
    Enum.Material.Metal,
    Enum.Material.Grass,
    Enum.Material.Brick,
    Enum.Material.Concrete,
    Enum.Material.Glass,
    Enum.Material.Ice,
}

-- Construit la petite scène 3D d'aperçu pour un matériau donné
local function createPreview(material)
    local viewport = Instance.new("ViewportFrame")
    viewport.Size = UDim2.new(1, 0, 1, -20)
    viewport.BackgroundTransparency = 1
    viewport.BorderSizePixel = 0

    local worldModel = Instance.new("WorldModel")
    worldModel.Parent = viewport

    local sample = Instance.new("Part")
    sample.Size = Vector3.new(2, 2, 2)
    sample.Material = material
    sample.Color = Color3.fromRGB(255, 255, 255)
    sample.Anchored = true
    sample.Parent = worldModel

    local light = Instance.new("PointLight")
    light.Brightness = 3
    light.Range = 20
    light.Parent = sample

    local camera = Instance.new("Camera")
    camera.CFrame = CFrame.new(Vector3.new(3, 3, 3), sample.Position)
    viewport.CurrentCamera = camera
    camera.Parent = viewport

    return viewport
end

for _, material in ipairs(materials) do
    local button = Instance.new("TextButton")
    button.Name = material.Name
    button.Size = UDim2.new(0, 80, 0, 100)
    button.Text = ""
    button.AutoButtonColor = false
    button.BackgroundTransparency = 1
    button.Parent = picker

    local preview = createPreview(material)
    preview.Parent = button

    local label = Instance.new("TextLabel")
    label.Size = UDim2.new(1, 0, 0, 20)
    label.Position = UDim2.new(0, 0, 1, -20)
    label.BackgroundTransparency = 1
    label.Text = material.Name
    label.TextColor3 = Color3.fromRGB(255, 255, 255)
    label.TextScaled = true
    label.Font = Enum.Font.SourceSansBold
    label.Parent = button

    button.MouseButton1Click:Connect(function()
        targetPart.Material = material

        for _, other in ipairs(picker:GetChildren()) do
            if other:IsA("TextButton") then
                other.BackgroundColor3 = Color3.fromRGB(60, 60, 60)
            end
        end
        button.BackgroundColor3 = Color3.fromRGB(0, 140, 255)
    end)
end

Documentation Roblox :

À toi de jouer !

  1. Crée MaterialPicker dans le Frame principal, recopie le script.
  2. Teste : chaque matériau doit s’afficher avec un vrai aperçu 3D, et cliquer dessus doit changer le matériau du bloc.
  3. Modification à essayer : ajoute Enum.Material.Neon à la liste materials, et observe le rendu très différent (le néon « brille » même sans lumière).

Questions

1. Pourquoi utilise-t-on un ViewportFrame plutôt qu’une simple image pour chaque matériau ?


Conclusion

Mets-toi maintenant à la place d’un joueur qui découvre ton jeu pour la première fois : est-ce que le menu est facile à comprendre sans explication ? Est-ce qu’il sait tout de suite comment ouvrir le menu, changer les réglages, et le fermer ?

Puis imagine ce même joueur, une semaine plus tard, devenu un expert de ton jeu : qu’est-ce qui pourrait encore le surprendre ou l’amuser ? Un nouveau matériau à débloquer ? Un défi à relever ?

N’hésite pas à partager ton jeu avec tes camarades pour qu’ils le testent, et note leurs retours : c’est souvent en observant quelqu’un d’autre jouer qu’on repère les points à améliorer. Et surtout, continue à le faire évoluer — ajoute des règles, des niveaux, des surprises. C’est ton jeu maintenant !