Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU01

Le défi des blocs

Tu vas créer un petit jeu Roblox dans lequel plusieurs blocs sont placés dans un dossier.

Chaque bloc possède une couleur et peut réagir lorsque le joueur clique dessus.

Au fur et à mesure du tutoriel, tu vas apprendre à :

  • récupérer un objet dans Roblox ;
  • parcourir plusieurs objets ;
  • utiliser des variables ;
  • utiliser des conditions ;
  • réagir à un événement ;
  • modifier un objet ;
  • déplacer un objet ;
  • utiliser un Vector3 ;
  • créer une animation avec TweenService.

Le jeu commencera très simplement, puis tu vas progressivement le transformer.

La règle du jeu

Au départ, il y a trois blocs :

          PART01       PART02       PART03
           ROUGE        VERT         BLEU
             ↓            ↓            ↓
          [    ]       [    ]       [    ]

Le joueur doit cliquer sur les blocs pour les faire bouger.

Mais attention : c’est toi, le programmeur, qui vas décider de leurs règles !

Étape 1 : Comprendre le jeu avant de coder

But

Avant d’écrire une seule ligne de code, observe le jeu.

Tu dois identifier :

  • les objets ;
  • leurs noms ;
  • leurs propriétés ;
  • les actions que le joueur pourra effectuer.

Concepts clés

  • Objet
  • Propriété

Explications

Dans Roblox, presque tout ce que tu vois est un objet.

Une Part est un objet.

Elle possède des propriétés :

Nom
Couleur
Position
Taille
Anchored
...

On peut comparer un objet Roblox à une boîte qui contient des informations.

Par exemple :

Part01
 ├── Name = "Part01"
 ├── Color = rouge
 ├── Size = ...
 ├── Position = ...
 └── Anchored = true

Le programme pourra modifier ces informations.

À toi de jouer !

Ouvre Roblox Studio.

Dans Workspace, crée un dossier nommé :

FolderTest

À l’intérieur, crée trois Part :

FolderTest
│
├── Part01
├── Part02
└── Part03

Place-les à différents endroits dans la scène.

Test

Lance le jeu et vérifie que tu vois bien les trois blocs.

Questions

1. Une Part est-elle un objet Roblox ?

  • a) Oui
  • b) Non

2. Où se trouvent les trois Parts ?

  • a) Dans ServerStorage
  • b) Dans ReplicatedStorage
  • c) Dans Workspace

3. À quoi sert FolderTest ?

a) À ranger les Parts
b) À déplacer les Parts
c) À créer un joueur

Documentation Roblox

Roblox Creator Documentation – Instances

Roblox Creator Documentation – Folder

Roblox Creator Documentation – ClickDetector


Étape 3 : Lire un Folder

But

Tu vas apprendre à récupérer un objet et à utiliser ses propriétés.

Concepts clés

  • Variable
  • WaitForChild()

Explications

Une variable est comme une boîte portant une étiquette.

Nous allons créer une boîte appelée :

folderTest

et y ranger notre dossier Roblox.

Code Lua

Crée un Script dans ServerScriptService.

-- Récupère le dossier FolderKilyan dans Workspace
local folderTest = workspace:WaitForChild("FolderTest")

-- Affiche le nom et le type de l'objet
print("Folder test :", folderTest.Name, folderTest.ClassName)

Que fait le programme ?

Cette ligne :

local folderTest = workspace:WaitForChild("FolderTest")

signifie :

« Cherche FolderTest dans Workspace et place-le dans la variable folderTest. »

Puis :

folderTest.Name

récupère son nom.

Et :

folderTest.ClassName

récupère son type.

À toi de jouer !

Lance le jeu.

Regarde la fenêtre Output (Sortie)

Tu devrais voir quelque chose comme :

Folder test : FolderTest Folder

Modification

Remplace :

print("Folder test :", folderTest.Name, folderTest.ClassName)

par :

print("Mon dossier s'appelle :", folderTest.Name)

Observe le résultat.

Questions

1. Que contient la variable folderTest ?

  • a) Une Part
  • b) Un folder nommé FolderTest
  • c) Le joueur

2. À quoi sert local ?

a) À créer une variable
b) À créer une Part
c) À détecter un clic

Documentation Roblox

Roblox Creator Documentation – WaitForChild


Étape 4 : Lire toutes les Parts du Folder

But

Jusqu’à maintenant, nous savons récupérer un dossier (Folder).

Maintenant, nous voulons parcourir tous les objets qu’il contient.

Concepts clés

  • Boucle for
  • GetChildren()

Explications

Imagine que le programme ouvre la boîte FolderTest et regarde chaque objet l’un après l’autre.

FolderTest
     ↓
  Part01
     ↓
  Part02
     ↓
  Part03

C’est exactement le rôle de la boucle for.

Code Lua

local folderTest = workspace:WaitForChild("FolderTest")

-- Parcourt tous les objets directement contenus dans le Folder
for i, part in ipairs(folderTest:GetChildren()) do
	
	-- Affiche le nom et le type de chaque objet
	print("Part :", part.Name, part.ClassName)
	
end

Explication

Cette partie :

folderTest:GetChildren()

demande :

« Donne-moi tous les enfants de FolderTest. »

La boucle :

for i, part in ipairs(...)

prend ensuite chaque objet un par un.

part représente l’objet actuellement étudié.

À toi de jouer !

Lance le jeu.

Tu dois obtenir :

Part : Part01 Part
Part : Part02 Part
Part : Part03 Part

Expérience

Ajoute une quatrième Part :

Part04

Sans modifier le script.

Relance le jeu.

Que se passe-t-il ?

Le programme doit automatiquement détecter Part04.

C’est la puissance des boucles !

Questions

1. Combien de fois la boucle s’exécute-t-elle avec 3 Parts ?

  • a) 1
  • b) 2
  • c) 3

2. Que contient la variable part pendant la boucle ?

  • a) Le joueur
  • b) L’objet actuellement parcouru
  • c) Le Folder

Documentation Roblox

Roblox Creator Documentation – GetChildren

Roblox Creator Documentation – Lua for loops


Étape 5 : Vérifier que l’objet est bien une Part

But

Tous les objets d’un Folder ne sont pas forcément des Parts.

Nous allons apprendre à vérifier le type d’un objet avant de le modifier.

Concept clé

  • Condition if

Code Lua

local folderTest = workspace:WaitForChild("FolderTest")

for _, part in ipairs(folderTest:GetChildren()) do
	
	-- Vérifie si l'objet est bien une pièce physique
	if part:IsA("BasePart") then
		
		print(part.Name, part.ClassName)
		
		-- Change la couleur de la Part en rouge
		part.Color = Color3.fromRGB(255, 0, 0)
		
	end
	
end

Explications

La condition :

if part:IsA("BasePart") then

signifie :

« Est-ce que cet objet est une Part physique ? »

Si la réponse est oui, le programme exécute le code entre :

then

et

end

À toi de jouer !

Ajoute un objet différent dans le Folder, par exemple un Model.

Lance le jeu.

Le programme ne devrait pas essayer de lui appliquer une couleur.

Modification

Change :

Color3.fromRGB(255, 0, 0)

par :

Color3.fromRGB(0, 255, 0)

Que se passe-t-il ?

Questions

1. À quoi sert if ?

  • a) À répéter
  • b) À poser une question au programme
  • c) À créer une variable

2. Que vérifie IsA() ?

  • a) Le nom
  • b) La couleur
  • c) Le type d’objet

Documentation Roblox

Roblox Creator Documentation – IsA


Étape 6 : Donner une couleur différente à chaque Part

But

Tu vas maintenant utiliser plusieurs conditions.

Concepts clés

  • if
  • elseif
  • else

Code Lua

local folderTest = workspace:WaitForChild("FolderTest")

for _, part in ipairs(folderTest:GetChildren()) do
	
	-- Vérifie que l'objet est bien une Part
	if part:IsA("BasePart") then
		
		-- Part01 devient rouge
		if part.Name == "Part01" then
			part.Color = Color3.fromRGB(255, 0, 0)
		
		-- Part02 devient verte
		elseif part.Name == "Part02" then
			part.Color = Color3.fromRGB(0, 255, 0)
		
		-- Toutes les autres Parts deviennent bleues
		else
			part.Color = Color3.fromRGB(0, 0, 255)
		end
		
	end
end

Explications

Le programme pose plusieurs questions :

Est-ce Part01 ?
     ↓
    oui → rouge

    non
     ↓
Est-ce Part02 ?
     ↓
    oui → vert

    non
     ↓
    bleu

Le else signifie :

« Si aucune des conditions précédentes n’est vraie. »

À toi de jouer !

Lance le jeu.

Tu dois obtenir :

Part01 → ROUGE

Part02 → VERT

Part03 → BLEU

Expérience

Ajoute :

Part04
Part05

Elles doivent automatiquement devenir bleues.

Puis crée une condition pour Part04.

Quelle couleur veux-tu lui donner ?

Questions

Si part.Name vaut "Part01", quelle condition est vraie ?

  • a) part.Name == "Part02"
  • b) part.Name == "Part01"
  • c) part.Name == "Part03"

Documentation Roblox

Roblox Creator Documentation – Conditional statements


Étape 7 : Réagir au clic du joueur

But

Jusqu’à maintenant, le programme s’exécute automatiquement.

Nous allons maintenant attendre une action du joueur.

Concepts clés

  • Événement
  • Fonction

Explications

Un événement signifie :

« Quelque chose vient de se produire. »

Par exemple :

Le joueur clique
       ↓
événement MouseClick
       ↓
le programme réagit

Nous allons utiliser le ClickDetector.

Rajoute un ClickDetector à chaque part

Code Lua

local folderTest = workspace:WaitForChild("FolderKTest")

for _, part in ipairs(folderTest:GetChildren()) do
	
	if part:IsA("BasePart") then
		
		-- Recherche le ClickDetector placé dans la Part
		local clickDetector = part:FindFirstChildOfClass("ClickDetector")
		
		if clickDetector then
			
			-- Attend que le joueur clique sur la Part
			clickDetector.MouseClick:Connect(function(player)
				
				-- Affiche le nom du joueur et de la Part
				print(player.Name .. " a cliqué sur " .. part.Name)
				
			end)
		end
	end
end

Explication de function

Cette partie :

function(player)

crée une petite fonction.

Elle sera exécutée plus tard, lorsque le joueur cliquera.

C’est comme dire :

« Quand le clic arrivera, fais ce qui se trouve dans cette fonction. »

À toi de jouer !

Lance le jeu.

Clique sur les Parts.

Dans Output, tu dois voir :

Patrick a cliqué sur Part01
Patrick a cliqué sur Part02

Le nom dépend évidemment du joueur.

Modification

Ajoute :

print("Bravo !")

dans la fonction.

Question

Quand la fonction est-elle exécutée ?

  • a) Au lancement du jeu
  • b) Quand le joueur clique
  • c) Quand le joueur quitte

Documentation Roblox

Roblox Creator Documentation – Events

Roblox Creator Documentation – ClickDetector.MouseClick


Étape 8 : Faire bouger une Part avec un vecteur

But

Tu vas maintenant faire agir la Part lorsqu’elle est cliquée.

C’est ici que nous faisons le lien avec les mathématiques.

Concepts clés

  • Vector3
  • Vitesse

Explications

Un Vector3 contient trois valeurs :

X
Y
Z

Par exemple :

Vector3.new(50, 0, 0)

signifie :

X = 50
Y = 0
Z = 0

On demande donc à la Part de se déplacer selon l’axe X.

Code Lua

local folderTest = workspace:WaitForChild("FolderTest")

for _, part in ipairs(folderTest:GetChildren()) do
	
	if part:IsA("BasePart") then
		
		-- Recherche le ClickDetector
		local clickDetector = part:FindFirstChildOfClass("ClickDetector")
		
		if clickDetector then
			
			-- Action réalisée lorsque le joueur clique
			clickDetector.MouseClick:Connect(function(player)
				
				print(player.Name .. " a cliqué sur " .. part.Name)
				
				-- Autorise la Part à être déplacée par la physique
				part.Anchored = false
				
				-- Crée un vecteur de vitesse
				local velocityVector = Vector3.new(50, 0, 0)
				
				-- Applique le vecteur à la Part
				part.AssemblyLinearVelocity = velocityVector
				
			end)
		end
	end
end

Que représente le vecteur ?

Vector3.new(50, 0, 0)

On peut le représenter mathématiquement :v=(50;0;0)\vec{v}=(50;0;0)

La Part part dans la direction positive de X.

À toi de jouer !

Clique sur une Part.

Elle doit partir vers la droite.

Expériences

Essaie successivement :

Vector3.new(-50, 0, 0)

Puis :

Vector3.new(0, 50, 0)

Puis :

Vector3.new(0, 0, 50)

Observe les directions.

Puis essaie :

Vector3.new(30, 0, 40)

Que se passe-t-il ?

Tu viens de créer un déplacement oblique.

Question

Que signifie :

Vector3.new(0, 50, 0)
  • a) Aller vers X
  • b) Aller vers Y
  • c) Aller vers Z

Pour aller plus loin

Demande-toi :

Pourquoi Vector3 est-il intéressant pour représenter un vecteur mathématique ?

Tu peux maintenant commencer à introduire :

Vecteur mathématique
        ↓
Direction
Sens
Longueur

        ↓

Vector3 Roblox
(X, Y, Z)

Documentation Roblox

Roblox Creator Documentation – Vector3

Roblox Creator Documentation – AssemblyLinearVelocity


Étape 9 : Faire monter la Part avec TweenService

But

Jusqu’à maintenant, la Part était lancée avec une vitesse.

Maintenant, nous allons créer une animation contrôlée.

Concepts clés

  • Service
  • Tween

Explications

TweenService permet de modifier progressivement une propriété.

Par exemple :

Position initiale
       ↓
       ↓
       ↓
Position finale

Au lieu de déplacer brutalement la Part, Roblox crée une animation.

Code Lua

local folderTest = workspace:WaitForChild("FolderKilyan")

-- Récupère le service qui permet de créer des animations
local tweenService = game:GetService("TweenService")

-- Définit la durée et le type de l'animation
local tweenInfo = TweenInfo.new(
	1,
	Enum.EasingStyle.Linear,
	Enum.EasingDirection.Out,
	0,
	false,
	0
)

for _, part in ipairs(folderTest:GetChildren()) do
	
	if part:IsA("BasePart") then
		
		-- Recherche le ClickDetector
		local clickDetector = part:FindFirstChildOfClass("ClickDetector")
		
		if clickDetector then
			
			clickDetector.MouseClick:Connect(function(player)
				
				print(player.Name .. " a cliqué sur " .. part.Name)
				
				-- Vecteur de déplacement
				local moveVector = CFrame.new(0, 10, 0)
				
				-- Création de l'animation
				local tween = tweenService:Create(
					part,
					tweenInfo,
					{
						CFrame = part.CFrame * moveVector
					}
				)
				
				-- Lance l'animation
				tween:Play()
				
			end)
		end
	end
end

Que signifie le déplacement ?

CFrame.new(0, 10, 0)

correspond à :

X = 0
Y = 10
Z = 0

La Part monte donc de 10 studs.

À toi de jouer !

Clique sur une Part.

Elle doit monter progressivement.

Modification 1

Essaie :

CFrame.new(10, 0, 0)

La Part se déplace horizontalement.

Modification 2

Essaie :

CFrame.new(10, 10, 0)

La Part se déplace à la fois :

  • sur X ;
  • sur Y.

Tu viens de construire un déplacement composé de deux directions.

Question

Quelle instruction démarre réellement l’animation ?

  • a) tweenInfo
  • b) tween:Play()
  • c) CFrame.new()

Documentation Roblox

Roblox Creator Documentation – TweenService

Roblox Creator Documentation – TweenInfo


Étape 10 : Construire la règle du jeu

Nous avons maintenant suffisamment de programmation pour créer une vraie règle.

But

Tu vas combiner plusieurs concepts.

Règle proposée

Chaque couleur correspond à une action :

ROUGE → se déplace à droite

VERT → monte

BLEU → se déplace en diagonale

Code

local folderTest = workspace:WaitForChild("FolderKilyan")

for _, part in ipairs(folderTest:GetChildren()) do
	
	if part:IsA("BasePart") then
		
		-- Détermine la couleur de la Part
		if part.Name == "Part01" then
			part.Color = Color3.fromRGB(255, 0, 0)
			
		elseif part.Name == "Part02" then
			part.Color = Color3.fromRGB(0, 255, 0)
			
		else
			part.Color = Color3.fromRGB(0, 0, 255)
		end
		
		-- Recherche le ClickDetector
		local clickDetector = part:FindFirstChildOfClass("ClickDetector")
		
		if clickDetector then
			
			clickDetector.MouseClick:Connect(function(player)
				
				print(player.Name .. " a cliqué sur " .. part.Name)
				
				-- Part rouge
				if part.Name == "Part01" then
					
					part.Anchored = false
					part.AssemblyLinearVelocity = Vector3.new(50, 0, 0)
				
				-- Part verte
				elseif part.Name == "Part02" then
					
					part.Anchored = false
					part.AssemblyLinearVelocity = Vector3.new(0, 50, 0)
				
				-- Part bleue
				else
					
					part.Anchored = false
					part.AssemblyLinearVelocity = Vector3.new(30, 0, 40)
					
				end
				
			end)
		end
	end
end

Le jeu possède maintenant une règle

                JOUEUR
                   |
                 clique
                   ↓
              Quelle Part ?
              /     |     \
          ROUGE    VERT    BLEU
            ↓        ↓       ↓
          → X       ↑ Y    ↗ X+Z

Étape 11 : Défi mathématique — prédire le déplacement

Maintenant, c’est à ton tour de réfléchir avant d’exécuter le programme.

Le code indique :

local moveVector = Vector3.new(20, 0, 30)

Demande-toi :

Dans quelle direction la Part va-t-elle partir ?

Dessine le vecteur sur une feuille.

Puis teste dans Roblox.

Défi 1

Quel vecteur faut-il utiliser pour aller :

10 à droite et 20 vers l’avant ?

Réponse :

Vector3.new(10, 0, 20)

Défi 2

Quel vecteur faut-il utiliser pour aller :

20 à gauche ?

Vector3.new(-20, 0, 0)

Défi 3

Quel vecteur permet de monter de 15 studs ?

Vector3.new(0, 15, 0)

Défi 4

Construis un déplacement :

X = -20
Y = 10
Z = 30

Solution :

Vector3.new(-20, 10, 30)

Tu viens de manipuler un vecteur dans l’espace.


Ce que tu as appris

À la fin du tutoriel, tu as utilisé de nombreuses notions fondamentales :

NotionExemple dans le jeu
VariablefolderTest
Objetpart
Propriétépart.Color
Conditionif part.Name == ...
elseautre Part
Bouclefor _, part
Fonctionfunction(player)
ÉvénementMouseClick
ServiceGetService()
Vector3Vector3.new(50,0,0)
PhysiqueAssemblyLinearVelocity
AnimationTweenService
OrganisationFolder

Et surtout, tu as découvert une idée importante :

PROGRAMME

        ↓

OBSERVER
        ↓
DÉCIDER
        ↓
AGIR

C’est cette structure qui permet de transformer progressivement quelques lignes de Lua en un véritable jeu.


Pour aller beaucoup plus loin

Maintenant, mets-toi à la place du joueur.

Au début, tu ne connais pas le jeu :

« Je clique sur une Part. Que va-t-il se passer ? »

Puis tu deviens joueur expérimenté :

« La Part rouge part dans la direction X. »

Puis programmeur :

« Si je remplace Vector3.new(50,0,0) par Vector3.new(20,0,40), je change la direction du déplacement. »

Puis concepteur de jeu :

« Je vais créer une zone d’arrivée et donner des points lorsque la Part atteint sa cible. »

Tu peux alors faire évoluer le projet avec :

  1. un score ;
  2. des vies ;
  3. plusieurs niveaux ;
  4. des obstacles ;
  5. des zones interdites ;
  6. des portes ;
  7. des téléporteurs ;
  8. des vecteurs à calculer ;
  9. des angles à déterminer ;
  10. un véritable défi de géométrie dans l’espace.

Cela permettrait de faire évoluer le même projet de la sixième à la troisième, en introduisant progressivement programmation, coordonnées, déplacements, vecteurs, angles, distances et géométrie 3D.

Catégories
Jeu vidéo ROBLOX ROBLOX-NIVEAU03

Détruis une pyramide de briques

Dans ce tutoriel, tu vas créer un jeu où le joueur doit détruire une pyramide de briques en lançant une balle.
Voici ce que tu vas apprendre :

  • Construire une pyramide dynamiquement avec des tailles aléatoires.
  • Détecter les interactions (quand le joueur entre dans une zone, appuie sur un bouton, etc.).
  • Lancer une balle et vérifier si la pyramide est détruite.
  • Gérer des effets comme le recul du joueur ou la caméra en première personne.

Étape 1 : Comprendre le jeu avant de coder

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

Explications

Avant de coder, il faut imaginer le jeu :

  1. Une pyramide de briques est générée automatiquement au début.
  2. Le joueur entre dans une zone de lancer (hitbox).
  3. En appuyant sur un bouton (ProximityPrompt), il lance une balle.
  4. Si la balle détruit toutes les briques de la pyramide, le joueur gagne des points et est éjecté de la zone.
  5. Sinon, il perd un point et peut réessayer.

Objets nécessaires :

  • Une hitbox (zone invisible pour détecter le joueur).
  • Un modèle de balle (à cloner pour chaque lancer).
  • Un modèle de brique (pour construire la pyramide).
  • Un ProximityPrompt (bouton pour lancer la balle).
  • Un détecteur de pyramide (zone pour vérifier si des briques restent debout).

À toi de jouer !

  1. Dans Roblox Studio, ajoute :
    • Une Part (pour la hitbox) dans Workspace.
    • Un Modèle de balle (Part avec une forme de sphère) dans Workspace.
    • Un Modèle de brique (Part cubique) dans Workspace.
    • Un ProximityPrompt (attache-le à la hitbox).
  2. Teste : Déplace ton personnage dans la hitbox et imagine le gameplay.

QCM / Questions

Quels objets sont nécessaires pour construire la pyramide ?

  • a) Une hitbox, une balle, un ProximityPrompt.
  • b) Un modèle de brique, un détecteur de pyramide.
  • c) Un script, une image, un son.

Documentation Roblox Lua :


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

But : Organiser les objets du jeu dans l’Explorateur.
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 (pyramide, hitbox, balle, etc.).
  • ServerScriptService : Contient les scripts côté serveur (pour la logique du jeu).
  • ReplicatedStorage : Contient les modèles réutilisables (comme la balle ou la brique).
  • StarterPack : Contient des objets donnés au joueur au démarrage.

Structure recommandée :

Workspace
├── BreakPyramid (Folder)
│   ├── HitBox (Part)
│   │   └── Attachment (avec ProximityPrompt)
│   ├── Ball (Part)
│   ├── Part (Part, modèle de brique)
│   └── ControlPyramid (Part, détecteur de pyramide)
ServerScriptService
└── Script (pour la logique du jeu)

À toi de jouer !

  1. Crée un dossier BreakPyramid dans Workspace.
  2. Ajoute les objets suivants dans BreakPyramid :
    • Une Part nommée HitBox.
    • Une Part nommée Ball (forme de sphère).
    • Une Part nommée Part (modèle de brique).
    • Une Part nommée ControlPyramid (pour détecter les briques restantes).
  3. Ajoute un Attachment à HitBox et un ProximityPrompt à l’Attachment.

Étape 3 : Construire dynamiquement une pyramide de briques

But : Créer une fonction qui génère une pyramide avec des briques de tailles aléatoires.
Concepts clés : Fonctions, boucles for, nombres aléatoires, clonage d’objets, CFrame.

Explications

Pour construire une pyramide dynamiquement (sans tout faire à la main), on utilise :

  • Une boucle for pour répéter la création de briques.
  • math.random() pour générer des tailles aléatoires.
  • partTemplate:Clone() pour créer des copies de la brique de base.
  • CFrame pour positionner et orienter les briques.

Exemple concret :

  • La première brique est grande.
  • Chaque brique suivante est plus petite (pour former une pyramide).
  • Les briques sont empilées verticalement.

Code Lua

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

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

-- ==================== CONFIGURATION ====================
local PYRAMID_PARTS_COUNT = 8      -- Nombre de briques dans la pyramide
local PYRAMID_MIN_SIZE_STEP = 0.2  -- Réduction minimale de la taille à chaque brique
local PYRAMID_MAX_SIZE_STEP = 0.5  -- Réduction maximale de la taille à chaque brique

-- ==================== RÉFÉRENCES SCÈNE ====================
local breakPyramidFolder = Workspace:WaitForChild("BreakPyramid")
local pyramidPartTemplate = breakPyramidFolder:WaitForChild("Part")
local ballTemplate = breakPyramidFolder:WaitForChild("Ball")

-- on retire le modèle du workspace, il ne sert que de gabarit à cloner
ballTemplate.Parent = ServerStorage 

-- ==================== FONCTION : CONSTRUIRE LA PYRAMIDE ====================
-- (Re)construit la pyramide de briques à partir du gabarit
local function rebuildPyramid(partTemplate)
    local baseHeight = partTemplate.Position.Y + partTemplate.Size.Y / 2
    local parentFolder = partTemplate.Parent

    -- Supprime l'ancienne pyramide si elle existe
    local existingFolder = parentFolder:FindFirstChild("PyramidParts")
    if existingFolder then
        existingFolder:Destroy()
    end

    -- Crée un dossier pour les briques de la pyramide
    local folder = Instance.new("Folder")
    folder.Name = "PyramidParts"
    folder.Parent = parentFolder

    -- Taille initiale de la brique (entre minSize et maxSize)
    local minSize = partTemplate.Size.X / 2
    local maxSize = partTemplate.Size.X
    local currentHeight = baseHeight

    -- 1) Construction : toutes les briques sont créées ANCRÉES et SANS collision
    for i = 1, PYRAMID_PARTS_COUNT do
        local part = partTemplate:Clone()
        local partHeight = math.random(1, 3)  -- Hauteur aléatoire entre 1 et 3
        currentHeight = currentHeight + partHeight / 2

        part.Name = "pyramid"  -- Nom pour identifier les briques de la pyramide
        part.Anchored = true    -- Empêche la brique de bouger pendant la construction
        part.CanCollide = false -- Désactive les collisions pendant la construction
        part.CanTouch = false   -- Désactive la détection de contact
        part.Transparency = 0    -- Rend la brique visible
        part.Size = Vector3.new(
            math.random(minSize, maxSize),  -- Largeur aléatoire
            partHeight,                      -- Hauteur aléatoire
            math.random(minSize, maxSize)   -- Profondeur aléatoire
        )
        -- Positionne la brique au centre de la pyramide, avec une rotation aléatoire
        part.CFrame = CFrame.new(
            partTemplate.Position.X,
            currentHeight,
            partTemplate.Position.Z
        ) * CFrame.Angles(0, math.rad(math.random(0, 360)), 0)  -- Rotation aléatoire autour de l'axe Y

        -- Copie les propriétés visuelles du modèle
        part.BrickColor = partTemplate.BrickColor
        part.Material = partTemplate.Material

        -- Ajoute la brique au dossier
        part.Parent = folder

        -- Réduit la taille pour les prochaines briques
        minSize = minSize - PYRAMID_MIN_SIZE_STEP
        maxSize = maxSize - PYRAMID_MAX_SIZE_STEP
        currentHeight = currentHeight + partHeight / 2
    end

    -- 2) Activation : une fois la pyramide construite, on active la physique
    task.wait(0.1)  -- Petit délai pour s'assurer que tout est bien positionné
    for _, part in ipairs(folder:GetChildren()) do
        part.CanCollide = true   -- Active les collisions
        part.CanTouch = true     -- Active la détection de contact
        part.Anchored = false    -- Permet à la brique de bouger (physique)
    end
end

-- ==================== INITIALISATION ====================
rebuildPyramid(pyramidPartTemplate)  -- Construit la pyramide au démarrage

Explications du code

LigneExplication
for i = 1, PYRAMID_PARTS_COUNT doBoucle qui répète la création de briques 8 fois.
math.random(1, 3)Génère un nombre aléatoire entre 1 et 3 (hauteur de la brique).
partTemplate:Clone()Crée une copie du modèle de brique.
part.Size = Vector3.new(...)Définit la taille de la brique (largeur, hauteur, profondeur).
CFrame.new(x, y, z)Positionne la brique à un endroit précis.
CFrame.Angles(...)Applique une rotation aléatoire à la brique.
part.Anchored = trueEmpêche la brique de bouger pendant la construction.
task.wait(0.1)Attend 0.1 seconde pour que tout soit bien positionné.
part.Anchored = falsePermet à la brique de bouger (physique activée).

À toi de jouer !

  1. Copie le code dans un Script dans ServerScriptService.
  2. Teste :
    • Lance le jeu : une pyramide de 8 briques doit apparaître.
    • Observe les tailles et les rotations aléatoires.
  3. Modifie :
    • Change PYRAMID_PARTS_COUNT à 5 pour avoir une pyramide plus petite.
    • Change PYRAMID_MIN_SIZE_STEP et PYRAMID_MAX_SIZE_STEP à 0.3 pour que les briques rétrécissent plus vite.

QCM / Questions

À quoi sert la boucle for i = 1, PYRAMID_PARTS_COUNT do ?

  • a) À créer une seule brique.
  • b) À répéter la création de briques PYRAMID_PARTS_COUNT fois.
  • c) À détruire la pyramide.

Que fait partTemplate:Clone() ?

  • a) Supprime le modèle de brique.
  • b) Crée une copie du modèle de brique.
  • c) Change la couleur de la brique.

Pourquoi utilise-t-on task.wait(0.1) avant d’activer la physique ?

  • a) Pour attendre que toutes les briques soient bien positionnées.
  • b) Pour ralentir le jeu.
  • c) Pour afficher un message.

Pour aller plus loin

  • Ajoute des couleurs aléatoires aux briques avec part.BrickColor = BrickColor.new(math.random()).
  • Fais en sorte que les briques soient empilées en spirale en modifiant leur position.

Documentation Roblox Lua :


Étape 4 : Gérer une hitbox pour détecter l’arrivée du joueur

But : Détecter quand un joueur entre dans la zone de lancer.
Concepts clés : Événements, fonctions, détection de collision, ProximityPrompt.

Explications

Une hitbox est une zone invisible qui détecte quand un joueur entre ou sort.
En Lua, on utilise :

  • L’événement .Touched pour savoir quand un objet touche la hitbox.
  • L’événement .TouchEnded pour savoir quand un objet quitte la hitbox.
  • ProximityPrompt pour afficher un bouton quand le joueur est dans la zone.

Exemple concret :

  • Quand le joueur entre dans la hitbox, on peut changer sa caméra ou activer un bouton.

Code Lua

Ajoute ce code au Script précédent :

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

-- ==================== CONFIGURATION ====================
local PYRAMID_PARTS_COUNT = 8      -- Nombre de briques dans la pyramide
local PYRAMID_MIN_SIZE_STEP = 0.2  -- Réduction minimale de la taille à chaque brique
local PYRAMID_MAX_SIZE_STEP = 0.5  -- Réduction maximale de la taille à chaque brique

-- ==================== RÉFÉRENCES SCÈNE ====================
local breakPyramidFolder = Workspace:WaitForChild("BreakPyramid")
local pyramidPartTemplate = breakPyramidFolder:WaitForChild("Part")
local ballTemplate = breakPyramidFolder:WaitForChild("Ball")
local hitbox = breakPyramidFolder:WaitForChild("HitBox")
local prompt = hitbox.Attachment:WaitForChild("ProximityPrompt")

-- ==================== ÉTAT DU JEU ====================
-- playersData[player] = { inZone = bool, nbrPoints = number }
local playersData = {}

-- on retire le modèle du workspace, il ne sert que de gabarit à cloner
ballTemplate.Parent = ServerStorage 

-- ==================== FONCTION : CONSTRUIRE LA PYRAMIDE ====================
-- (Re)construit la pyramide de briques à partir du gabarit
local function rebuildPyramid(partTemplate)
	local baseHeight = partTemplate.Position.Y + partTemplate.Size.Y / 2
	local parentFolder = partTemplate.Parent

	-- Supprime l'ancienne pyramide si elle existe
	local existingFolder = parentFolder:FindFirstChild("PyramidParts")
	if existingFolder then
		existingFolder:Destroy()
	end

	-- Crée un dossier pour les briques de la pyramide
	local folder = Instance.new("Folder")
	folder.Name = "PyramidParts"
	folder.Parent = parentFolder

	-- Taille initiale de la brique (entre minSize et maxSize)
	local minSize = partTemplate.Size.X / 2
	local maxSize = partTemplate.Size.X
	local currentHeight = baseHeight

	-- 1) Construction : toutes les briques sont créées ANCRÉES et SANS collision
	for i = 1, PYRAMID_PARTS_COUNT do
		local part = partTemplate:Clone()
		local partHeight = math.random(1, 3)  -- Hauteur aléatoire entre 1 et 3
		currentHeight = currentHeight + partHeight / 2

		part.Name = "pyramid"  -- Nom pour identifier les briques de la pyramide
		part.Anchored = true    -- Empêche la brique de bouger pendant la construction
		part.CanCollide = false -- Désactive les collisions pendant la construction
		part.CanTouch = false   -- Désactive la détection de contact
		part.Transparency = 0    -- Rend la brique visible
		part.Size = Vector3.new(
			math.random(minSize, maxSize),  -- Largeur aléatoire
			partHeight,                      -- Hauteur aléatoire
			math.random(minSize, maxSize)   -- Profondeur aléatoire
		)
		-- Positionne la brique au centre de la pyramide, avec une rotation aléatoire
		part.CFrame = CFrame.new(
			partTemplate.Position.X,
			currentHeight,
			partTemplate.Position.Z
		) * CFrame.Angles(0, math.rad(math.random(0, 360)), 0)  -- Rotation aléatoire autour de l'axe Y

		-- Copie les propriétés visuelles du modèle
		part.BrickColor = partTemplate.BrickColor
		part.Material = partTemplate.Material

		-- Ajoute la brique au dossier
		part.Parent = folder

		-- Réduit la taille pour les prochaines briques
		minSize = minSize - PYRAMID_MIN_SIZE_STEP
		maxSize = maxSize - PYRAMID_MAX_SIZE_STEP
		currentHeight = currentHeight + partHeight / 2
	end

	-- 2) Activation : une fois la pyramide construite, on active la physique
	task.wait(0.1)  -- Petit délai pour s'assurer que tout est bien positionné
	for _, part in ipairs(folder:GetChildren()) do
		part.CanCollide = true   -- Active les collisions
		part.CanTouch = true     -- Active la détection de contact
		part.Anchored = false    -- Permet à la brique de bouger (physique)
	end
end

-- ==================== 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 : ZONE DE JEU ====================
-- Quand un joueur touche la hitbox
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 playersData[player] and playersData[player].inZone then return end

	playersData[player] = { inZone = true, nbrPoints = 5 }  -- 5 points de départ
	print(player.Name .. " est entré dans la zone de lancer !")
end)

-- Quand un joueur quitte la hitbox
hitbox.TouchEnded:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end
	
	if playersData[player] and playersData[player].inZone==false then return end

	-- Attend 0.5 seconde pour éviter les faux positifs (si le joueur touche encore la hitbox avec une autre partie du corps)
	task.wait(0.5)

	-- 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 playersData[player] and playersData[player].inZone==false then return end
	
	-- Le joueur a vraiment quitté la zone
	playersData[player].inZone = false	
	print(player.Name .. " a quitté la zone de lancer.")
end)

-- ==================== INITIALISATION ====================
rebuildPyramid(pyramidPartTemplate)  -- Construit la pyramide au démarrage

Explications du code

LigneExplication
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.
playersData[player]Stocke des informations sur le joueur (ex: s’il est dans la zone, son nombre de points).
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 la hitbox.
task.wait(0.5)Attend 0.5 seconde pour éviter les faux positifs (ex: si le joueur bouge rapidement).

À toi de jouer !

  1. Copie le code dans le Script existant.
  2. Teste :
    • Entre dans la hitbox avec ton personnage.
    • Ouvre la fenêtre de sortie (Affichage > Sortie) pour voir les messages.
  3. Modifie :
    • Change le nombre de points de départ (nbrPoints = 10).
    • Ajoute un print pour afficher le nombre de points du joueur quand il entre dans la zone.

QCM / Questions

À 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.

Pourquoi utilise-t-on task.wait(0.5) dans TouchEnded ?

  • a) Pour attendre que le joueur ait vraiment quitté la zone.
  • b) Pour ralentir le jeu.
  • c) Pour afficher un message.

Que fait playersData[player] = { inZone = true, nbrPoints = 5 } ?

  • a) Supprime les données du joueur.
  • b) Stocke des informations sur le joueur (s’il est dans la zone et son nombre de points).
  • c) Change le nom du joueur.

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 5 : Changer la vision du joueur en première personne

But : Passer en vue première personne quand le joueur entre dans la zone de lancer.
Concepts clés : Propriétés des objets, fonctions utilitaires, CameraMode.

Explications

En Roblox, on peut modifier la caméra du joueur pour :

  • LockFirstPerson : Vue à la première personne (comme dans un FPS).
  • Classic : Vue classique (derrière le personnage).

Exemple concret :

  • Quand le joueur entre dans la zone, on passe en première personne pour mieux viser.
  • Quand il quitte la zone, on revient en vue classique.

Code Lua

Ajoute ces fonctions au Script :

-- ==================== UTILITAIRES ====================
-- Applique une caméra first person (vue première personne verrouillée et zoomée)
local function lockFirstPersonCamera(player)
    player.CameraMode = Enum.CameraMode.LockFirstPerson
    player.CameraMinZoomDistance = 0.5
    player.CameraMaxZoomDistance = 0.5
end

-- Restaure la caméra classique du joueur
local function resetCamera(player)
    player.CameraMode = Enum.CameraMode.Classic
    player.CameraMaxZoomDistance = 400
    task.wait(0.1)  -- Attend 0.1 seconde pour que la caméra se réinitialise correctement
    player.CameraMinZoomDistance = 10
end

Puis modifie les événements Touched et TouchEnded :

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

-- ==================== CONFIGURATION ====================
local PYRAMID_PARTS_COUNT = 8      -- Nombre de briques dans la pyramide
local PYRAMID_MIN_SIZE_STEP = 0.2  -- Réduction minimale de la taille à chaque brique
local PYRAMID_MAX_SIZE_STEP = 0.5  -- Réduction maximale de la taille à chaque brique

-- ==================== RÉFÉRENCES SCÈNE ====================
local breakPyramidFolder = Workspace:WaitForChild("BreakPyramid")
local pyramidPartTemplate = breakPyramidFolder:WaitForChild("Part")
local ballTemplate = breakPyramidFolder:WaitForChild("Ball")
local hitbox = breakPyramidFolder:WaitForChild("HitBox")
local prompt = hitbox.Attachment:WaitForChild("ProximityPrompt")

-- ==================== ÉTAT DU JEU ====================
-- playersData[player] = { inZone = bool, nbrPoints = number }
local playersData = {}

-- on retire le modèle du workspace, il ne sert que de gabarit à cloner
ballTemplate.Parent = ServerStorage 

-- ==================== FONCTION : CONSTRUIRE LA PYRAMIDE ====================
-- (Re)construit la pyramide de briques à partir du gabarit
local function rebuildPyramid(partTemplate)
	local baseHeight = partTemplate.Position.Y + partTemplate.Size.Y / 2
	local parentFolder = partTemplate.Parent

	-- Supprime l'ancienne pyramide si elle existe
	local existingFolder = parentFolder:FindFirstChild("PyramidParts")
	if existingFolder then
		existingFolder:Destroy()
	end

	-- Crée un dossier pour les briques de la pyramide
	local folder = Instance.new("Folder")
	folder.Name = "PyramidParts"
	folder.Parent = parentFolder

	-- Taille initiale de la brique (entre minSize et maxSize)
	local minSize = partTemplate.Size.X / 2
	local maxSize = partTemplate.Size.X
	local currentHeight = baseHeight

	-- 1) Construction : toutes les briques sont créées ANCRÉES et SANS collision
	for i = 1, PYRAMID_PARTS_COUNT do
		local part = partTemplate:Clone()
		local partHeight = math.random(1, 3)  -- Hauteur aléatoire entre 1 et 3
		currentHeight = currentHeight + partHeight / 2

		part.Name = "pyramid"  -- Nom pour identifier les briques de la pyramide
		part.Anchored = true    -- Empêche la brique de bouger pendant la construction
		part.CanCollide = false -- Désactive les collisions pendant la construction
		part.CanTouch = false   -- Désactive la détection de contact
		part.Transparency = 0    -- Rend la brique visible
		part.Size = Vector3.new(
			math.random(minSize, maxSize),  -- Largeur aléatoire
			partHeight,                      -- Hauteur aléatoire
			math.random(minSize, maxSize)   -- Profondeur aléatoire
		)
		-- Positionne la brique au centre de la pyramide, avec une rotation aléatoire
		part.CFrame = CFrame.new(
			partTemplate.Position.X,
			currentHeight,
			partTemplate.Position.Z
		) * CFrame.Angles(0, math.rad(math.random(0, 360)), 0)  -- Rotation aléatoire autour de l'axe Y

		-- Copie les propriétés visuelles du modèle
		part.BrickColor = partTemplate.BrickColor
		part.Material = partTemplate.Material

		-- Ajoute la brique au dossier
		part.Parent = folder

		-- Réduit la taille pour les prochaines briques
		minSize = minSize - PYRAMID_MIN_SIZE_STEP
		maxSize = maxSize - PYRAMID_MAX_SIZE_STEP
		currentHeight = currentHeight + partHeight / 2
	end

	-- 2) Activation : une fois la pyramide construite, on active la physique
	task.wait(0.1)  -- Petit délai pour s'assurer que tout est bien positionné
	for _, part in ipairs(folder:GetChildren()) do
		part.CanCollide = true   -- Active les collisions
		part.CanTouch = true     -- Active la détection de contact
		part.Anchored = false    -- Permet à la brique de bouger (physique)
	end
end

-- ==================== UTILITAIRES ====================
-- Applique une caméra first person (vue première personne verrouillée et zoomée)
local function lockFirstPersonCamera(player)
	player.CameraMode = Enum.CameraMode.LockFirstPerson
	player.CameraMinZoomDistance = 0.5
	player.CameraMaxZoomDistance = 0.5
end

-- Restaure la caméra classique du joueur
local function resetCamera(player)
	player.CameraMode = Enum.CameraMode.Classic
	player.CameraMaxZoomDistance = 400
	task.wait(0.1)  -- Attend 0.1 seconde pour que la caméra se réinitialise correctement
	player.CameraMinZoomDistance = 10
end
-- 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 : ZONE DE JEU ====================
-- Quand un joueur touche la hitbox
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 playersData[player] and playersData[player].inZone then return end

	playersData[player] = { inZone = true, nbrPoints = 5 }  -- 5 points de départ
	print(player.Name .. " est entré dans la zone de lancer !")
	lockFirstPersonCamera(player)
end)

-- Quand un joueur quitte la hitbox
hitbox.TouchEnded:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end
	
	if playersData[player] and playersData[player].inZone==false then return end

	-- Attend 0.5 seconde pour éviter les faux positifs (si le joueur touche encore la hitbox avec une autre partie du corps)
	task.wait(0.5)

	-- 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 playersData[player] and playersData[player].inZone==false then return end
	
	-- Le joueur a vraiment quitté la zone
	playersData[player].inZone = false	
	print(player.Name .. " a quitté la zone de lancer.")
	resetCamera(player)
end)

-- ==================== INITIALISATION ====================
rebuildPyramid(pyramidPartTemplate)  -- Construit la pyramide au démarrage

Explications du code

LigneExplication
player.CameraMode = Enum.CameraMode.LockFirstPersonForce la vue à la première personne.
player.CameraMinZoomDistance = 0.5Définit la distance minimale de zoom.
player.CameraMaxZoomDistance = 0.5Définit la distance maximale de zoom.
task.wait(0.1)Attend 0.1 seconde pour éviter les bugs de caméra.
resetCamera(player)Réinitialise la caméra en vue classique.

À toi de jouer !

  1. Copie le code dans le Script.
  2. Teste :
    • Entre dans la hitbox : la vue doit passer en première personne.
    • Quitte la hitbox : la vue doit revenir en classique.
  3. Modifie :
    • Change CameraMinZoomDistance à 1.0 et observe la différence.
    • Essaie de désactiver LockFirstPerson et utilise Enum.CameraMode.FirstPerson pour voir la différence.

QCM / Questions

Que fait player.CameraMode = Enum.CameraMode.LockFirstPerson ?

  • a) Passe en vue à la troisième personne.
  • b) Passe en vue à la première personne (verrouillée).
  • c) Désactive la caméra.

Pourquoi utilise-t-on task.wait(0.1) dans resetCamera ?

  • a) Pour attendre que la caméra se réinitialise correctement.
  • b) Pour ralentir le jeu.
  • c) Pour afficher un message.

Documentation Roblox Lua :


Étape 6 : Proposer le lancement d’une balle avec un ProximityPrompt

But : Permettre au joueur de lancer une balle en appuyant sur un bouton.
Concepts clés : ProximityPrompt, clonage d’objets, physique, RemoteEvent (optionnel).

Explications

Un ProximityPrompt est un bouton qui apparaît quand le joueur est proche d’un objet.
Quand le joueur appuie sur le bouton (par défaut, la touche E), on peut :

  1. Cloner la balle depuis ReplicatedStorage.
  2. Positionner la balle devant le joueur.
  3. Appliquer une force pour la lancer.

Exemple concret :

  • Le joueur voit un bouton « Lancer la balle » quand il est dans la hitbox.
  • En appuyant sur E, une balle est lancée devant lui.

Code Lua

Ajoute ce code au Script :

-- ==================== UTILITAIRES ====================
-- Fait apparaître une balle devant le joueur et lui applique une impulsion
local function fireBall(player)
    local character = player.Character or player.CharacterAdded:Wait()
    local hrp = character:WaitForChild("HumanoidRootPart")

    local ball = ballTemplate:Clone()
    ball.Parent = workspace
    ball.CFrame = hrp.CFrame * CFrame.new(-2, 2, 0)  -- Position devant le joueur

    -- Applique une vitesse initiale vers l'avant + vers le haut
    ball.AssemblyLinearVelocity = hrp.CFrame.LookVector * 60 + Vector3.new(0, 60, 0)

    -- Détruit la balle après 2 secondes
    game:GetService("Debris"):AddItem(ball, 2)
end

-- ==================== ÉVÉNEMENT : PROXIMITY PROMPT ====================
prompt.Triggered:Connect(function(player)
    local data = playersData[player]
    if not data or not data.inZone then return end  -- Vérifie que le joueur est bien dans la zone

    fireBall(player)
    print(player.Name .. " a lancé une balle !")
end)

Exemple du code modifié :

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

-- ==================== CONFIGURATION ====================
local PYRAMID_PARTS_COUNT = 8      -- Nombre de briques dans la pyramide
local PYRAMID_MIN_SIZE_STEP = 0.2  -- Réduction minimale de la taille à chaque brique
local PYRAMID_MAX_SIZE_STEP = 0.5  -- Réduction maximale de la taille à chaque brique

-- ==================== RÉFÉRENCES SCÈNE ====================
local breakPyramidFolder = Workspace:WaitForChild("BreakPyramid")
local pyramidPartTemplate = breakPyramidFolder:WaitForChild("Part")
local ballTemplate = breakPyramidFolder:WaitForChild("Ball")
local hitbox = breakPyramidFolder:WaitForChild("HitBox")
local prompt = hitbox.Attachment:WaitForChild("ProximityPrompt")

-- ==================== ÉTAT DU JEU ====================
-- playersData[player] = { inZone = bool, nbrPoints = number }
local playersData = {}

-- on retire le modèle du workspace, il ne sert que de gabarit à cloner
ballTemplate.Parent = ServerStorage 

-- ==================== FONCTION : CONSTRUIRE LA PYRAMIDE ====================
-- (Re)construit la pyramide de briques à partir du gabarit
local function rebuildPyramid(partTemplate)
	local baseHeight = partTemplate.Position.Y + partTemplate.Size.Y / 2
	local parentFolder = partTemplate.Parent

	-- Supprime l'ancienne pyramide si elle existe
	local existingFolder = parentFolder:FindFirstChild("PyramidParts")
	if existingFolder then
		existingFolder:Destroy()
	end

	-- Crée un dossier pour les briques de la pyramide
	local folder = Instance.new("Folder")
	folder.Name = "PyramidParts"
	folder.Parent = parentFolder

	-- Taille initiale de la brique (entre minSize et maxSize)
	local minSize = partTemplate.Size.X / 2
	local maxSize = partTemplate.Size.X
	local currentHeight = baseHeight

	-- 1) Construction : toutes les briques sont créées ANCRÉES et SANS collision
	for i = 1, PYRAMID_PARTS_COUNT do
		local part = partTemplate:Clone()
		local partHeight = math.random(1, 3)  -- Hauteur aléatoire entre 1 et 3
		currentHeight = currentHeight + partHeight / 2

		part.Name = "pyramid"  -- Nom pour identifier les briques de la pyramide
		part.Anchored = true    -- Empêche la brique de bouger pendant la construction
		part.CanCollide = false -- Désactive les collisions pendant la construction
		part.CanTouch = false   -- Désactive la détection de contact
		part.Transparency = 0    -- Rend la brique visible
		part.Size = Vector3.new(
			math.random(minSize, maxSize),  -- Largeur aléatoire
			partHeight,                      -- Hauteur aléatoire
			math.random(minSize, maxSize)   -- Profondeur aléatoire
		)
		-- Positionne la brique au centre de la pyramide, avec une rotation aléatoire
		part.CFrame = CFrame.new(
			partTemplate.Position.X,
			currentHeight,
			partTemplate.Position.Z
		) * CFrame.Angles(0, math.rad(math.random(0, 360)), 0)  -- Rotation aléatoire autour de l'axe Y

		-- Copie les propriétés visuelles du modèle
		part.BrickColor = partTemplate.BrickColor
		part.Material = partTemplate.Material

		-- Ajoute la brique au dossier
		part.Parent = folder

		-- Réduit la taille pour les prochaines briques
		minSize = minSize - PYRAMID_MIN_SIZE_STEP
		maxSize = maxSize - PYRAMID_MAX_SIZE_STEP
		currentHeight = currentHeight + partHeight / 2
	end

	-- 2) Activation : une fois la pyramide construite, on active la physique
	task.wait(0.1)  -- Petit délai pour s'assurer que tout est bien positionné
	for _, part in ipairs(folder:GetChildren()) do
		part.CanCollide = true   -- Active les collisions
		part.CanTouch = true     -- Active la détection de contact
		part.Anchored = false    -- Permet à la brique de bouger (physique)
	end
end

-- ==================== UTILITAIRES ====================
-- Fait apparaître une balle devant le joueur et lui applique une impulsion
local function fireBall(player)
	local character = player.Character or player.CharacterAdded:Wait()
	local hrp = character:WaitForChild("HumanoidRootPart")

	local ball = ballTemplate:Clone()
	ball.Parent = workspace
	ball.CFrame = hrp.CFrame * CFrame.new(-2, 2, 0)  -- Position devant le joueur

	-- Applique une vitesse initiale vers l'avant + vers le haut
	ball.AssemblyLinearVelocity = hrp.CFrame.LookVector * 60 + Vector3.new(0, 60, 0)

	-- Détruit la balle après 2 secondes
	game:GetService("Debris"):AddItem(ball, 2)
end

-- Applique une caméra first person (vue première personne verrouillée et zoomée)
local function lockFirstPersonCamera(player)
	player.CameraMode = Enum.CameraMode.LockFirstPerson
	player.CameraMinZoomDistance = 0.5
	player.CameraMaxZoomDistance = 0.5
end

-- Restaure la caméra classique du joueur
local function resetCamera(player)
	player.CameraMode = Enum.CameraMode.Classic
	player.CameraMaxZoomDistance = 400
	task.wait(0.1)  -- Attend 0.1 seconde pour que la caméra se réinitialise correctement
	player.CameraMinZoomDistance = 10
end
-- 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 : ZONE DE JEU ====================
-- Quand un joueur touche la hitbox
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 playersData[player] and playersData[player].inZone then return end

	playersData[player] = { inZone = true, nbrPoints = 5 }  -- 5 points de départ
	print(player.Name .. " est entré dans la zone de lancer !")
	lockFirstPersonCamera(player)
	prompt.Enabled = true
end)

-- Quand un joueur quitte la hitbox
hitbox.TouchEnded:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end
	
	if playersData[player] and playersData[player].inZone==false then return end

	-- Attend 0.5 seconde pour éviter les faux positifs (si le joueur touche encore la hitbox avec une autre partie du corps)
	task.wait(0.5)

	-- 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 playersData[player] and playersData[player].inZone==false then return end
	
	-- Le joueur a vraiment quitté la zone
	playersData[player].inZone = false	
	print(player.Name .. " a quitté la zone de lancer.")
	resetCamera(player)
	prompt.Enabled = false
end)
-- ==================== ÉVÉNEMENT : PROXIMITY PROMPT ====================
prompt.Triggered:Connect(function(player)
	local data = playersData[player]
	if not data or not data.inZone then return end  -- Vérifie que le joueur est bien dans la zone

	fireBall(player)
	print(player.Name .. " a lancé une balle !")
end)

-- ==================== INITIALISATION ====================
rebuildPyramid(pyramidPartTemplate)  -- Construit la pyramide au démarrage
prompt.Enabled = false  -- Masque le prompt au démarrage

Explications du code

LigneExplication
ballTemplate.Parent = ServerStorageDéplace le modèle de balle dans ServerStorage pour le cloner plus tard.
prompt.Triggered:Connect(...)Écoute l’événement quand le joueur appuie sur le bouton.
ballTemplate:Clone()Crée une copie de la balle.
ball.CFrame = hrp.CFrame * CFrame.new(-2, 2, 0)Positionne la balle devant le joueur (2 unités devant, 2 unités au-dessus).
ball.AssemblyLinearVelocity = ...Applique une vitesse à la balle (60 unités vers l’avant + 60 unités vers le haut).
Debris:AddItem(ball, 2)Détruit la balle après 2 secondes.

À toi de jouer !

  1. Déplace le modèle de balle dans ServerStorage (si ce n’est pas déjà fait).
    • local ServerStorage = game:GetService(« ServerStorage »)
    • local ballTemplate = breakPyramidFolder:WaitForChild(« Ball »)r
    • ballTemplate.Parent = ServerStorage
  2. Copie le code dans le Script.
  3. Teste :
    • Entre dans la hitbox.
    • Appuie sur E (ou la touche définie pour le ProximityPrompt) : une balle doit être lancée !
  4. Modifie :
    • Change la position de la balle (CFrame.new(-2, 2, 0) en CFrame.new(-3, 1, 0)).
    • Change la vitesse de la balle (60 en 80 pour un lancer plus fort).

QCM / Questions

À quoi sert prompt.Triggered:Connect(...) ?

  • a) À détecter quand le joueur appuie sur le bouton du ProximityPrompt.
  • b) À détruire le ProximityPrompt.
  • c) À changer la couleur du bouton.

Que fait ball.AssemblyLinearVelocity = hrp.CFrame.LookVector * 60 + Vector3.new(0, 60, 0) ?

  • a) Change la taille de la balle.
  • b) Détruit la balle.
  • c) Applique une vitesse à la balle vers l’avant et vers le haut.

Pour aller plus loin

  • Ajoute un son quand la balle est lancée.

Documentation Roblox Lua :


Étape 7 : Détecter que la pyramide est détruite

But : Vérifier si toutes les briques de la pyramide ont été détruites.
Concepts clés : Détection de collision, boucles for, conditions if, compteur.

Explications

Pour vérifier si la pyramide est détruite, on peut :

  1. Compter le nombre de briques encore debout dans la zone de détection (ControlPyramid).
  2. Comparer ce nombre à 0 : si aucune brique ne reste, la pyramide est détruite.

Exemple concret :

  • Après chaque lancer, on vérifie si des briques touchent encore ControlPyramid.
  • Si aucune brique ne touche ControlPyramid, le joueur a réussi !

Code Lua

Ajoute ces fonctions au Script :

-- ==================== UTILITAIRES ====================
-- Compte le nombre de briques de la pyramide encore présentes dans la zone de détection
local function countStandingPyramidParts()
    local touchingParts = workspace:GetPartsInPart(pyramidDetector)
    local total = 0
    for _, part in ipairs(touchingParts) do
        if part.Name == "pyramid" then
            total += 1
        end
    end
    return total
end

-- Applique un recul physique au joueur (utilisé après un strike réussi)
local function knockbackPlayer(player, force)
    local character = player.Character
    if not character then return end

    local hrp = character:FindFirstChild("HumanoidRootPart")
    if not hrp then return end

    hrp.AssemblyLinearVelocity = hrp.CFrame.LookVector * -force
end
-- Fait sortir proprement un joueur de la zone : reset caméra + pyramide + état
local function exitZone(player)
    local data = playersData[player]
    if not data or not data.inZone then return end

    data.inZone = false
    prompt.Enabled = true  -- Réactive le ProximityPrompt

    resetCamera(player)
    rebuildPyramid(pyramidPartTemplate)  -- Reconstruit la pyramide
end

Exemple du code modifié :

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

-- ==================== CONFIGURATION ====================
local PYRAMID_PARTS_COUNT = 8      -- Nombre de briques dans la pyramide
local PYRAMID_MIN_SIZE_STEP = 0.2  -- Réduction minimale de la taille à chaque brique
local PYRAMID_MAX_SIZE_STEP = 0.5  -- Réduction maximale de la taille à chaque brique

-- ==================== RÉFÉRENCES SCÈNE ====================
local breakPyramidFolder = Workspace:WaitForChild("BreakPyramid")
local pyramidPartTemplate = breakPyramidFolder:WaitForChild("Part")
local ballTemplate = breakPyramidFolder:WaitForChild("Ball")
local hitbox = breakPyramidFolder:WaitForChild("HitBox")
local prompt = hitbox.Attachment:WaitForChild("ProximityPrompt")
local pyramidDetector = breakPyramidFolder:WaitForChild("ControlPyramid") -- zone qui détecte les briques encore debout

-- ==================== ÉTAT DU JEU ====================
-- playersData[player] = { inZone = bool, nbrPoints = number }
local playersData = {}

-- on retire le modèle du workspace, il ne sert que de gabarit à cloner
ballTemplate.Parent = ServerStorage 

-- ==================== FONCTION : CONSTRUIRE LA PYRAMIDE ====================
-- (Re)construit la pyramide de briques à partir du gabarit
local function rebuildPyramid(partTemplate)
	local baseHeight = partTemplate.Position.Y + partTemplate.Size.Y / 2
	local parentFolder = partTemplate.Parent

	-- Supprime l'ancienne pyramide si elle existe
	local existingFolder = parentFolder:FindFirstChild("PyramidParts")
	if existingFolder then
		existingFolder:Destroy()
	end

	-- Crée un dossier pour les briques de la pyramide
	local folder = Instance.new("Folder")
	folder.Name = "PyramidParts"
	folder.Parent = parentFolder

	-- Taille initiale de la brique (entre minSize et maxSize)
	local minSize = partTemplate.Size.X / 2
	local maxSize = partTemplate.Size.X
	local currentHeight = baseHeight

	-- 1) Construction : toutes les briques sont créées ANCRÉES et SANS collision
	for i = 1, PYRAMID_PARTS_COUNT do
		local part = partTemplate:Clone()
		local partHeight = math.random(1, 3)  -- Hauteur aléatoire entre 1 et 3
		currentHeight = currentHeight + partHeight / 2

		part.Name = "pyramid"  -- Nom pour identifier les briques de la pyramide
		part.Anchored = true    -- Empêche la brique de bouger pendant la construction
		part.CanCollide = false -- Désactive les collisions pendant la construction
		part.CanTouch = false   -- Désactive la détection de contact
		part.Transparency = 0    -- Rend la brique visible
		part.Size = Vector3.new(
			math.random(minSize, maxSize),  -- Largeur aléatoire
			partHeight,                      -- Hauteur aléatoire
			math.random(minSize, maxSize)   -- Profondeur aléatoire
		)
		-- Positionne la brique au centre de la pyramide, avec une rotation aléatoire
		part.CFrame = CFrame.new(
			partTemplate.Position.X,
			currentHeight,
			partTemplate.Position.Z
		) * CFrame.Angles(0, math.rad(math.random(0, 360)), 0)  -- Rotation aléatoire autour de l'axe Y

		-- Copie les propriétés visuelles du modèle
		part.BrickColor = partTemplate.BrickColor
		part.Material = partTemplate.Material

		-- Ajoute la brique au dossier
		part.Parent = folder

		-- Réduit la taille pour les prochaines briques
		minSize = minSize - PYRAMID_MIN_SIZE_STEP
		maxSize = maxSize - PYRAMID_MAX_SIZE_STEP
		currentHeight = currentHeight + partHeight / 2
	end

	-- 2) Activation : une fois la pyramide construite, on active la physique
	task.wait(0.1)  -- Petit délai pour s'assurer que tout est bien positionné
	for _, part in ipairs(folder:GetChildren()) do
		part.CanCollide = true   -- Active les collisions
		part.CanTouch = true     -- Active la détection de contact
		part.Anchored = false    -- Permet à la brique de bouger (physique)
	end
end

-- ==================== UTILITAIRES ====================
-- Fait apparaître une balle devant le joueur et lui applique une impulsion
local function fireBall(player)
	local character = player.Character or player.CharacterAdded:Wait()
	local hrp = character:WaitForChild("HumanoidRootPart")

	local ball = ballTemplate:Clone()
	ball.Parent = workspace
	ball.CFrame = hrp.CFrame * CFrame.new(-2, 2, 0)  -- Position devant le joueur

	-- Applique une vitesse initiale vers l'avant + vers le haut
	ball.AssemblyLinearVelocity = hrp.CFrame.LookVector * 60 + Vector3.new(0, 60, 0)

	-- Détruit la balle après 2 secondes
	game:GetService("Debris"):AddItem(ball, 2)
end

-- Applique une caméra first person (vue première personne verrouillée et zoomée)
local function lockFirstPersonCamera(player)
	player.CameraMode = Enum.CameraMode.LockFirstPerson
	player.CameraMinZoomDistance = 0.5
	player.CameraMaxZoomDistance = 0.5
end

-- Restaure la caméra classique du joueur
local function resetCamera(player)
	player.CameraMode = Enum.CameraMode.Classic
	player.CameraMaxZoomDistance = 400
	task.wait(0.1)  -- Attend 0.1 seconde pour que la caméra se réinitialise correctement
	player.CameraMinZoomDistance = 10
end
-- 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
-- Compte le nombre de briques de la pyramide encore présentes dans la zone de détection
local function countStandingPyramidParts()
	local touchingParts = workspace:GetPartsInPart(pyramidDetector)
	local total = 0
	for _, part in ipairs(touchingParts) do
		if part.Name == "pyramid" then
			total += 1
		end
	end
	return total
end

-- Applique un recul physique au joueur (utilisé après un strike réussi)
local function knockbackPlayer(player, force)
	local character = player.Character
	if not character then return end

	local hrp = character:FindFirstChild("HumanoidRootPart")
	if not hrp then return end

	hrp.AssemblyLinearVelocity = hrp.CFrame.LookVector * -force
end
-- Fait sortir proprement un joueur de la zone : reset caméra + pyramide + état
local function exitZone(player)
	local data = playersData[player]
	if not data or not data.inZone then return end

	data.inZone = false
	prompt.Enabled = true  -- Réactive le ProximityPrompt

	resetCamera(player)
	
end
-- ==================== ÉVÉNEMENTS : ZONE DE JEU ====================
-- Quand un joueur touche la hitbox
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 playersData[player] and playersData[player].inZone then return end

	playersData[player] = { inZone = true, nbrPoints = 5 }  -- 5 points de départ
	print(player.Name .. " est entré dans la zone de lancer !")
	lockFirstPersonCamera(player)
	prompt.Enabled = true
end)

-- Quand un joueur quitte la hitbox
hitbox.TouchEnded:Connect(function(hit)
	local player = getPlayerFromTouchedPart(hit)
	if not player then return end
	
	if playersData[player] and playersData[player].inZone==false then return end

	-- Attend 0.5 seconde pour éviter les faux positifs (si le joueur touche encore la hitbox avec une autre partie du corps)
	task.wait(0.5)

	-- 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 playersData[player] and playersData[player].inZone==false then return end
	
	-- Le joueur a vraiment quitté la zone
	playersData[player].inZone = false	
	print(player.Name .. " a quitté la zone de lancer.")
	resetCamera(player)
	prompt.Enabled = false
end)
-- ==================== ÉVÉNEMENT : PROXIMITY PROMPT ====================
prompt.Triggered:Connect(function(player)
	local data = playersData[player]
	if not data or not data.inZone then return end  -- Vérifie que le joueur est bien dans la zone

	fireBall(player)

	-- Attend 2 secondes pour laisser le temps à la balle de toucher les briques
	prompt.Enabled = false
	task.wait(1)
	prompt.Enabled = true

	if countStandingPyramidParts() > 0 then
		-- Raté : il reste des briques debout
		if data.nbrPoints > 0 then
			data.nbrPoints -= 1
			print(player.Name .. " a raté ! Il lui reste " .. data.nbrPoints .. " points.")
		end
		prompt.Enabled = true  -- Réactive le bouton
	else
		-- Strike : plus aucune brique debout
		print(player.Name .. " a détruit la pyramide !")
		knockbackPlayer(player, 150)  -- Applique un recul au joueur
		exitZone(player)  -- Fait sortir le joueur de la zone
		task.wait(2)
		rebuildPyramid(pyramidPartTemplate)  -- Reconstruit la pyramide
	end
	
end)

-- ==================== INITIALISATION ====================
rebuildPyramid(pyramidPartTemplate)  -- Construit la pyramide au démarrage
prompt.Enabled = false  -- Masque le prompt au démarrage

Explications du code

LigneExplication
workspace:GetPartsInPart(pyramidDetector)Récupère toutes les parties qui touchent ControlPyramid.
for _, part in ipairs(touchingParts) doParcourt toutes les parties en contact avec ControlPyramid.
if part.Name == "pyramid" thenVérifie si la partie est une brique de la pyramide.
countStandingPyramidParts()Compte le nombre de briques encore debout.
knockbackPlayer(player, 150)Applique un recul au joueur (force de 150).
exitZone(player)Fait sortir le joueur de la zone et reconstruit la pyramide.

À toi de jouer !

  1. Copie le code dans le Script.
  2. Teste :
    • Entre dans la hitbox et lance des balles jusqu’à détruire la pyramide.
    • Observe le recul du joueur quand il réussit.
  3. Modifie :
    • Change la force du recul (150 en 200).
    • Change le nombre de points perdus en cas d’échec (data.nbrPoints -= 2).

QCM / Questions

À quoi sert workspace:GetPartsInPart(pyramidDetector) ?

  • a) À détruire ControlPyramid.
  • b) À récupérer toutes les parties qui touchent ControlPyramid.
  • c) À changer la couleur de ControlPyramid.

Que fait knockbackPlayer(player, 150) ?

  • a) Applique un recul au joueur avec une force de 150.
  • b) Détruit le joueur.
  • c) Affiche un message.

Pourquoi utilise-t-on task.wait(2) après fireBall(player) ?

  • a) Pour attendre que la balle ait le temps de toucher les briques.
  • b) Pour ralentir le jeu.
  • c) Pour afficher un message.

Pour aller plus loin

  • Ajoute un système de score : Utilise ScoreManager pour ajouter des points au joueur quand il réussit.
  • Affiche un message de victoire à l’écran quand le joueur détruit la pyramide.

Documentation Roblox Lua :

Catégories
Jeu vidéo ROBLOX

Apprendre Lua sur Roblox – 10 scénarios OBBY

Ateliers de 3h · Multijoueur · Progression pédagogique


Défi 01 : La Course aux Portes

Dans un couloir géant, des portes colorées s’ouvrent ou se ferment en fonction d’une condition secrète à définir au lancement de la partie (nombre pair, tous les x, couleur, niveau de transparence, type de matériaux de la porte …). La mauvaise porte téléporte en début de partie.

Règles du jeu

  • 2 à 6 joueurs simultanés sur le même serveur
  • Chaque manche dure 90 secondes — le premier à passer 5 portes gagne
  • Une mauvaise porte = retour en début de partie
  • Si possible les indices changent aléatoirement à chaque manche et l’accès aux bonnes portes
  • Un tableau de score (Leaderboard) est visible de tous et montre le nombre de bonnes portes ouvertes et le nombre de parties gagnées

Techniques Lua abordées

  • Variables locales/globales : Stocker la couleur de la porte et le score du joueur
  • Instruction if/elseif/else : Tester la condition de la bonne porte
  • Gérer dynamiquement et aléatoirement les propriétés des Parts
  • RemoteEvents : Communiquer entre le serveur et les clients
  • Leaderboard (Leaderstats) : Afficher les scores de tous les joueurs
  • ClickDetector pour l’ouverture des portes
  • Gérer les ressources du workspace (script.Parent, :GetChildren(), if part:IsA(« BasePart »)
  • Boucle
for _, enfant in ipairs(dossier:GetChildren()) do
    print(enfant.Name)
end

Compétences acquises

  • Déclarer et utiliser des variables
  • Écrire des conditions simples et imbriquées
  • Comprendre la différence Server/Client
  • Créer un système de score multijoueur

QCM de validation — 4 questions

1. En Lua, quelle instruction permet de tester une condition ?

  • A. check
  • B. if
  • C. test
  • D. when

2. Comment déclare-t-on une variable locale en Lua ?

  • A. var score = 0
  • B. let score = 0
  • C. local score = 0
  • D. int score = 0

3. Quel objet Roblox permet d’afficher le score de tous les joueurs ?

  • A. ScoreBoard
  • B. Leaderstats
  • C. PlayerGUI
  • D. DataStore

4. Que se passe-t-il si la condition d’un if est fausse et qu’il y a un else ?

  • A. Le script s’arrête
  • B. La condition est retestée
  • C. Le bloc else est exécuté
  • D. Une erreur apparaît

Défi 02 : Le Labyrinthe Fantôme

Des murs du labyrinthe, ou un chemin de plateformes apparaissent et disparaissent par cycles réguliers, comme des fantômes. Les joueurs doivent mémoriser et anticiper. Un joueur « Fantôme » (désigné aléatoirement) voit tous les murs en transparence et peut guider les autres via un chat.

Règles du jeu

  • 1 Fantôme (voit tout) + 1 à 5 Chasseurs (voient un brouillard)
  • Les murs disparaissent ou réapparaissent toutes les 3 secondes
  • Le Fantôme doit guider les Chasseurs jusqu’à la sortie en 2 minutes
  • Si un Chasseur touche un mur plein, il revient au départ
  • Bonus si toute l’équipe sort ensemble

Techniques Lua abordées

  • Boucle while true do : Faire clignoter les murs indéfiniment
  • task.wait(n) : Contrôler la durée entre chaque clignotement
  • Propriété Transparency : Rendre les murs visibles/invisibles
  • CanCollide : Activer/désactiver la collision des murs
  • Lire tous les joueurs local Players = game:GetService(« Players ») Players.PlayerAdded:Connect(function(player)

Compétences acquises

  • Comprendre le fonctionnement client/serveur
  • Gérer les RemoteEvent
  • Créer des boucles infinies contrôlées
  • Manipuler les propriétés des Parts Roblox
  • Concevoir un mécanisme de jeu temporisé
  • Comprendre CanCollide et Transparency

QCM de validation — 4 questions

1. Quelle boucle Lua tourne indéfiniment jusqu’à ce qu’on l’arrête ?

  • A. for i=1,10
  • B. repeat until
  • C. while true do
  • D. loop forever

2. Que fait wait(3) dans un script Roblox ?

  • A. Attend l’appui d’une touche
  • B. Pause l’exécution 3 secondes
  • C. Répète une action 3 fois
  • D. Crée un délai de 0,3 s

3. Quelle propriété rend un objet traversable sans le rendre invisible ?

  • A. Transparency = 1
  • B. CanCollide = false
  • C. Anchored = true
  • D. Locked = true

4. Comment accède-t-on à la propriété d’un objet Roblox en Lua ?

  • A. objet->propriete
  • B. objet.propriete
  • C. objet[propriete]
  • D. get(objet, propriete)

Défi 03 : Roi de la Plateforme

Une grande plateforme centrale se rétrécit progressivement grâce au TweenService. Des blocs se déplacent ou tournent sur cette plateforme et poussent les joueurs. Ceux qui tombent sont éliminés. Le dernier joueur debout devient le Roi et gagne des points. Les joueurs éliminés doivent attendre en spectateur, qu’il ne reste plus qu’un seul joueur présent sur la plateforme, pour recommencer une nouvelle partie.

La difficulté augmente : les blocs se déplacent de plus en plus vite.

Règles du jeu

  • 2 à 8 joueurs par partie
  • La plateforme perd 10% de sa taille toutes les 15 secondes
  • Un joueur éliminé peut regarder la partie en spectateur
  • Victoire = survivre jusqu’à la fin et être le dernier avec le plus de points
  • 3 manches par session, le total des Points compte pour la victoire finale

Techniques Lua abordées

  • Fonctions nommées : Organiser le code en blocs réutilisables
  • TweenService : Animer la réduction de la plateforme
  • Touched event : Détecter quand un joueur tombe dans le vide
  • Boucle for numérique : Compter les manches et les cycles de rétrécissement

Compétences acquises

  • Déclarer et appeler des fonctions
  • Utiliser TweenService pour des animations
  • Connecter des événements (Touched)
  • Structurer un jeu en manches avec boucle for

QCM de validation — 4 questions

1. Comment appelle-t-on une fonction nommée ‘demarrer’ en Lua ?

  • A. call demarrer()
  • B. run:demarrer()
  • C. demarrer()
  • D. execute(demarrer)

2. Quel service Roblox permet de créer des animations fluides ?

  • A. AnimationService
  • B. TweenService
  • C. SmoothService
  • D. TransitionService

3. Comment s’écrit une boucle for qui compte de 1 à 5 en Lua ?

  • A. for(i=0; i<5; i++)
  • B. for i in range(5)
  • C. for i = 1, 5 do
  • D. repeat 5 times

4. Quel événement détecte qu’un objet touche une Part Roblox ?

  • A. .OnTouch
  • B. .Touched:Connect
  • C. .Hit:Bind
  • D. Contact:Link

Défi 04 : La Bombe Logique

Une bombe virtuelle affiche un chrono avant son explosion. Elle raccordée par une séquence de fils de couleurs tirée au hasard. Les joueurs doivent couper les fils dans la bonne séquence. Chaque joueur reçoit une partie du code sur son écran (GUI), et doit communiquer avec les autres pour coordonner la séquence exacte de câbles à couper avant que le chrono n’atteigne zéro.

Règles du jeu

  • 3 à 5 joueurs requis (chaque joueur voit 1/3 du code)
  • La bombe explose si le mauvais câble est coupé
  • 30 secondes maximum pour désamorcer
  • Code différent à chaque partie (aléatoire)

Techniques Lua abordées

  • Tables (tableaux) : Stocker la séquence de couleurs de la bombe
  • math.random() : Générer la séquence aléatoire à chaque partie
  • ScreenGui / Frame : Afficher les indices à chaque joueur
  • ipairs / pairs : Parcourir la table des couleurs

Compétences acquises

  • Créer et manipuler des tables Lua
  • Utiliser math.random pour l’aléatoire
  • Créer une interface joueur (GUI)
  • Itérer avec ipairs sur une liste

QCM de validation — 4 questions

1. Comment crée-t-on une table vide en Lua ?

  • A. table = []
  • B. table = new Table()
  • C. table = {}
  • D. table = list()

2. Quelle fonction ajoute un élément à la fin d’une table ?

  • A. table.push()
  • B. table.add()
  • C. table.append()
  • D. table.insert()

3. Que retourne math.random(1, 6) ?

  • A. Toujours 1
  • B. Un nombre décimal entre 1 et 6
  • C. Un entier aléatoire entre 1 et 6
  • D. La somme 1+6

4. Quelle boucle est faite pour parcourir une table indexée en Lua ?

  • A. while
  • B. for k,v in pairs
  • C. for i,v in ipairs
  • D. foreach

Défi 05 : Ascenseur Infernal

Les joueurs fabriquent, dans un espace dédié, un ascenseur fonctionnel qui monte et descend entre différents étages. Chaque étage contient des obstacles différents. Le joueur programme lui-même la vitesse, les arrêts et les comportements des obstacles à chaque palier et un objet à trouver. Un mini concours final compare les ascenseurs de tous les participants.

Règles du jeu

  • Travail individuel avec présentation finale en groupe
  • L’ascenseur doit desservir au minimum 3 étages
  • Chaque étage doit avoir un obstacle scripté différent
  • Le joueur doit pouvoir appuyer sur un bouton pour appeler l’ascenseur
  • Bonus : ajouter une porte qui s’ouvre à l’arrivée

Compétences acquises

  • Manipuler CFrame et Vector3 pour le déplacement
  • Utiliser les Welds pour solidariser des objets
  • Créer des interactions ProximityPrompt
  • Organiser des données dans une table d’étages

QCM de validation — 4 questions

1. Qu’est-ce que CFrame représente dans Roblox ?

  • A. La couleur d’un objet
  • B. La position ET l’orientation d’un objet
  • C. La taille d’une Part
  • D. Le nom d’un script

2. À quoi sert un WeldConstraint entre deux Parts ?

  • A. Les rendre invisibles
  • B. Les coller ensemble pour qu’elles bougent solidairement
  • C. Les faire tomber en même temps
  • D. Les fusionner en une seule Part

3. Comment crée-t-on un point 3D à X=5, Y=10, Z=0 en Lua Roblox ?

  • A. Point(5,10,0)
  • B. Vector3.new(5,10,0)
  • C. CFrame.new(5,10,0)
  • D. Pos3D(5,10,0)

4. Quel objet Roblox crée une interaction ‘Appuyer sur E’ dans le monde ?

  • A. ButtonPart
  • B. ClickDetector
  • C. ProximityPrompt
  • D. TouchButton

Défi 06 : Sauve-qui-peut !

Un joueur est désigné « Monstre » avec une vitesse boostée. Les autres doivent traverser un OBBY classique tout en étant poursuivis. Des zones de ralentissement, de vitesse boost, et de gravité réduite sont scriptées dans l’arène. Si le Monstre te touche, tu deviens Monstre à ton tour ! Le jeu s’arrête lorsque les joueurs sont tous des monstres ou au bout d’un certain temps.

Règles du jeu

  • Monstre pour commencer, désigné aléatoirement
  • Les survivants gagnent 1 point par checkpoint franchi
  • Un survivant tagué devient immédiatement Monstre
  • Le dernier survivant gagne un bonus x3
  • Partie de 3 minutes max

Techniques Lua abordées

  • Humanoid.WalkSpeed : Modifier la vitesse du Monstre et des joueurs
  • Humanoid.JumpPower : Ajuster la hauteur de saut dans les zones spéciales
  • workspace.Gravity : Créer des zones de gravité réduite
  • GetPlayers() : Récupérer tous les joueurs pour désigner le Monstre

Compétences acquises

  • Modifier les propriétés du Humanoid
  • Sélectionner un joueur aléatoire depuis GetPlayers()
  • Créer des zones d’effet avec Touched
  • Comprendre la physique Roblox (vitesse, gravité)

QCM de validation — 4 questions

1. Quelle propriété du Humanoid contrôle la vitesse de marche ?

  • A. Humanoid.Speed
  • B. Humanoid.WalkSpeed
  • C. Character.Velocity
  • D. Player.RunSpeed

2. Que retourne Players:GetPlayers() ?

  • A. Le nombre de joueurs
  • B. Le premier joueur connecté
  • C. Une table de tous les joueurs connectés
  • D. Le joueur local

3. Comment accède-t-on au personnage d’un joueur ?

  • A. player.Model
  • B. player.Character
  • C. player.Avatar
  • D. player.Body

4. Qu’est-ce que workspace.Gravity contrôle ?

  • A. La couleur du ciel
  • B. La force de gravité sur tous les objets
  • C. La vitesse du vent
  • D. La taille des joueurs

Défi 07 : Le Voleur de Flag

Deux équipes (Rouge et Bleue) s’affrontent dans un OBBY en miroir. Chaque équipe doit traverser l’OBBY adverse, voler le flag ennemi et le ramener à sa base. Des pièges scriptés bloquent les chemins selon l’équipe du joueur. Le flag est un objet physique que le personnage « porte » grâce à un Weld. Le jeu se termine quand une équipe ramène le flag à un endroit déterminé.

Règles du jeu

  • 2 équipes de 2 à 4 joueurs chacune
  • Pour marquer, le joueur doit ramener le flag sans mourir
  • Les pièges changent d’état toutes les 10 secondes

Techniques Lua abordées

  • Teams Service :; Créer et assigner des équipes Rouge/Bleue
  • ObjectValue / BoolValue : Savoir qui porte le flag (valeur partagée)
  • RemoteEvent (FireAllClients) : Annoncer une capture à tous les joueurs
  • Weld dynamique : Attacher le flag au personnage qui le ramasse

Compétences acquises

  • Configurer le système Teams de Roblox
  • Partager des données entre Server et Clients
  • Créer des Welds dynamiques par script
  • Utiliser FireAllClients pour des annonces globales

QCM de validation — 4 questions

1. Comment vérifier l’équipe d’un joueur en Lua Roblox ?

  • A. player.Color
  • B. player.Group.Name
  • C. player.Team.Name
  • D. player.Side

2. Quelle valeur Roblox permet de stocker une référence à un objet ?

  • A. StringValue
  • B. ObjectValue
  • C. NumberValue
  • D. ReferenceValue

3. Que fait RemoteEvent:FireAllClients() ?

  • A. Envoie un signal au serveur
  • B. Envoie un signal à un seul joueur
  • C. Envoie un signal à tous les joueurs connectés
  • D. Crée un nouvel événement

4. Comment créer un nouvel objet Roblox par script ?

  • A. new Instance(‘WeldConstraint’)
  • B. Instance.new(‘WeldConstraint’)
  • C. create(‘WeldConstraint’)
  • D. Roblox.new(‘WeldConstraint’)

Défi 08 : Puzzle Coopératif

Un OBBY avec des portes géantes qui ne s’ouvrent que si PLUSIEURS joueurs appuient simultanément sur des boutons répartis dans des zones différentes. Le niveau est impossible à finir seul. Des scripts de détection comptent les joueurs sur chaque bouton et déclenchent l’ouverture seulement quand le bon nombre est atteint.

Règles du jeu

  • 3 à 5 joueurs requis (portes à 2, 3 joueurs simultanés)
  • Les boutons restent actifs 5 secondes maximum
  • Si un joueur lâche son bouton, tout le monde doit recommencer
  • Des checkpoints communs sauvegardent la progression collective
  • Fin = tous les joueurs arrivent ensemble à la salle finale

Techniques Lua abordées

  • BindableEvent : Communiquer entre scripts locaux du serveur
  • Compteur de joueurs : Détecter combien de joueurs sont sur un bouton
  • Fonctions modulaires : Organiser le code en modules réutilisables
  • BoolValue partagée : Synchroniser l’état ouvert/fermé des portes

Compétences acquises

  • Utiliser des compteurs et variables d’état
  • Coordonner plusieurs scripts avec BindableEvent
  • Concevoir une logique coopérative
  • Structurer du code modulaire et réutilisable

QCM de validation — 4 questions

1. Quelle est la différence entre BindableEvent et RemoteEvent ?

  • A. Aucune différence
  • B. BindableEvent = serveur→serveur, RemoteEvent = serveur↔client
  • C. BindableEvent = client→client uniquement
  • D. RemoteEvent ne fonctionne qu’en studio

2. Comment incrémenter un compteur de 1 en Lua ?

  • A. compteur++
  • B. compteur += 1
  • C. compteur = compteur + 1
  • D. increment(compteur)

3. Que retourne hit.Parent:FindFirstChild(‘Humanoid’) si absent ?

  • A. Une erreur
  • B. false
  • C. nil
  • D. 0

4. Pour qu’une porte détecte l’événement d’un autre script, on utilise ?

  • A. .OnServerEvent
  • B. .Event:Connect
  • C. .Listen()
  • D. .Subscribe()

Défi : 09 Arène des pièges

Chaque joueur crée un piège dans l’arène (lame rotative, boule de feu, zone électrique) puis tous jouent ensemble sur la carte collective. Le créateur du piège qui élimine le plus d’adversaires gagne. Les pièges doivent utiliser des patterns de mouvement différents (oscillation, rotation, trajectoire). Chaque joueur crée une zone bonus protégée par les pièges pour permettre la sortie de l’arène.

Règles du jeu

  • Phase 1 (45 min) : chaque joueur code son piège dans sa zone
  • Phase 2 : les pièges sont assemblés dans une arène commune
  • Chaque élimination rapporte 2 points au créateur du piège
  • Atteindre une une zone de bonus 10 points
  • Obtenir plus de 30 points permet de sortir de l’arène

Techniques Lua abordées

  • TakeDamage() : Infliger des dégâts au Humanoid au contact
  • CFrame rotation : Faire tourner un piège sur lui-même
  • math.sin() oscillation : Créer un mouvement de va-et-vient fluide
  • Respawn personnalisé : Réapparaître à un spawn aléatoire

Compétences acquises

  • Infliger et gérer les points de vie (TakeDamage)
  • Animer avec RunService.Heartbeat
  • Créer des mouvements avec math.sin et CFrame
  • Concevoir et partager ses propres mécaniques

QCM de validation — 4 questions

1. Quelle méthode inflige des dégâts à un Humanoid ?

  • A. Humanoid.Health -= 10
  • B. Humanoid:Hurt(10)
  • C. Humanoid:TakeDamage(10)
  • D. Humanoid.DealDamage(10)

2. Que fait RunService.Heartbeat:Connect(function(dt) … end) ?

  • A. Lance le jeu au démarrage
  • B. Exécute la fonction à chaque frame
  • C. Attend 1 seconde puis exécute
  • D. Connecte deux serveurs

3. math.sin(t) retourne des valeurs entre :

  • A. 0 et 1
  • B. -1 et 1
  • C. -180 et 180
  • D. 0 et 360

4. CFrame.Angles(0, math.pi, 0) représente une rotation de :

  • A. 90° sur l’axe Y
  • B. 360° sur l’axe X
  • C. 180° sur l’axe Y
  • D. 45° sur tous les axe

Défi 10 : Le Grand OBBY Final

Projet final d’atelier : les joueurs assemblent tous les mécanismes appris dans un OBBY. Chaque stage reprend une technique (portes, labyrinthes, plateformes, pièges). Les progrès sont sauvegardés dans un DataStore pour qu’un joueur puisse reprendre où il s’est arrêté. Un leaderboard global classe tous les participants de la session.

Règles du jeu

  • OBBY solo mais visible par tous les autres joueurs en temps réel
  • Reprise de tous les 9 scénarios + un boss final
  • Chaque stage complété est sauvegardé en DataStore
  • Timer global affiché pour tout le serveur
  • Cérémonie de fin : podium animé avec les 3 meilleurs temps

Techniques Lua abordées

  • DataStoreService : Sauvegarder la progression du joueur entre sessions
  • Checkpoint system : Respawn au dernier stage sauvegardé
  • ModuleScript : Centraliser le code réutilisé dans des modules
  • Intégration complète : Combiner toutes les techniques précédentes

Compétences acquises

  • Utiliser DataStoreService pour la persistance
  • Architecturer un projet Roblox complet
  • Utiliser des ModuleScripts pour organiser le code
  • Combiner toutes les compétences Lua acquises

QCM de validation — 4 questions

1. À quoi sert le DataStoreService ?

  • A. Sauvegarder des données entre les sessions de jeu
  • B. Stocker les scripts du jeu
  • C. Gérer les animations des personnages
  • D. Envoyer des données au client

2. Quelle est la clé utilisée pour identifier un joueur de façon unique ?

  • A. player.Name
  • B. player.DisplayName
  • C. player.UserId
  • D. player.AccountId

3. Qu’est-ce qu’un ModuleScript dans Roblox ?

  • A. Un script qui se lance automatiquement
  • B. Un script qui retourne des fonctions réutilisables
  • C. Un script réservé aux admins
  • D. Un script qui gère les animations

4. Que fait store:SetAsync(cle, valeur) ?

  • A. Lit une valeur dans le DataStore
  • B. Efface une valeur du DataStore
  • C. Sauvegarde une valeur dans le DataStore
  • D. Vérifie si une clé existe