Ce script permet de changer la taille du joueur quand il passe à travers un bloc spécial (Part).
Effet : Le joueur devient 30% de sa taille normale (très petit !).
Déclencheur : Quand le joueur touche la pièce Part (dans le dossier Folder).
Objets à créer dans Roblox Studio
Pour que le script fonctionne, il faut :
Créer un dossier dans le Workspace :
Nom : Folder (le script attend ce dossier).
Ajouter une pièce dans ce dossier :
Nom : Part (c’est la pièce qui déclenchera le changement de taille).
Type : BasePart (un cube, une sphère, etc.).
Crée un script dans ServerScriptService :
local heightScaleFolder = workspace:WaitForChild("Folder")
local heightScale = heightScaleFolder:WaitForChild("Part")
heightScale.Touched:Connect(function(hit)
local character = hit:FindFirstAncestorOfClass("Model")
if not character then return end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.BodyHeightScale.Value = 0.3
humanoid.BodyWidthScale.Value = 0.3
humanoid.BodyDepthScale.Value = 0.3
humanoid.HeadScale.Value = 0.3
end
end)
Explication du code pas à pas
1. Récupérer les objets dans le Workspace
local heightScaleFolder = workspace:WaitForChild("Folder")
local heightScale = heightScaleFolder:WaitForChild("Part")
workspace:WaitForChild("Folder") :
Attend que le dossier Folder existe dans le Workspace.
Si le dossier n’existe pas encore, le script attend qu’il soit créé (au lieu de planter).
heightScaleFolder:WaitForChild("Part") :
Attend que la pièce Part existe dans le dossier Folder.
💡 Pourquoi WaitForChild ?
En Lua/Roblox, si un objet n’existe pas encore au moment où le script s’exécute, le jeu pourrait planter.
WaitForChild évite ce problème en attendant que l’objet soit créé.
2. Détecter quand un joueur touche la pièce
heightScale.Touched:Connect(function(hit)
heightScale.Touched :
C’est un événement qui se déclenche quand quelque chose touche la pièce Part.
hit :
C’est l’objet qui a touché la pièce (ex: la main, la tête ou le torse du joueur).
3. Récupérer le personnage du joueur
local character = hit:FindFirstAncestorOfClass("Model")
hit:FindFirstAncestorOfClass("Model") :
Cherche le premier ancêtre de hit qui est un Model.
En Roblox, un joueur est représenté par un Model (son personnage entier).
hit est une partie du corps (ex: Head, Torso), et son ancêtre est le modèle du joueur.
🔍 Exemple : Si hit est la main du joueur (RightHand), alors : RightHand → RightArm → Torso → Character (le Model). La fonction trouve Character.
4. Récupérer l’Humanoid
local humanoid = character:FindFirstChildOfClass("Humanoid")
FindFirstChildOfClass("Humanoid") :
Cherche l’Humanoid dans le modèle du joueur.
L’Humanoid est le composant qui gère :
La vie du joueur.
Le mouvement (saut, course, etc.).
La taille (BodyHeightScale, BodyWidthScale, etc.).
5. Vérifier si l’Humanoid existe
if humanoid then
Pourquoi cette vérification ?
Si humanoid est nil (n’existe pas), le script ne fait rien (return implicite).
C’est une sécurité pour éviter les erreurs.
6. Modifier la taille du joueur
humanoid.BodyHeightScale.Value = 0.3 -- Hauteur du corps = 30% de la normale.
humanoid.BodyWidthScale.Value = 0.3 -- Largeur du corps = 30% de la normale.
humanoid.BodyDepthScale.Value = 0.3 -- Profondeur du corps = 30% de la normale.
humanoid.HeadScale.Value = 0.3 -- Taille de la tête = 30% de la normale.
BodyHeightScale : Contrôle la hauteur du corps.
BodyWidthScale : Contrôle la largeur du corps.
BodyDepthScale : Contrôle la profondeur (épaisseur) du corps.
HeadScale : Contrôle la taille de la tête.
0.3 : Le joueur sera 30% de sa taille normale (donc très petit).
💡 Pour agrandir le joueur :
Remplace 0.3 par 2 pour doubler la taille.
Remplace 0.3 par 0.5 pour réduire de moitié.
QCM pour vérifier la compréhension
Questions sur le script « Changer la taille du joueur »
Que fait workspace:WaitForChild("Folder") ?
A. Crée un dossier nommé Folder.
B. Attend que le dossier Folder existe dans le Workspace.
C. Supprime le dossier Folder.
D. Change le nom du dossier.
Que représente hit dans heightScale.Touched:Connect(function(hit) ?
A. Le dossier Folder.
B.La partie du joueur qui a touché la pièce Part (ex: la main, la tête).
C. La pièce Part elle-même.
D. Le script Lua.
À quoi sert hit:FindFirstAncestorOfClass("Model") ?
A. Trouve la pièce Part.
B. Trouve le modèle du personnage (le joueur entier).
C. Trouve l’Humanoid.
D. Change la taille de la pièce.
Que fait humanoid.BodyHeightScale.Value = 0.3 ?
A. Réduit la hauteur du corps du joueur à 30% de sa taille normale.
B. Augmente la hauteur de saut.
C. Change la couleur du joueur.
D. Supprime l’Humanoid.
Si on veut que le joueur devienne deux fois plus grand, que doit-on faire ?
A. Mettre BodyHeightScale.Value = 0.5.
B. Mettre BodyHeightScale.Value = 2.
C. Mettre BodyHeightScale.Value = 0.
D. Supprimer la ligne BodyHeightScale.
Pourquoi utilise-t-on if humanoid then ?
A. Pour créer un Humanoid.
B. Pour éviter les erreurs si l’Humanoid n’existe pas.
Cette platine Expansion Board pour UNIHIKER K10, M10 et micro:bit est une carte d’extension modulable conçue pour du prototypage et des projets robotiques. Elle combine un pilote de moteur, une alimentation avec support pour accu Li-Ion 18650 et de nombreuses E/S Gravity.
Ce script permet de donner un super-pouvoir de saut au joueur quand il touche une pièce spéciale (JumpPart).
Effet : Le joueur peut sauter beaucoup plus haut (200 unités au lieu de la valeur normale, qui est généralement autour de 7–8).
Déclencheur : Quand le joueur touche la pièce JumpPart (dans le dossier JumpFolder).
Créer un part « JumpPart » sous un folder « JumpFolder »pour donner au joueur le pouvoir de courir plus vite :
Saisie sous un script sous ServerScriptService :
local jumpFolder = workspace:waitForChild("JumpFolder")
local jumpPart = jumpFolder:waitForChild("JumpPart")
jumpPart.Touched:Connect(function(hit)
local character = hit:FindFirstAncestorOfClass("Model")
if not character then return end
local humanoid = character:FindFirstChild("Humanoid")
if humanoid then
humanoid.JumpHeight = 200
end
end)
Explication du code pas à pas
1. Récupérer les objets dans le Workspace
local jumpFolder = workspace:WaitForChild("JumpFolder")
local jumpPart = jumpFolder:WaitForChild("JumpPart")
workspace:WaitForChild("JumpFolder") :
Le script attend que le dossier JumpFolder existe dans le Workspace.
Si le dossier n’existe pas encore (par exemple, s’il est créé plus tard dans le jeu), le script ne plante pas et attend qu’il soit disponible.
jumpFolder:WaitForChild("JumpPart") :
Attend que la pièce JumpPart existe à l’intérieur du dossier JumpFolder.
💡 Pourquoi WaitForChild ?
En Roblox, les objets peuvent être créés après que le script ait commencé à s’exécuter.
Sans WaitForChild, si JumpFolder ou JumpPart n’existe pas encore, le script planterait avec une erreur.
WaitForChild permet d’éviter cela en attendant que l’objet soit prêt.
2. Détecter quand un joueur touche la pièce
jumpPart.Touched:Connect(function(hit)
jumpPart.Touched :
C’est un événement qui se déclenche chaque fois qu’un objet touche JumpPart.
function(hit) :
hit est l’objet qui a touché JumpPart (par exemple, la main, la tête ou le torse du joueur).
3. Récupérer le modèle du personnage
local character = hit:FindFirstAncestorOfClass("Model")
if not character then return end
hit:FindFirstAncestorOfClass("Model") :
Cherche le premier ancêtre de hit qui est un Model.
Dans Roblox, un joueur est représenté par un Model (son personnage complet, avec toutes ses parties : tête, bras, jambes, etc.).
hit est une partie du corps (ex: Head, RightArm), et son ancêtre est le modèle du joueur (Character).
🔍 Exemple concret : Si hit est la tête du joueur (Head), alors : Head → Character (le Model). La fonction renvoie donc Character.
if not character then return end :
Si character est nil (c’est-à-dire que hit n’a pas de modèle parent), le script s’arrête ici (return).
C’est une sécurité pour éviter les erreurs si hit n’est pas une partie d’un joueur.
4. Récupérer l’Humanoid
local humanoid = character:FindFirstChild("Humanoid")
character:FindFirstChild("Humanoid") :
Cherche l’Humanoid dans le modèle du joueur (character).
L’Humanoid est un composant essentiel qui gère :
La vie du joueur.
Le mouvement (saut, course, etc.).
La taille (BodyHeightScale, etc.).
La hauteur de saut (JumpHeight).
5. Vérifier si l’Humanoid existe et modifier la hauteur de saut
if humanoid then
humanoid.JumpHeight = 200
end
if humanoid then :
Vérifie que l’Humanoid existe avant de continuer.
Si humanoid est nil (le joueur n’a pas d’Humanoid), le script ne fait rien pour éviter les erreurs.
humanoid.JumpHeight = 200 :
Modifie la hauteur de saut du joueur à 200 unités.
Effet : Le joueur peut sauter beaucoup plus haut que d’habitude (la valeur normale est généralement entre 7 et 20).
💡 Pour ajuster la hauteur de saut :
humanoid.JumpHeight = 50 → Saut modéré.
humanoid.JumpHeight = 100 → Saut très haut.
humanoid.JumpHeight = 200 → Saut extrême (attention, le joueur peut sortir de la carte !).
QCM pour tester la compréhension
Questions sur le script « Pouvoir de sauter plus haut »
Que fait workspace:WaitForChild("JumpFolder") ?
A. Crée un dossier nommé JumpFolder.
B. Attend que le dossier JumpFolder existe dans le Workspace.
C. Supprime le dossier JumpFolder.
D. Change la couleur du dossier.
Que représente hit dans jumpPart.Touched:Connect(function(hit) ?
A. La pièce JumpPart elle-même.
B. La partie du joueur qui a touché JumpPart (ex: la main, la tête).
C. Le script Lua.
D. Le dossier JumpFolder.
À quoi sert hit:FindFirstAncestorOfClass("Model") ?
A. Trouve la pièce JumpPart.
B.Trouve le modèledu personnage (le joueur entier).
C. Trouve l’Humanoid directement.
D. Change la taille de la pièce.
Que fait humanoid.JumpHeight = 200 ?
A. Réduit la taille du joueur.
B. Donne au joueur une hauteur de saut de 200 unités.
C. Change la couleur du joueur.
D. Supprime l’Humanoid.
Pourquoi utilise-t-on if humanoid then ?
A. Pour créer un Humanoid.
B.Pour éviter les erreurs si l’Humanoid n’existe pas.
C. Pour changer la taille de la pièce.
D. Pour désactiver le script.
Que se passe-t-il si un joueur touche JumpPart ?
A. Le joueur disparaît.
B. Le joueur peut sauter beaucoup plus haut (200 unités).
C. Le joueur devient plus petit.
D. La pièce change de couleur.
Où doit-on placer la pièce JumpPart pour que le script fonctionne ?
A. Dans le Workspace directement.
B. Dans un dossier nommé JumpFolder dans le Workspace.
C. Dans le ServerScriptService.
D. Dans le StarterPack.
Si on veut que le joueur saute encore plus haut, que doit-on modifier ?
Créer un part « SpeedPart » sous un folder « SpeedFolder »pour donner au joueur le pouvoir de courir plus vite :
Saisie sous un script sous ServerScriptService :
local speedFolder = workspace:waitForChild("SpeedFolder")
local speedPart = speedFolder:waitForChild("SpeedPart")
speedPart.Touched:Connect(function(hit)
local character = hit:FindFirstAncestorOfClass("Model")
if not character then return end
local humanoid = character:FindFirstChild("Humanoid")
if humanoid then
humanoid.WalkSpeed = 100
end
end)
Comment fonctionne ce script ?
Ce script Lua crée un bonus de vitesse : quand un joueur touche le part, il devient plus rapide.
1. Les variables : ranger des objets dans des « boîtes »
local speedFolder = workspace:waitForChild("SpeedFolder")
local speedPart = speedFolder:waitForChild("SpeedPart")
Une variable, c’est comme une boîte avec une étiquette, dans laquelle on range quelque chose pour s’en servir plus tard.
speedFolder est une variable qui contient le dossier SpeedFolder (un dossier rangé dans le jeu, le workspace).
speedPart est une variable qui contient la partie SpeedPart, qui se trouve à l’intérieur de ce dossier.
waitForChild("...") veut dire « attends que cet objet existe, puis récupère-le ». C’est plus sûr que d’aller le chercher directement, car parfois le jeu met un petit moment à charger tous les objets.
2. L’événement Touched : réagir à un contact
speedPart.Touched:Connect(function(hit)
Touched est un événement : c’est un signal qui se déclenche automatiquement dès qu’un objet touche speedPart. Un événement, c’est comme une sonnette : elle ne fait rien toute seule, mais dès qu’on appuie dessus, ça déclenche une action.
:Connect(function(hit) ... end) veut dire « quand la sonnette retentit, exécute cette fonction ». La fonction reçoit une information appelée hit, qui est l’objet qui a touché la partie (par exemple, un bras, une jambe, un accessoire du joueur).
3. Récupérer le joueur à partir de l’objet touché
lua
local character = hit:FindFirstAncestorOfClass("Model")
if not character then return end
Le problème, c’est que hit n’est pas le joueur en entier : c’est juste un petit morceau de son corps. Alors le script remonte « dans l’arbre » pour retrouver le personnage complet :
FindFirstAncestorOfClass("Model") veut dire « cherche parmi les parents de cet objet, le premier qui est un Model » (le personnage entier est un Model).
if not character then return end veut dire « si on n’a rien trouvé, on arrête la fonction ici » (par sécurité, au cas où ce ne soit pas un joueur qui a touché la partie).
local humanoid = character:FindFirstChild("Humanoid")
if humanoid then
humanoid.WalkSpeed = 100
end
Chaque personnage contient un objet Humanoid, qui gère ses caractéristiques (vie, vitesse, etc.). Le script le récupère avec FindFirstChild("Humanoid"), puis change sa propriété WalkSpeed (vitesse de marche) à 100, ce qui rend le joueur beaucoup plus rapide que la normale (16 par défaut).
En résumé
Toucher la partie → l’événement se déclenche → le script retrouve le joueur → il augmente sa vitesse.
À quoi sert une variable comme ‘speedPart’ dans ce script ?
Elle sert à créer une nouvelle partie dans le jeu
Elle sert uniquement à afficher du texte à l’écran
Elle supprime l’objet du jeu
Elle sert à ranger et nommer un objet pour pouvoir le réutiliser facilement
Que représente l’événement ‘Touched’ ?
Une variable qui stocke la vitesse du joueur
Une commande qui affiche un message d’erreur
Un signal qui se déclenche automatiquement quand un objet touche la partie
Un minuteur qui se déclenche toutes les secondes
Que reçoit la fonction connectée à l’événement Touched (le paramètre ‘hit’) ?
Le nom du joueur uniquement
La vitesse actuelle du joueur
L’objet exact qui est entré en contact avec la partiet
Le dossier SpeedFolder
Pourquoi utilise-t-on ‘FindFirstAncestorOfClass(« Model »)’ sur ‘hit’ ?
Pour changer la couleur du personnage
Pour remonter jusqu’au personnage complet du joueur à partir du morceau touché
Pour créer un nouveau personnage
Pour supprimer l’objet touché
Que fait la ligne ‘humanoid.WalkSpeed = 100’ ?
Elle augmente la vitesse de marche du personnage à 100
Elle téléporte le joueur
Elle donne 100 points de vie au joueur
Elle supprime l’Humanoid
A toi de créer un nouveau part pour rendre la vitesse normal au joueur
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.
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.
Ouvre Roblox Studio, crée un nouveau lieu vide (Baseplate).
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).
Crée cette hiérarchie directement dans l’Explorer de Studio (clic droit > Insert Object).
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.
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).
Sélectionne ton Frame principal, et regarde sa propriété Size dans les Properties.
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.
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.
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.
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.
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)
Recopie ce script dans un LocalScript, dans StarterPlayer > StarterPlayerScripts.
Lance le Play Test, appuie sur la touche X : le menu doit apparaître. Clique sur CloseButton : il doit disparaître.
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
Recopie ce script dans un LocalScript, dans StarterPlayerScripts (ou dans ScreenGui).
Teste : chaque couleur de la liste doit apparaître comme un bouton, et cliquer dessus doit changer la couleur du bloc.
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.
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)
Crée SliderBar, avec Fill et Knob à l’intérieur, dans le Frame principal.
Recopie le script, teste en faisant glisser le Knob : le bloc doit devenir de plus en plus transparent.
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.
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
Crée MaterialPicker dans le Frame principal, recopie le script.
Teste : chaque matériau doit s’afficher avec un vrai aperçu 3D, et cliquer dessus doit changer le matériau du bloc.
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 !
L’UNIHIKER K10 (ci-après dénommée K10) est une carte de développement et d’apprentissage conçue pour une prise en main rapide de l’intelligence artificielle. Elle répond aux besoins d’apprentissage en programmation, IoT et IA. La carte intègre un écran LCD couleur, le Wi-Fi, le Bluetooth, une caméra, un microphone, un haut-parleur, un indicateur RGB, de multiples capteurs et de nombreuses interfaces d’extension. Grâce à ses ressources embarquées hautement intégrées, le contrôle des capteurs, les applications IoT et les projets d’intelligence artificielle tels que la reconnaissance faciale, la reconnaissance vocale et la synthèse vocale peuvent être facilement mis en œuvre sans avoir besoin de connecter d’appareils supplémentaires pendant l’apprentissage.
Fonctionnalité
Caméra intégrée et algorithmes intégrés pour la détection d’images hors ligne
Microphone intégré et algorithme intégré pour la reconnaissance vocale hors ligne
Haut-parleur intégré et algorithme intégré pour la synthèse vocale hors ligne
Écran couleur de 2,8 pouces pour une présentation des données plus claire
Interface riche, compatible avec de nombreux logiciels de programmation, bonne évolutivité
Couleur d’arrière-plan de l’affichage
Définition de la couleur de fond de l’écran K10
Connecte ton UNIHIKER K10 :
Demande le téléchargement de ton programme :
Lorsque le téléchargement est effectué, teste ton programme.
Puis réalise un test
Album photo électronique
Formatez la carte TF en FAT32 et insérez-la dans le K10. Appuyez sur le bouton A du K10 pour prendre une photo et sur le bouton B pour l’afficher.
Puis réalise un test
Afficher du texte sur l’image
Lors de l’affichage de texte par-dessus une image, désactivez l’effacement automatique et utilisez une actualisation constante de l’image pour écraser l’ancien contenu.
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
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 ?
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.
É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 ?
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
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 :
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)
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 ?
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 :
Notion
Exemple dans le jeu
Variable
folderTest
Objet
part
Propriété
part.Color
Condition
if part.Name == ...
else
autre Part
Boucle
for _, part
Fonction
function(player)
Événement
MouseClick
Service
GetService()
Vector3
Vector3.new(50,0,0)
Physique
AssemblyLinearVelocity
Animation
TweenService
Organisation
Folder
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 :
un score ;
des vies ;
plusieurs niveaux ;
des obstacles ;
des zones interdites ;
des portes ;
des téléporteurs ;
des vecteurs à calculer ;
des angles à déterminer ;
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.
Présenter Roblox Studio, l’objectif du jeu, et faire le lien entre les espaces vectoriels en maths et les objets 3D dans Roblox. Concepts clés : Espace vectoriel, coordonnées 3D, vecteurs.
Dans le ScreenGui, ajoute un Frame nommé VectorSpace (taille : 300, 200).
Dans le Frame, ajoute 6 TextButtons (noms : UpButton, DownButton, etc.).
Positionne les boutons comme ceci :
Haut : En haut au centre.
Bas : En bas au centre.
Gauche : À gauche au centre.
Droite : À droite au centre.
Avant : En bas à gauche.
Arrière : En bas à droite.
Teste : Vérifie que tous les objets sont bien placés.
QCM / Questions
Où doit-on placer les boutons de déplacement ?
a) Dans Workspace.
b) Dans StarterGui.
c) Dans ServerScriptService.
Quel objet contient le bloc mobile (Part) ?
a) Workspace > VectorSpace
b) StarterGui
c) ServerScriptService
Étape 3 : Détecter l’arrivée du joueur dans la hitbox
But : Détecter quand un joueur entre dans la zone de jeu et activer l’interface. Concepts clés : Événements, fonctions, détection de collision.
Explications
Pour détecter quand un joueur entre dans la HitBox, on utilise l’événement .Touched. Quand un joueur touche la hitbox :
On vérifie que c’est bien un personnage de joueur (et pas un autre objet).
On active l’interface pour ce joueur.
On stocke l’heure d’entrée pour éviter les bugs (ex: si le joueur entre/sort rapidement).
Pourquoi utiliser .Touched ?
C’est l’événement standard pour détecter les collisions dans Roblox.
Il se déclenche quand une partie du personnage (bras, jambes, tête) touche la hitbox.
Code Lua
Crée un Script nommé VectorSpaceScript dans ServerScriptService et copie ce code :
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5 -- Délai pour éviter les faux positifs à la sortie
-- ==================== RÉFÉRENCES WORKSPACE ====================
local vectorSpaceGameFolder = Workspace:WaitForChild("VectorSpace")
local hitbox = vectorSpaceGameFolder:WaitForChild("HitBox")
local movablePart = vectorSpaceGameFolder:WaitForChild("Part")
-- ==================== ÉTAT DU JEU ====================
local playerEntryTimestamps = {} -- [Player] = tick() au moment de l'entrée dans la zone
-- ==================== FONCTIONS UTILITAIRES ====================
-- Récupère le joueur à partir d'une partie touchée
local function getPlayerFromTouchedPart(hit)
local character = hit.Parent
if not character then return nil end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid then return nil end
return Players:GetPlayerFromCharacter(character)
end
-- ==================== ÉVÉNEMENTS DE COLLISION ====================
hitbox.Touched:Connect(function(hit)
local player = getPlayerFromTouchedPart(hit)
if not player then return end
-- Anti-spam : ignore si le joueur est déjà dans la zone
if playerEntryTimestamps[player] then return end
playerEntryTimestamps[player] = tick()
print(player.Name .. " est entré dans la zone !")
end)
hitbox.TouchEnded:Connect(function(hit)
local player = getPlayerFromTouchedPart(hit)
if not player then return end
-- Vérifie que le joueur est bien dans la zone
if not playerEntryTimestamps[player] then return end
-- Attend un délai pour éviter les faux positifs (ex: si le joueur touche encore la hitbox avec une autre partie du corps)
task.wait(EXIT_CONFIRM_DELAY)
-- Vérifie qu'aucune partie du personnage ne touche encore la hitbox
local character = player.Character
local stillTouching = false
if character then
for _, part in pairs(hitbox:GetTouchingParts()) do
if part.Parent == character then
stillTouching = true
break
end
end
end
if stillTouching then return end
if not playerEntryTimestamps[player] then return end
-- Le joueur a vraiment quitté la zone
playerEntryTimestamps[player] = nil
print(player.Name .. " a quitté la zone.")
end)
Explications du code
Ligne
Explication
Players = game:GetService("Players")
Récupère le service Players pour accéder aux joueurs.
hitbox.Touched:Connect(...)
Écoute l’événement Touched sur la hitbox.
getPlayerFromTouchedPart(hit)
Vérifie si l’objet qui a touché est un personnage de joueur.
playerEntryTimestamps[player] = tick()
Stocke l’heure d’entrée du joueur dans la zone.
hitbox.TouchEnded:Connect(...)
Écoute l’événement TouchEnded pour détecter quand le joueur quitte la hitbox.
hitbox:GetTouchingParts()
Récupère toutes les parties qui touchent encore la hitbox.
task.wait(EXIT_CONFIRM_DELAY)
Attend 0.5 seconde pour éviter les faux positifs.
À toi de jouer !
Copie le code dans le ScriptVectorSpaceScript.
Teste :
Entre dans la hitbox avec ton personnage.
Ouvre la fenêtre de sortie (Affichage > Sortie) pour voir les messages.
Modifie :
Change EXIT_CONFIRM_DELAY à 1 pour voir l’effet.
Ajoute un print pour afficher le nombre de joueurs dans la zone.
QCM / Questions
1. À quoi sert hitbox.Touched:Connect(...) ? a) À détruire la hitbox. b) À exécuter du code quand un objet touche la hitbox. c) À changer la couleur de la hitbox. Réponse : b)
2. Pourquoi utilise-t-on playerEntryTimestamps[player] ? a) Pour stocker l’heure d’entrée du joueur dans la zone. b) Pour changer le nom du joueur. c) Pour supprimer le joueur. Réponse : a)
3. Que fait hitbox:GetTouchingParts() ? a) Récupère toutes les parties qui touchent la hitbox. b) Détruit la hitbox. c) Change la taille de la hitbox. Réponse : a)
Pour aller plus loin
Affiche un message à l’écran quand le joueur entre dans la zone (utilise un ScreenGui et un TextLabel).
Étape 4 : Créer une interface utilisateur pour déplacer le bloc
But : Afficher une interface avec des boutons pour déplacer le bloc dans les 3 dimensions. Concepts clés : ScreenGui, TextButton.
Explications
Pour créer une interface utilisateur (GUI), on utilise :
Un ScreenGui : Conteneur pour les éléments d’interface.
Un Frame : Cadre pour regrouper les boutons.
Des TextButtons : Boutons cliquables.
Un PlayerGuit : Dossier propre à chaque joueur qui contient les interfaces graphiques
Pourquoi un LocalScript ?
Les interfaces utilisateur sont spécifiques à chaque joueur (chacun voit la sienne).
Récupération du dossier PlayerGuicôté serveur (pour un joueur spécifique), évitant d’utiliser un LocalScript qui s’exécute côté client. Cela permet notamment de centraliser la gestion de l’interface et de décider quand elle apparaît.
Code Lua
Modifie le code du script serveur : :
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5 -- Délai pour éviter les faux positifs à la sortie
-- ==================== RÉFÉRENCES WORKSPACE ====================
local vectorSpaceGameFolder = Workspace:WaitForChild("VectorSpace")
local hitbox = vectorSpaceGameFolder:WaitForChild("HitBox")
local movablePart = vectorSpaceGameFolder:WaitForChild("Part")
-- ==================== ÉTAT DU JEU ====================
local playerEntryTimestamps = {} -- [Player] = tick() au moment de l'entrée dans la zone
-- ==================== FONCTIONS UTILITAIRES ====================
-- Récupère le joueur à partir d'une partie touchée
local function getPlayerFromTouchedPart(hit)
local character = hit.Parent
if not character then return nil end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid then return nil end
return Players:GetPlayerFromCharacter(character)
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiElements(player)
local playerGui = player:WaitForChild("PlayerGui")
local screenGui = playerGui:WaitForChild("ScreenGui")
local vectorSpaceFrame = screenGui:WaitForChild("VectorSpace")
return vectorSpaceFrame
end
-- ==================== ÉVÉNEMENTS DE COLLISION ====================
hitbox.Touched:Connect(function(hit)
local player = getPlayerFromTouchedPart(hit)
if not player then return end
-- Anti-spam : ignore si le joueur est déjà dans la zone
if playerEntryTimestamps[player] then return end
playerEntryTimestamps[player] = tick()
local vectorSpaceFrame = getPlayerGuiElements(player)
if not vectorSpaceFrame then return end
vectorSpaceFrame.Visible = true
end)
hitbox.TouchEnded:Connect(function(hit)
local player = getPlayerFromTouchedPart(hit)
if not player then return end
if not playerEntryTimestamps[player] then return end
-- Attend un délai pour éviter les faux positifs (ex: si le joueur touche encore la hitbox avec une autre partie du corps)
task.wait(EXIT_CONFIRM_DELAY)
-- Vérifie qu'aucune partie du personnage ne touche encore la hitbox
local character = player.Character
local stillTouching = false
if character then
for _, part in pairs(hitbox:GetTouchingParts()) do
if part.Parent == character then
stillTouching = true
break
end
end
end
if stillTouching then return end
if not playerEntryTimestamps[player] then return end
-- Le joueur a vraiment quitté la zone
playerEntryTimestamps[player] = nil
local vectorSpaceFrame = getPlayerGuiElements(player)
if not vectorSpaceFrame then return end
vectorSpaceFrame.Visible = false
end)
-- Interface invisible au lancement du joueur
Players.PlayerAdded:Connect(function(player)
-- Aucune action nécessaire pour les joueurs déjà connectés
local vectorSpaceFrame = getPlayerGuiElements(player)
if not vectorSpaceFrame then return end
vectorSpaceFrame.Visible = false
end)
Pour l’instant, l’interface est masquée par défaut. On va la lier au Script serveur dans l’étape suivante.
À toi de jouer !
Modifie le script serveur dans ServerScriptService.
Teste :
Lance le jeu : l’interface ne doit pas être visible.
Personnalise l’interface :
Change la couleur de fond du Frame (BackgroundColor3).
Change le texte des boutons (Text).
Change la taille des boutons (Size).
Pour aller plus loin
Ajoute des icônes aux boutons (utilise ImageLabel).
Pour détecter les collisions, puisque les blocs sont Anchored et ROBLOX ne gère pas les collisions entre les blocs Anchored :
Raycast
Blockcast
Code Lua
1. Modifie le Script serveur (VectorSpaceScript)
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5 -- Délai pour éviter les faux positifs à la sortie
local MOVE_STEP = 1 -- Distance parcourue par clic (en studs)
-- ==================== RÉFÉRENCES WORKSPACE ====================
local vectorSpaceGameFolder = Workspace:WaitForChild("VectorSpace")
local hitbox = vectorSpaceGameFolder:WaitForChild("HitBox")
local movablePart = vectorSpaceGameFolder:WaitForChild("Part")
local wall = vectorSpaceGameFolder:WaitForChild("Wall")
-- ==================== ÉTAT DU JEU ====================
local playerEntryTimestamps = {} -- [Player] = tick() au moment de l'entrée dans la zone
local playerButtonConnections = {} -- [Player] = liste des connexions de boutons
-- ==================== RAYCAST (empêche de traverser les murs) ====================
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {hitbox}-- ==================== DÉTECTION DE COLLISION ====================
-- Touched ne se déclenche pas entre deux parts Anchored : on vérifie donc manuellement les chevauchements
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
overlapParams.FilterDescendantsInstances = {hitbox, movablePart}
-- ==================== FONCTIONS UTILITAIRES ====================
-- Récupère le joueur à partir d'une partie touchée
local function getPlayerFromTouchedPart(hit)
local character = hit.Parent
if not character then return nil end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid then return nil end
return Players:GetPlayerFromCharacter(character)
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiElements(player)
local playerGui = player:WaitForChild("PlayerGui")
local screenGui = playerGui:WaitForChild("ScreenGui")
local vectorSpaceFrame = screenGui:WaitForChild("VectorSpace")
return vectorSpaceFrame
end
-- Déplace la Part si le raycast ne détecte aucun obstacle sur le trajet
local function moveMovablePart(movablePart, axisVector, directionSign)
if not movablePart then return end
local moveOffset = axisVector * directionSign
local rayOrigin = movablePart.Position + ((movablePart.Size / 2) * directionSign)
local result = workspace:Raycast(rayOrigin, moveOffset, raycastParams)
if result then return end -- Un obstacle bloque le trajet
movablePart.Position = movablePart.Position + moveOffset
print("Bloc déplacé à :", movablePart.Position)
end
-- Nettoie les connexions de boutons d'un joueur
local function disconnectPlayerButtons(player)
local connections = playerButtonConnections[player]
if not connections then return end
for _, connection in ipairs(connections) do
connection:Disconnect()
end
playerButtonConnections[player] = nil
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiButtons(vectorSpaceFrame)
local upButton = vectorSpaceFrame:WaitForChild("UpButton")
local downButton = vectorSpaceFrame:WaitForChild("DownButton")
local leftButton = vectorSpaceFrame:WaitForChild("LeftButton")
local rightButton = vectorSpaceFrame:WaitForChild("RightButton")
local backButton = vectorSpaceFrame:WaitForChild("BackButton")
local frontButton = vectorSpaceFrame:WaitForChild("FrontButton")
return upButton, downButton, leftButton, rightButton, backButton, frontButton
end
-- Met en place les contrôles de déplacement pour un joueur
local function setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
if not player then return end
if not vectorSpaceFrame then return end
-- On récupère les boutons du joueur
local upButton, downButton, leftButton, rightButton, backButton, frontButton = getPlayerGuiButtons(vectorSpaceFrame)
-- On nettoie d'éventuelles anciennes connexions avant d'en recréer
disconnectPlayerButtons(player)
local connections = {}
table.insert(connections, downButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), -1)
end))
table.insert(connections, upButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), 1)
end))
table.insert(connections, leftButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), -1)
end))
table.insert(connections, rightButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), 1)
end))
table.insert(connections, backButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), 1)
end))
table.insert(connections, frontButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), -1)
end))
playerButtonConnections[player] = connections
end
-- ==================== ÉVÉNEMENTS DE COLLISION ====================
hitbox.Touched:Connect(function(hit)
local player = getPlayerFromTouchedPart(hit)
if not player then return end
-- Anti-spam : ignore si le joueur est déjà dans la zone
if playerEntryTimestamps[player] then return end
playerEntryTimestamps[player] = tick()
local vectorSpaceFrame = getPlayerGuiElements(player)
if not vectorSpaceFrame then return end
vectorSpaceFrame.Visible = true
setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
end)
hitbox.TouchEnded:Connect(function(hit)
local player = getPlayerFromTouchedPart(hit)
if not player then return end
if not playerEntryTimestamps[player] then return end
-- Attend un délai pour éviter les faux positifs (ex: si le joueur touche encore la hitbox avec une autre partie du corps)
task.wait(EXIT_CONFIRM_DELAY)
-- Vérifie qu'aucune partie du personnage ne touche encore la hitbox
local character = player.Character
local stillTouching = false
if character then
for _, part in pairs(hitbox:GetTouchingParts()) do
if part.Parent == character then
stillTouching = true
break
end
end
end
if stillTouching then return end
if not playerEntryTimestamps[player] then return end
-- Le joueur a vraiment quitté la zone
playerEntryTimestamps[player] = nil
local vectorSpaceFrame = getPlayerGuiElements(player)
if not vectorSpaceFrame then return end
vectorSpaceFrame.Visible = false
end)
-- Interface invisible au lancement du joueur
Players.PlayerAdded:Connect(function(player)
-- Aucune action nécessaire pour les joueurs déjà connectés
local vectorSpaceFrame = getPlayerGuiElements(player)
if not vectorSpaceFrame then return end
vectorSpaceFrame.Visible = false
end)
Explications du code
Script serveur :
Ligne
Explication
moveMovablePart(axisVector, directionSign)
Déplace le bloc dans la direction spécifiée, si aucun obstacle ne bloque le chemin.
Raycast
Vérifie s’il y a un obstacle entre la position actuelle et la nouvelle position.
moveEvent.OnServerEvent:Connect(...)
Écoute les demandes de déplacement envoyées par le client.
À toi de jouer !
Copie le code dans le Script serveur
Teste :
Entre dans la hitbox : l’interface doit apparaître.
Clique sur les boutons : le bloc doit se déplacer.
Quitte la hitbox : l’interface doit disparaître.
Modifie :
Change MOVE_STEP à 2 pour que le bloc se déplace plus vite.
Ajoute un print dans la fonction moveMovablePart pour afficher la nouvelle position.
QCM / Questions
1. Pourquoi utilise-t-on Raycast dans moveMovablePart ?
a) Pour vérifier s’il y a un obstacle entre la position actuelle et la nouvelle position.
b) Pour changer la couleur du bloc.
c) Pour détruire le bloc.
Pour aller plus loin
Ajoute un effet visuel quand le bloc se déplace (ex: une traînée de particules).
Limite les déplacements pour que le bloc ne sorte pas de la hitbox.
Étape 7 : Détecter les collisions avec les clés (Raycast et OverlapParams)
But : Détecter quand le bloc mobile touche une clé, et gérer la logique de collecte. Concepts clés : OverlapParams, GetPartsInPart, conditions, tableaux.
Explications
Problème :
L’événement .Touched ne fonctionne pas entre deux Parts ancrées (comme notre bloc mobile et les clés).
On doit donc vérifier manuellement si le bloc touche une clé après chaque déplacement.
Solution :
Utiliser workspace:GetPartsInPart(movablePart, overlapParams) pour récupérer toutes les parties qui touchent le bloc mobile.
Parcourir les parties retournées et vérifier si l’une d’elles est une clé.
Si c’est une clé, vérifier sa couleur et la comparer à la prochaine couleur attendue (KEY_COLORS[1]).
Si la couleur correspond, supprimer la clé et passer à la suivante.
Pourquoi GetPartsInPart ?
Cette fonction retourne toutes les parties qui chevauchent (overlap) une partie donnée.
On peut filtrer les résultats avec OverlapParams pour exclure certaines parties (ex: la hitbox).
Code Lua
Ajoute ce code au Script serveur (VectorSpaceScript) :
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5 -- Délai pour éviter les faux positifs à la sortie
local MOVE_STEP = 1 -- Distance parcourue par clic (en studs)
-- Couleurs des clés à collecter, dans l'ordre où elles doivent être ramassées
local KEY_COLORS = {
Color3.fromRGB(255, 0, 0), -- Rouge
Color3.fromRGB(0, 0, 255), -- Bleu
Color3.fromRGB(0, 255, 0), -- Vert
}
-- ==================== RÉFÉRENCES WORKSPACE ====================
local vectorSpaceGameFolder = Workspace:WaitForChild("VectorSpace")
local hitbox = vectorSpaceGameFolder:WaitForChild("HitBox")
local movablePart = vectorSpaceGameFolder:WaitForChild("Part")
local wall = vectorSpaceGameFolder:WaitForChild("Wall")
-- ==================== ÉTAT DU JEU ====================
local playerEntryTimestamps = {} -- [Player] = tick() au moment de l'entrée dans la zone
local playerButtonConnections = {} -- [Player] = liste des connexions de boutons
-- ==================== RAYCAST (empêche de traverser les murs) ====================
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {hitbox}
-- ==================== DÉTECTION DE COLLISION ====================
-- Touched ne se déclenche pas entre deux parts Anchored : on vérifie donc manuellement les chevauchements
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
overlapParams.FilterDescendantsInstances = {hitbox}
-- ==================== FONCTIONS UTILITAIRES ====================
-- Récupère le joueur à partir d'une partie touchée
local function getPlayerFromTouchedPart(hit)
local character = hit.Parent
if not character then return nil end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid then return nil end
return Players:GetPlayerFromCharacter(character)
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiElements(player)
local playerGui = player:WaitForChild("PlayerGui")
local screenGui = playerGui:WaitForChild("ScreenGui")
local vectorSpaceFrame = screenGui:WaitForChild("VectorSpace")
return vectorSpaceFrame
end
-- Ouvre la porte
local function openWall()
if wall then
wall:Destroy()
movablePart:Destroy()
movablePart = nil
print("La porte s'ouvre !")
end
end
-- Vérifie si le bloc mobile touche une clé, et déclenche la logique de collecte
local function checkMovablePartCollisions()
if not movablePart then return end
print("test de collision")
local touchingParts = Workspace:GetPartsInPart(movablePart, overlapParams)
print(touchingParts)
for _, touchingPart in ipairs(touchingParts) do
print(touchingPart.Name)
-- On ne réagit qu'à la clé actuellement attendue (la première de la liste)
if touchingPart.Name == "Key" and touchingPart.Color == KEY_COLORS[1] then
touchingPart:Destroy() -- Supprime la clé
table.remove(KEY_COLORS, 1) -- Retire la couleur de la liste
if #KEY_COLORS > 0 then
-- Il reste des clés à collecter : on indique la prochaine couleur attendue
movablePart.Color = KEY_COLORS[1]
print("Prochaine clé :", KEY_COLORS[1])
else
-- Toutes les clés sont collectées : victoire !
print("Toutes les clés ont été collectées !")
openWall()
end
break -- On sort de la boucle car on a trouvé une clé
end
end
end
-- Déplace la Part si le raycast ne détecte aucun obstacle sur le trajet
local function moveMovablePart(movablePart, axisVector, directionSign)
if not movablePart then return end
local moveOffset = axisVector * directionSign
local rayOrigin = movablePart.Position + ((movablePart.Size / 2) * directionSign)
local result = workspace:Raycast(rayOrigin, moveOffset, raycastParams)
if result then return end -- Un obstacle bloque le trajet
movablePart.Position = movablePart.Position + moveOffset
checkMovablePartCollisions()
end
-- Nettoie les connexions de boutons d'un joueur
local function disconnectPlayerButtons(player)
local connections = playerButtonConnections[player]
if not connections then return end
for _, connection in ipairs(connections) do
connection:Disconnect()
end
playerButtonConnections[player] = nil
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiButtons(vectorSpaceFrame)
local upButton = vectorSpaceFrame:WaitForChild("UpButton")
local downButton = vectorSpaceFrame:WaitForChild("DownButton")
local leftButton = vectorSpaceFrame:WaitForChild("LeftButton")
local rightButton = vectorSpaceFrame:WaitForChild("RightButton")
local backButton = vectorSpaceFrame:WaitForChild("BackButton")
local frontButton = vectorSpaceFrame:WaitForChild("FrontButton")
return upButton, downButton, leftButton, rightButton, backButton, frontButton
end
-- Met en place les contrôles de déplacement pour un joueur
local function setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
if not player then return end
if not vectorSpaceFrame then return end
-- On récupère les boutons du joueur
local upButton, downButton, leftButton, rightButton, backButton, frontButton = getPlayerGuiButtons(vectorSpaceFrame)
-- On nettoie d'éventuelles anciennes connexions avant d'en recréer
disconnectPlayerButtons(player)
local connections = {}
table.insert(connections, downButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), -1)
end))
table.insert(connections, upButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), 1)
end))
table.insert(connections, leftButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), -1)
end))
table.insert(connections, rightButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), 1)
end))
table.insert(connections, backButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), 1)
end))
table.insert(connections, frontButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), -1)
end))
playerButtonConnections[player] = connections
end
-- ==================== ÉVÉNEMENTS DE COLLISION ====================
hitbox.Touched:Connect(function(hit)
local player = getPlayerFromTouchedPart(hit)
if not player then return end
-- Anti-spam : ignore si le joueur est déjà dans la zone
if playerEntryTimestamps[player] then return end
playerEntryTimestamps[player] = tick()
local vectorSpaceFrame = getPlayerGuiElements(player)
if not vectorSpaceFrame then return end
vectorSpaceFrame.Visible = true
setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
end)
hitbox.TouchEnded:Connect(function(hit)
local player = getPlayerFromTouchedPart(hit)
if not player then return end
if not playerEntryTimestamps[player] then return end
-- Attend un délai pour éviter les faux positifs (ex: si le joueur touche encore la hitbox avec une autre partie du corps)
task.wait(EXIT_CONFIRM_DELAY)
-- Vérifie qu'aucune partie du personnage ne touche encore la hitbox
local character = player.Character
local stillTouching = false
if character then
for _, part in pairs(hitbox:GetTouchingParts()) do
if part.Parent == character then
stillTouching = true
break
end
end
end
if stillTouching then return end
if not playerEntryTimestamps[player] then return end
-- Le joueur a vraiment quitté la zone
playerEntryTimestamps[player] = nil
local vectorSpaceFrame = getPlayerGuiElements(player)
if not vectorSpaceFrame then return end
vectorSpaceFrame.Visible = false
end)
-- Interface invisible au lancement du joueur
Players.PlayerAdded:Connect(function(player)
-- Aucune action nécessaire pour les joueurs déjà connectés
local vectorSpaceFrame = getPlayerGuiElements(player)
if not vectorSpaceFrame then return end
vectorSpaceFrame.Visible = false
end)
Explications du code
Ligne
Explication
KEY_COLORS = {Color3.fromRGB(255, 0, 0), ...}
Définit l’ordre des couleurs des clés à collecter.
Étape 8 : Finaliser le jeu (nettoyage et optimisations)
But : Finaliser le jeu en ajoutant des optimisations et en nettoyant les connexions. Concepts clés : Gestion de la mémoire, nettoyage des connexions, optimisation.
Explications
Pour éviter les fuites mémoire (problèmes de performance), il faut :
Déconnecter les connexions quand un joueur quitte la zone ou le jeu.
Nettoyer les références aux objets détruits (ex: movablePart = nil après sa destruction).
Gérer les cas d’erreur (ex: si un joueur quitte pendant qu’il est dans la zone).
Code Lua (Script serveur final)
Voici le script complet avec toutes les optimisations :
luaCopier
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5 -- Délai pour éviter les faux positifs à la sortie
local MOVE_STEP = 1 -- Distance parcourue par clic (en studs)
-- Couleurs des clés à collecter, dans l'ordre où elles doivent être ramassées
local KEY_COLORS = {
Color3.fromRGB(255, 0, 0), -- Rouge
Color3.fromRGB(0, 0, 255), -- Bleu
Color3.fromRGB(0, 255, 0), -- Vert
}
-- ==================== RÉFÉRENCES WORKSPACE ====================
local vectorSpaceGameFolder = Workspace:WaitForChild("VectorSpace")
local hitbox = vectorSpaceGameFolder:WaitForChild("HitBox")
local movablePart = vectorSpaceGameFolder:WaitForChild("Part")
local wall = vectorSpaceGameFolder:WaitForChild("Wall")
-- ==================== ÉTAT DU JEU ====================
local playerEntryTimestamps = {} -- [Player] = tick() au moment de l'entrée dans la zone
local playerButtonConnections = {} -- [Player] = liste des connexions de boutons
-- ==================== RAYCAST (empêche de traverser les murs) ====================
local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.FilterDescendantsInstances = {hitbox}
-- ==================== DÉTECTION DE COLLISION ====================
-- Touched ne se déclenche pas entre deux parts Anchored : on vérifie donc manuellement les chevauchements
local overlapParams = OverlapParams.new()
overlapParams.FilterType = Enum.RaycastFilterType.Exclude
overlapParams.FilterDescendantsInstances = {hitbox}
-- ==================== FONCTIONS UTILITAIRES ====================
-- Récupère le joueur à partir d'une partie touchée
local function getPlayerFromTouchedPart(hit)
local character = hit.Parent
if not character then return nil end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid then return nil end
return Players:GetPlayerFromCharacter(character)
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiElements(player)
local playerGui = player:WaitForChild("PlayerGui")
local screenGui = playerGui:WaitForChild("ScreenGui")
local vectorSpaceFrame = screenGui:WaitForChild("VectorSpace")
return vectorSpaceFrame
end
-- Cache l'interface pour tous les joueurs
local function setInterfaceVisibility(player, etat)
if not player then return end
local vectorSpaceFrame = getPlayerGuiElements(player)
vectorSpaceFrame.Visible = etat
return vectorSpaceFrame
end
-- Ouvre la porte
local function openWall()
if wall then
wall:Destroy()
end
if movablePart then
movablePart:Destroy()
movablePart = nil
end
print("La porte s'ouvre !")
-- Cache l'interface pour tous les joueurs
for player, _ in pairs(playerEntryTimestamps) do
setInterfaceVisibility(player, false)
end
end
-- Vérifie si le bloc mobile touche une clé, et déclenche la logique de collecte
local function checkMovablePartCollisions()
if not movablePart then return end
local touchingParts = Workspace:GetPartsInPart(movablePart, overlapParams)
for _, touchingPart in ipairs(touchingParts) do
-- On ne réagit qu'à la clé actuellement attendue (la première de la liste)
if touchingPart.Name == "Key" and touchingPart.Color == KEY_COLORS[1] then
touchingPart:Destroy() -- Supprime la clé
table.remove(KEY_COLORS, 1) -- Retire la couleur de la liste
if #KEY_COLORS > 0 then
-- Il reste des clés à collecter : on indique la prochaine couleur attendue
movablePart.Color = KEY_COLORS[1]
else
-- Toutes les clés sont collectées : victoire !
openWall()
end
break -- On sort de la boucle car on a trouvé une clé
end
end
end
-- Déplace la Part si le raycast ne détecte aucun obstacle sur le trajet
local function moveMovablePart(movablePart, axisVector, directionSign)
if not movablePart then return end
local moveOffset = axisVector * directionSign
local rayOrigin = movablePart.Position + ((movablePart.Size / 2) * directionSign)
local result = workspace:Raycast(rayOrigin, moveOffset, raycastParams)
if result then return end -- Un obstacle bloque le trajet
movablePart.Position = movablePart.Position + moveOffset
checkMovablePartCollisions()
end
-- Nettoie les connexions de boutons d'un joueur
local function disconnectPlayerButtons(player)
local connections = playerButtonConnections[player]
if not connections then return end
for _, connection in ipairs(connections) do
connection:Disconnect()
end
playerButtonConnections[player] = nil
end
--- Récupère les GUI nécessaires au déplacement pour un joueur donné.
local function getPlayerGuiButtons(vectorSpaceFrame)
local upButton = vectorSpaceFrame:WaitForChild("UpButton")
local downButton = vectorSpaceFrame:WaitForChild("DownButton")
local leftButton = vectorSpaceFrame:WaitForChild("LeftButton")
local rightButton = vectorSpaceFrame:WaitForChild("RightButton")
local backButton = vectorSpaceFrame:WaitForChild("BackButton")
local frontButton = vectorSpaceFrame:WaitForChild("FrontButton")
return upButton, downButton, leftButton, rightButton, backButton, frontButton
end
-- Met en place les contrôles de déplacement pour un joueur
local function setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
if not player then return end
if not vectorSpaceFrame then return end
-- On récupère les boutons du joueur
local upButton, downButton, leftButton, rightButton, backButton, frontButton = getPlayerGuiButtons(vectorSpaceFrame)
-- On nettoie d'éventuelles anciennes connexions avant d'en recréer
disconnectPlayerButtons(player)
local connections = {}
table.insert(connections, downButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), -1)
end))
table.insert(connections, upButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, MOVE_STEP, 0), 1)
end))
table.insert(connections, leftButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), -1)
end))
table.insert(connections, rightButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(0, 0, MOVE_STEP), 1)
end))
table.insert(connections, backButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), 1)
end))
table.insert(connections, frontButton.MouseButton1Click:Connect(function()
moveMovablePart(movablePart, Vector3.new(MOVE_STEP, 0, 0), -1)
end))
playerButtonConnections[player] = connections
end
-- ==================== ÉVÉNEMENTS DE COLLISION ====================
hitbox.Touched:Connect(function(hit)
local player = getPlayerFromTouchedPart(hit)
if not player then return end
-- Anti-spam : ignore si le joueur est déjà dans la zone
if playerEntryTimestamps[player] then return end
playerEntryTimestamps[player] = tick()
local vectorSpaceFrame = setInterfaceVisibility(player, true)
setupPlayerMovementControls(player, movablePart, vectorSpaceFrame)
end)
hitbox.TouchEnded:Connect(function(hit)
local player = getPlayerFromTouchedPart(hit)
if not player then return end
if not playerEntryTimestamps[player] then return end
-- Attend un délai pour éviter les faux positifs (ex: si le joueur touche encore la hitbox avec une autre partie du corps)
task.wait(EXIT_CONFIRM_DELAY)
-- Vérifie qu'aucune partie du personnage ne touche encore la hitbox
local character = player.Character
local stillTouching = false
if character then
for _, part in pairs(hitbox:GetTouchingParts()) do
if part.Parent == character then
stillTouching = true
break
end
end
end
if stillTouching then return end
if not playerEntryTimestamps[player] then return end
-- Le joueur a vraiment quitté la zone
playerEntryTimestamps[player] = nil
setInterfaceVisibility(player, false)
disconnectPlayerButtons(player)
end)
-- Interface invisible au lancement du joueur
Players.PlayerAdded:Connect(function(player)
-- Aucune action nécessaire pour les joueurs déjà connectés
setInterfaceVisibility(player, false)
end)
-- Nettoyage quand un joueur quitte
Players.PlayerRemoving:Connect(function(player)
playerEntryTimestamps[player] = nil
disconnectPlayerButtons(player)
setInterfaceVisibility(player, false)
end)
Explications des optimisations
Optimisation
Explication
playerButtonConnections[player] = nil
Nettoie les connexions pour éviter les fuites mémoire.
if not movablePart then return end
Évite les erreurs si le bloc a été détruit.
setInterfaceVisibility(player, false)
Optimise la visibilité de l’écran du joueur
openWall() cache l’interface pour tous les joueurs
Améliore l’expérience utilisateur.
À toi de jouer !
Copie le script final dans VectorSpaceScript.
Teste le jeu complet :
Entre dans la hitbox.
Déplace le bloc pour toucher les clés dans l’ordre.
Vérifie que la porte s’ouvre et que l’interface disparaît.
Modifie :
Ajoute une 4ème clé avec une nouvelle couleur (ex: jaune).
Change MOVE_STEP à 0.5 pour des déplacements plus précis.
É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 !
Crée un dossierBreakPyramid dans Workspace.
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).
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
Ligne
Explication
for i = 1, PYRAMID_PARTS_COUNT do
Boucle 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 = true
Empêche la brique de bouger pendant la construction.
task.wait(0.1)
Attend 0.1 seconde pour que tout soit bien positionné.
part.Anchored = false
Permet à la brique de bouger (physique activée).
À toi de jouer !
Copie le code dans un Script dans ServerScriptService.
Teste :
Lance le jeu : une pyramide de 8 briques doit apparaître.
Observe les tailles et les rotations aléatoires.
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.
É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
Ligne
Explication
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 !
Copie le code dans le Script existant.
Teste :
Entre dans la hitbox avec ton personnage.
Ouvre la fenêtre de sortie (Affichage > Sortie) pour voir les messages.
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).
É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
É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 :
Cloner la balle depuis ReplicatedStorage.
Positionner la balle devant le joueur.
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
Ligne
Explication
ballTemplate.Parent = ServerStorage
Dé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 !
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
Copie le code dans le Script.
Teste :
Entre dans la hitbox.
Appuie sur E (ou la touche définie pour le ProximityPrompt) : une balle doit être lancée !
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.
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 :
Compter le nombre de briques encore debout dans la zone de détection (ControlPyramid).
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
Ligne
Explication
workspace:GetPartsInPart(pyramidDetector)
Récupère toutes les parties qui touchent ControlPyramid.
for _, part in ipairs(touchingParts) do
Parcourt toutes les parties en contact avec ControlPyramid.
if part.Name == "pyramid" then
Vé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 !
Copie le code dans le Script.
Teste :
Entre dans la hitbox et lance des balles jusqu’à détruire la pyramide.
Observe le recul du joueur quand il réussit.
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.
Étape 3 : Démarrer le script avec une gestion de hitbox
But : Détecter quand un joueur entre dans la zone de lancer. Concepts clés : Événements, fonctions, détection de collision.
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.
Exemple :
-- ==================== RÉFÉRENCES SCÈNE ====================
local basketFolder = workspace:WaitForChild("Basket")
local hitbox = basketFolder:WaitForChild("HitBox")
hitbox.Touched:Connect(function(hit)
print("Quelque chose a touché la hitbox !")
end)
Touched : Événement déclenché quand un objet entre en contact avec la hitbox.
hit : L’objet qui a touché la hitbox (ici, une partie du personnage du joueur).
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
-- ==================== RÉFÉRENCES SCÈNE ====================
local basketFolder = workspace:WaitForChild("Basket")
local hitbox = basketFolder:WaitForChild("HitBox")
-- ==================== ÉVÉNEMENTS : ZONE DE JEU
-- Quand un joueur touche la hitbox
hitbox.Touched:Connect(function(hit)
-- Vérifie si l'objet qui a touché est un personnage de joueur
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
print(player.Name .. " est entré dans la zone de lancer !")
end
end
end)
Explications du code
Ligne
Explication
local Players = game:GetService("Players")
Récupère le service Players pour accéder aux joueurs.
hitbox.Touched:Connect(...)
Écoute l’événement Touched sur la hitbox.
hit.Parent
Récupère le parent de l’objet qui a touché (ici, le personnage du joueur).
FindFirstChildOfClass("Humanoid")
Vérifie si le parent a un Humanoid (donc c’est un personnage joueur).
Players:GetPlayerFromCharacter(character)
Récupère le joueur associé au personnage.
Copie ce code dans un Script (dans ServerScriptService).
Teste :
Entre dans la hitbox avec ton personnage.
Ouvre la fenêtre de sortie (Affichage > Sortie) pour voir le message.
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.
Que fait Players:GetPlayerFromCharacter(character) ?
a) Crée un nouveau personnage.
b) Récupère le joueur associé à un personnage.
c) Supprime le personnage.
Étape 4 : Changer la vision du joueur en première personne
But : Passer en vue première personne quand le joueur entre dans la zone. Concepts clés : Propriétés des objets, fonctions utilitaires.
Explications
En Roblox, on peut modifier la caméra du joueur pour :
LockFirstPerson : Vue à la première personne (comme dans un FPS).
-- 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
player.CameraMinZoomDistance = 10
end
Puis modifie l’événement Touched :
hitbox.Touched:Connect(function(hit)
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
print(player.Name .. " est entré dans la zone de lancer !")
lockFirstPersonCamera(player) -- Passe en première personne
end
end
end)
Entre dans la hitbox : la vue doit passer en première personne.
Modifie :
Change CameraMinZoomDistance à 1.0 et observe 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. c) Désactive la caméra.
Rajoute dans ton code pour modifier la caméra en FirstPerson lorsque le joueur rentre dans la zone de tir, puis modifie la caméra en normal lorsque le joueur quitte la zone de tir :
-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5 -- Délai avant de confirmer la sortie de zone (anti faux-positif)
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
-- ==================== RÉFÉRENCES SCÈNE ====================
local basketFolder = workspace:WaitForChild("Basket")
local hitbox = basketFolder:WaitForChild("HitBox")
-- ==================== ÉTAT DU JEU ====================
-- playersData[player] = { inZone = bool, nbrPoints = number }
local playersData = {}
-- 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
player.CameraMinZoomDistance = 10
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
resetCamera(player)
end
-- ==================== ÉVÉNEMENTS : ZONE DE JEU
-- Quand un joueur touche la hitbox
hitbox.Touched:Connect(function(hit)
-- Vérifie si l'objet qui a touché est un personnage de joueur
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
-- Anti-spam : on ignore si le joueur est déjà comptabilisé dans la zone
local data = playersData[player]
if data and data.inZone then return end
playersData[player] = { inZone = true, fire = 0 }
print(player.Name .. " est entré dans la zone de lancer !")
lockFirstPersonCamera(player) -- Passe en première personne
end
end
end)
-- Quand une partie du personnage quitte la hitbox, on vérifie si c'est un joueur
hitbox.TouchEnded:Connect(function(hit)
-- Vérifie si l'objet qui a touché est un personnage de joueur
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
task.wait(EXIT_CONFIRM_DELAY)
-- On vérifie qu'aucune partie du personnage ne touche encore la hitbox
-- (utile car Touched/TouchEnded se déclenchent par membre du corps)
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
exitZone(player)
end
end
end)
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.
Étape 5 : Écrire une interface utilisateur pour indiquer d’utiliser la touche F
But : Afficher un message à l’écran pour guider le joueur. Concepts clés : LocalScript, ** interfaces utilisateur (GUI)**, TextLabel.
Explications
Pour afficher un message à l’écran, on utilise :
Un ScreenGui (interface à l’écran).
Un TextLabel (texte affiché).
Un LocalScript (script côté client, car l’interface est visible uniquement par le joueur).
Exemple :
local player = game.Players.LocalPlayer
local gui = Instance.new("ScreenGui")
local label = Instance.new("TextLabel")
label.Text = "Appuie sur F pour lancer !"
label.Parent = gui
gui.Parent = player:WaitForChild("PlayerGui")
Code Lua
Crée un LocalScript dans StarterPlayerScripts.
Copie ce code :
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- ==================== RÉFÉRENCES ====================
local player = Players.LocalPlayer
local readyEvent = ReplicatedStorage:WaitForChild("ReadyEvent")
-- ==================== INTERFACE ====================
local gui = Instance.new("ScreenGui")
local label = Instance.new("TextLabel")
-- Configure le TextLabel
label.Text = "Appuie sur F pour lancer !"
label.Size = UDim2.new(0, 200, 0, 50) -- Largeur: 200, Hauteur: 50
label.Position = UDim2.new(0.5, -100, 0.9, -25) -- Centre en bas
label.BackgroundColor3 = Color3.fromRGB(0, 0, 0) -- Fond noir
label.TextColor3 = Color3.fromRGB(255, 255, 255) -- Texte blanc
label.TextScaled = true -- Le texte s'adapte à la taille
label.Parent = gui
gui.Parent = player:WaitForChild("PlayerGui")
-- Désactive l'affichage au début
gui.Enabled = false
-- Écoute l'événement ReadyEvent pour activer/désactiver l'interface
readyEvent.OnClientEvent:Connect(function(isReady)
gui.Enabled = isReady
end)
Modifie le script sur le serveur pour envoyer l’évènement readyevent avec l’instruction readyEvent:FireClient(player, true) :
-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5 -- Délai avant de confirmer la sortie de zone (anti faux-positif)
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local fireInputEvent = ReplicatedStorage:WaitForChild("FireInputEvent")
local readyEvent = ReplicatedStorage:WaitForChild("ReadyEvent")
-- ==================== RÉFÉRENCES SCÈNE ====================
local basketFolder = workspace:WaitForChild("Basket")
local hitbox = basketFolder:WaitForChild("HitBox")
-- ==================== ÉTAT DU JEU ====================
-- playersData[player] = { inZone = bool, nbrPoints = number }
local playersData = {}
-- 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
player.CameraMinZoomDistance = 10
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
resetCamera(player)
end
-- ==================== ÉVÉNEMENTS : ZONE DE JEU
-- Quand un joueur touche la hitbox
hitbox.Touched:Connect(function(hit)
-- Vérifie si l'objet qui a touché est un personnage de joueur
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
-- Anti-spam : on ignore si le joueur est déjà comptabilisé dans la zone
local data = playersData[player]
if data and data.inZone then return end
playersData[player] = { inZone = true, fire = 0 }
print(player.Name .. " est entré dans la zone de lancer !")
lockFirstPersonCamera(player) -- Passe en première personne
-- Envoie d'un évènement au client pour lui dire qu'il peut lancer le ballon
readyEvent:FireClient(player, true) -- prêt à lancer le ballon
end
end
end)
-- Quand une partie du personnage quitte la hitbox, on vérifie si c'est un joueur
hitbox.TouchEnded:Connect(function(hit)
-- Vérifie si l'objet qui a touché est un personnage de joueur
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
task.wait(EXIT_CONFIRM_DELAY)
-- On vérifie qu'aucune partie du personnage ne touche encore la hitbox
-- (utile car Touched/TouchEnded se déclenchent par membre du corps)
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
exitZone(player)
-- Envoie d'un évènement au client pour lui dire qu'il ne peut plus lancer le ballon
readyEvent:FireClient(player, false)
end
end
end)
Explications du code
Ligne
Explication
local player = Players.LocalPlayer
Récupère le joueur local (celui qui exécute le script).
Instance.new("ScreenGui")
Crée une interface à l’écran.
label.Text = "Appuie sur F pour lancer !"
Définit le texte affiché.
readyEvent.OnClientEvent:Connect(...)
Écoute un événement distant pour activer/désactiver l’interface.
À toi de jouer !
Crée un RemoteEvent nommé ReadyEvent dans ReplicatedStorage.
Copie le code du LocalScript dans StarterPlayerScripts.
Teste :
Entre dans la hitbox : le message doit s’afficher.
Modifie :
Change le texte en "Prêt ? Appuie sur F !".
Change la couleur du fond en rouge (Color3.fromRGB(255, 0, 0)).
QCM / Questions
À quoi sert un LocalScript ?
a) À exécuter du code côté serveur.
b) À créer des objets dans le jeu.
c) À exécuter du code côté client (pour un joueur spécifique).
Que fait gui.Enabled = false ?
a) Supprime l’interface.
b) Désactive l’interface (elle n’est plus visible).
Étape 6 : Coder un LocalScript pour détecter l’appui sur la touche F
But : Détecter quand le joueur appuie sur F et envoyer un signal au serveur. Concepts clés : InputBegan, RemoteEvent, UserInputService.
Explications
Pour détecter une touche, on utilise :
UserInputService : Service qui gère les entrées clavier/souris.
InputBegan : Événement déclenché quand une touche est pressée.
RemoteEvent : Pour envoyer un signal du client au serveur.
Exemple :
luaCopier
local UserInputService = game:GetService("UserInputService")
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if input.KeyCode == Enum.KeyCode.F then
print("F a été pressée !")
end
end)
Code Lua
Ajoute ce code au LocalScript de l’étape 5 :
luaCopier
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- ==================== RÉFÉRENCES ====================
local player = Players.LocalPlayer
local readyEvent = ReplicatedStorage:WaitForChild("ReadyEvent")
local fireInputEvent = ReplicatedStorage:WaitForChild("FireInputEvent")
-- ==================== INTERFACE ====================
local gui = Instance.new("ScreenGui")
local label = Instance.new("TextLabel")
-- Configure le TextLabel
label.Text = "Appuie sur F pour lancer !"
label.Size = UDim2.new(0, 200, 0, 50) -- Largeur: 200, Hauteur: 50
label.Position = UDim2.new(0.5, -100, 0.9, -25) -- Centre en bas
label.BackgroundColor3 = Color3.fromRGB(0, 0, 0) -- Fond noir
label.TextColor3 = Color3.fromRGB(255, 255, 255) -- Texte blanc
label.TextScaled = true -- Le texte s'adapte à la taille
label.Parent = gui
gui.Parent = player:WaitForChild("PlayerGui")
-- Désactive l'affichage au début
gui.Enabled = false
-- Écoute l'événement ReadyEvent pour activer/désactiver l'interface
readyEvent.OnClientEvent:Connect(function(isReady)
gui.Enabled = isReady
end)
-- ==================== DÉTECTION TOUCHE F ====================
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if input.KeyCode == Enum.KeyCode.F then
-- Envoie un événement au serveur avec la direction du regard du joueur
local character = player.Character or player.CharacterAdded:Wait()
local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
local lookDirection = humanoidRootPart.CFrame.LookVector
fireInputEvent:FireServer(lookDirection)
end
end)
Puis modifie le script serveur pour recevoir l’évènement de la demande de TIR :
Rajoute EventRemote : fireInputEvent :
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local fireInputEvent = ReplicatedStorage:WaitForChild("FireInputEvent")
local readyEvent = ReplicatedStorage:WaitForChild("ReadyEvent")
Puis la réception de l’évènement dans hitbox.Touched :
-- ==================== ÉVÉNEMENTS : ZONE DE JEU
-- Quand un joueur touche la hitbox
hitbox.Touched:Connect(function(hit)
-- Vérifie si l'objet qui a touché est un personnage de joueur
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
-- Anti-spam : on ignore si le joueur est déjà comptabilisé dans la zone
local data = playersData[player]
if data and data.inZone then return end
playersData[player] = { inZone = true, fire = 0 }
print(player.Name .. " est entré dans la zone de lancer !")
lockFirstPersonCamera(player) -- Passe en première personne
-- Envoie d'un évènement au client pour lui dire qu'il peut lancer le ballon
readyEvent:FireClient(player, true) -- prêt à lancer le ballon
-- Réception véènment pour le lancer du ballon
fireInputEvent.OnServerEvent:Connect(function(player, lookDirection)
print("Lancement du ballon par " .. player.Name)
end)
end
end
end)
Explications du code
Ligne
Explication
UserInputService.InputBegan:Connect(...)
Écoute quand une touche est pressée.
input.KeyCode == Enum.KeyCode.F
Vérifie si la touche F a été pressée.
humanoidRootPart.CFrame.LookVector
Récupère la direction dans laquelle le joueur regarde.
fireInputEvent:FireServer(lookDirection)
Envoie la direction au serveur via un RemoteEvent.
À toi de jouer !
Crée un RemoteEvent nommé FireInputEvent dans ReplicatedStorage.
Copie le code dans le LocalScript.
Teste :
Entre dans la hitbox.
Appuie sur F : rien ne se passe encore (le serveur ne gère pas encore l’événement).
Modifie :
Change la touche en E (Enum.KeyCode.E).
QCM / Questions
À quoi sert UserInputService.InputBegan ?
a) À détecter quand une touche est relâchée.
b) À détecter quand une touche est pressée.
c) À changer la touche par défaut.
Que fait fireInputEvent:FireServer(lookDirection) ?
a) Affiche un message à l’écran.
b) Envoie la direction du regard au serveur.
c) Détruit le ballon.
Pour aller plus loin
Ajoute un cooldown (délai entre deux lancers) pour éviter le spam.
Étape 7 : Gérer la réception du RemoteEvent et lancer la balle
But : Recevoir le signal du client et lancer une balle. Concepts clés : RemoteEvent.OnServerEvent, clonage d’objets, physique.
Explications
Quand le serveur reçoit un RemoteEvent, il peut :
Vérifier que le joueur a le droit de lancer.
Cloner la balle depuis ReplicatedStorage.
Appliquer une force pour la lancer.
Modifie le Script (côté serveur) :
-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5 -- Délai avant de confirmer la sortie de zone (anti faux-positif)
local PUSH_FORCE = 150 -- Force de lancement en fonction de l'angle
local BALL_LIFETIME = 2 -- Durée de vie de la balle avant destruction (secondes)
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local fireInputEvent = ReplicatedStorage:WaitForChild("FireInputEvent")
local readyEvent = ReplicatedStorage:WaitForChild("ReadyEvent")
local ServerStorage = game:GetService("ServerStorage")
local Debris = game:GetService("Debris")
-- ==================== RÉFÉRENCES SCÈNE ====================
local basketFolder = workspace:WaitForChild("Basket")
local hitbox = basketFolder:WaitForChild("HitBox")
local ballTemplate = basketFolder:WaitForChild("Ball")
ballTemplate.Parent = ServerStorage -- Déplace le modèle dans ServerStorage
-- ==================== ÉTAT DU JEU ====================
-- playersData[player] = { inZone = bool, nbrPoints = number }
local playersData = {}
-- 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
player.CameraMinZoomDistance = 10
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
resetCamera(player)
end
-- Fait apparaître une balle devant le joueur et lui appliquer une impulsion
local function fireBall(player, force)
local character = player.Character or player.CharacterAdded:Wait()
local hrp = character:WaitForChild("HumanoidRootPart")
local ball = ballTemplate:Clone()
ball.CustomPhysicalProperties = PhysicalProperties.new(
1, -- Density
0, -- Friction
0.1, -- Elasticity
0, -- FrictionWeight
0.1 -- ElasticityWeight
)
ball.Parent = workspace
ball.CFrame = hrp.CFrame * CFrame.new(-2, 2, 0) -- Position devant le joueur
ball.Name = player.Name
-- Applique une vitesse initiale vers l'avant + vers le haut
ball.AssemblyLinearVelocity = hrp.CFrame.LookVector * (force - math.random(38, 42)) + Vector3.new(0, force + math.random(28, 32), 0)
-- Détruit la balle après 2 secondes
Debris:AddItem(ball, BALL_LIFETIME)
end
local function throwBall(player, lookDirection)
local data = playersData[player]
if tick() - data.fire < 2 then return end -- Cooldown de 2 secondes
readyEvent:FireClient(player, false)
data.fire = tick()
local force = ((lookDirection.Y + 1) / 2) * PUSH_FORCE -- Force de lancement en fonction de l'angle
fireBall(player, force)
task.wait(2)
readyEvent:FireClient(player, true)
end
-- ==================== ÉVÉNEMENTS : ZONE DE JEU
-- Quand un joueur touche la hitbox
hitbox.Touched:Connect(function(hit)
-- Vérifie si l'objet qui a touché est un personnage de joueur
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
-- Anti-spam : on ignore si le joueur est déjà comptabilisé dans la zone
local data = playersData[player]
if data and data.inZone then return end
playersData[player] = { inZone = true, fire = 0 }
print(player.Name .. " est entré dans la zone de lancer !")
lockFirstPersonCamera(player) -- Passe en première personne
-- Envoie d'un évènement au client pour lui dire qu'il peut lancer le ballon
readyEvent:FireClient(player, true) -- prêt à lancer le ballon
-- Réception véènment pour le lancer du ballon
fireInputEvent.OnServerEvent:Connect(function(player, lookDirection)
throwBall(player, lookDirection)
end)
end
end
end)
-- Quand une partie du personnage quitte la hitbox, on vérifie si c'est un joueur
hitbox.TouchEnded:Connect(function(hit)
-- Vérifie si l'objet qui a touché est un personnage de joueur
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
task.wait(EXIT_CONFIRM_DELAY)
-- On vérifie qu'aucune partie du personnage ne touche encore la hitbox
-- (utile car Touched/TouchEnded se déclenchent par membre du corps)
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
exitZone(player)
-- Envoie d'un évènement au client pour lui dire qu'il ne peut plus lancer le ballon
readyEvent:FireClient(player, false)
end
end
end)
Explications du code
Ligne
Explication
fireInputEvent.OnServerEvent:Connect(...)
Écoute l’événement envoyé par le client.
ballTemplate:Clone()
Crée une copie de la balle.
ball.CFrame = hrp.CFrame * CFrame.new(-2, 2, 0)
Positionne la balle devant le joueur.
ball.AssemblyLinearVelocity = ...
Applique une vitesse à la balle (physique).
Debris:AddItem(ball, 2)
Détruit la balle après 2 secondes.
tick() - data.fire < 2
Vérifie si 2 secondes se sont écoulées depuis le dernier lancer.
À toi de jouer !
Copie le code dans le Script côté serveur.
Teste :
Entre dans la hitbox.
Appuie sur F : une balle doit être lancée !
Modifie :
Change BALL_LIFETIME à 5 pour que la balle reste plus longtemps.
Change la force du lancer en modifiant local PUSH_FORCE = 150 — Force de lancement en fonction de l’angle force = ((lookDirection.Y + 1) / 2) * PUSH_FORCE .
QCM / Questions
À quoi sert ballTemplate:Clone() ?
a) À supprimer la balle.
b) À créer une copie de la balle.
c) À changer la couleur de la balle.
Que fait Debris:AddItem(ball, 2) ?
a) Détruit la balle après 2 secondes.
b) Affiche la balle pendant 2 secondes.
c) Fait rebondir la balle 2 fois.
Pour aller plus loin
Ajoute un effet de particule quand la balle est lancée.
Étape 8 : Détecter quand la balle touche le panier
But : Marquer des points quand la balle entre dans le panier. Concepts clés : Détection de collision, gestion de score, sons.
Pour détecter quand la balle touche le panier :
Ajoute une Part (nommée ControlBall) dans le panier.
Utilise .Touched pour détecter la collision.
Ajoute des points au joueur si la balle est la sienne.
Exemple :
ballDetector.Touched:Connect(function(hit)
local player = Players:FindFirstChild(hit.Name)
if player then
ScoreManager.addPoints(player, 3) -- Ajoute 3 points
end
end)
Voici le script final avec toutes les fonctionnalités intégrées :
-- ==================== CONFIGURATION ====================
local EXIT_CONFIRM_DELAY = 0.5 -- Délai avant de confirmer la sortie de zone (anti faux-positif)
local PUSH_FORCE = 150 -- Force de lancement en fonction de l'angle
local BALL_LIFETIME = 2 -- Durée de vie de la balle avant destruction (secondes)
-- ==================== SERVICES ====================
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local fireInputEvent = ReplicatedStorage:WaitForChild("FireInputEvent")
local readyEvent = ReplicatedStorage:WaitForChild("ReadyEvent")
local ServerStorage = game:GetService("ServerStorage")
local Debris = game:GetService("Debris")
-- ==================== RÉFÉRENCES SCÈNE ====================
local basketFolder = workspace:WaitForChild("Basket")
local hitbox = basketFolder:WaitForChild("HitBox")
local ballTemplate = basketFolder:WaitForChild("Ball")
local ballDetector = basketFolder:WaitForChild("ControlBall") -- zone qui détecte les balles dans le panier
ballTemplate.Parent = ServerStorage -- Déplace le modèle dans ServerStorage
local clapSound = basketFolder:WaitForChild("claps")
-- ==================== ÉTAT DU JEU ====================
-- playersData[player] = { inZone = bool, nbrPoints = number }
local playersData = {}
-- Joue un son
local function playSound(sound)
if not sound then return end
pcall(function() sound:Play() end)
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
player.CameraMinZoomDistance = 10
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
resetCamera(player)
end
-- Fait apparaître une balle devant le joueur et lui appliquer une impulsion
local function fireBall(player, force)
local character = player.Character or player.CharacterAdded:Wait()
local hrp = character:WaitForChild("HumanoidRootPart")
local ball = ballTemplate:Clone()
ball.CustomPhysicalProperties = PhysicalProperties.new(
1, -- Density
0, -- Friction
0.1, -- Elasticity
0, -- FrictionWeight
0.1 -- ElasticityWeight
)
ball.Parent = workspace
ball.CFrame = hrp.CFrame * CFrame.new(-2, 2, 0) -- Position devant le joueur
ball.Name = player.Name
-- Applique une vitesse initiale vers l'avant + vers le haut
ball.AssemblyLinearVelocity = hrp.CFrame.LookVector * (force - math.random(38, 42)) + Vector3.new(0, force + math.random(28, 32), 0)
-- Détruit la balle après 2 secondes
Debris:AddItem(ball, BALL_LIFETIME)
end
local function throwBall(player, lookDirection)
local data = playersData[player]
if tick() - data.fire < 2 then return end -- Cooldown de 2 secondes
readyEvent:FireClient(player, false)
data.fire = tick()
local force = ((lookDirection.Y + 1) / 2) * PUSH_FORCE -- Force de lancement en fonction de l'angle
fireBall(player, force)
task.wait(2)
readyEvent:FireClient(player, true)
end
-- ==================== ÉVÉNEMENTS : ZONE DE JEU
-- Quand un joueur touche la hitbox
hitbox.Touched:Connect(function(hit)
-- Vérifie si l'objet qui a touché est un personnage de joueur
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
-- Anti-spam : on ignore si le joueur est déjà comptabilisé dans la zone
local data = playersData[player]
if data and data.inZone then return end
playersData[player] = { inZone = true, fire = 0 }
print(player.Name .. " est entré dans la zone de lancer !")
lockFirstPersonCamera(player) -- Passe en première personne
-- Envoie d'un évènement au client pour lui dire qu'il peut lancer le ballon
readyEvent:FireClient(player, true) -- prêt à lancer le ballon
-- Réception véènment pour le lancer du ballon
fireInputEvent.OnServerEvent:Connect(function(player, lookDirection)
print("Lancement du ballon par " .. player.Name)
throwBall(player, lookDirection)
end)
end
end
end)
-- Quand une partie du personnage quitte la hitbox, on vérifie si c'est un joueur
hitbox.TouchEnded:Connect(function(hit)
-- Vérifie si l'objet qui a touché est un personnage de joueur
local character = hit.Parent
local humanoid = character:FindFirstChildOfClass("Humanoid")
if humanoid then
local player = Players:GetPlayerFromCharacter(character)
if player then
task.wait(EXIT_CONFIRM_DELAY)
-- On vérifie qu'aucune partie du personnage ne touche encore la hitbox
-- (utile car Touched/TouchEnded se déclenchent par membre du corps)
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
exitZone(player)
-- Envoie d'un évènement au client pour lui dire qu'il ne peut plus lancer le ballon
readyEvent:FireClient(player, false)
end
end
end)
-- Détection du ballon dans le panier
ballDetector.Touched:Connect(function(hit)
local player = Players:FindFirstChild(hit.Name)
if player then
playSound(clapSound)
print(player.Name .. " a marqué 3 points !")
end
end)
-- Nettoyage quand un joueur quitte
Players.PlayerRemoving:Connect(function(player)
playersData[player] = nil
end)
Explications du code
Ligne
Explication
ballDetector.Touched:Connect(...)
Écoute quand un objet touche le détecteur du panier.
Players:FindFirstChild(hit.Name)
Vérifie si l’objet qui a touché est une balle lancée par un joueur.
playSound(clapSound)
Joue un son de applaudissements.
print(player.Name .. " a marqué 3 points !")
Affiche un message dans la sortie.
À toi de jouer !
Ajoute une Part nommée ControlBall dans le panier (dans Basket).
Ajoute un son nommé claps dans Basket (tu peux importer un son depuis Roblox Library).
Copie le code dans le Script.
Teste :
Lance une balle dans le panier : tu seras applaudi.
QCM / Questions
À quoi sert ballDetector.Touched:Connect(...) ?
a) À détruire le panier.
b) À changer la couleur du panier.
c) À détecter quand une balle touche le panier.
Que fait Players:FindFirstChild(hit.Name) ?
a) Crée un nouveau joueur.
b) Trouve un joueur dont le nom correspond à celui de l’objet qui a touché.
c) Supprime le joueur.
Pour aller plus loin
Ajoute un système de score avec un Leaderboard (tableau des scores).