Catégories
Jeu vidéo ROBLOX

Jouer au basket

Roblox Studio est un outil qui permet de créer des jeux vidéo en utilisant le langage Lua. Aujourd’hui, nous allons créer un mini-jeu de basket où :

  • Le joueur entre dans une zone de lancer.
  • Il appuie sur la touche F pour lancer un ballon.
  • Si le ballon entre dans le panier, il marque des points.

Avant de coder, il faut imaginer le jeu :

  • Le joueur entre dans une zone de lancer (une hitbox).
  • En appuyant sur F, il lance une balle.
  • Si la balle touche le panier, il gagne des points.

Objets nécessaires :

  • Une hitbox (zone invisible pour détecter le joueur).
  • Un ballon (modèle 3D).
  • Un panier (pour marquer des points).
  • Un script (pour gérer les règles du jeu).

À toi de jouer !

  1. Dans Roblox Studio, ajoute :
    • Une Part (pour la hitbox) dans Workspace.
    • Un Ball (modèle de balle) dans ReplicatedStorage.
    • Un Basket (panier) dans Workspace.
  2. Teste : Déplace ton personnage dans la hitbox et imagine le gameplay.

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

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

Explications

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

  • Workspace : Contient tous les objets visibles dans le jeu (balles, paniers, etc.).
  • ServerScriptService : Contient les scripts côté serveur (pour la logique du jeu).
  • ReplicatedStorage : Contient les modèles réutilisables (comme la balle).
  • StarterGui : Contient les interfaces utilisateur (pour afficher des messages).

Exemple :

textCopier

Workspace
├── Basket (Folder)
│   ├── HitBox (Part)
│   ├── Ball (Model)
│   └── ControlBall (Part)
ReplicatedStorage
└── FireInputEvent (RemoteEvent)

É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

LigneExplication
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.ParentRé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).
  • Classic : Vue classique (derrière le personnage).

Exemple :

player.CameraMode = Enum.CameraMode.LockFirstPerson

Code Lua

Ajoute ces fonctions au script précédent :

luaCopier

-- 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)

Explications du code

LigneExplication
player.CameraMode = Enum.CameraMode.LockFirstPersonForce la vue à la première personne.
player.CameraMinZoomDistance = 0.5Définit la distance minimale de zoom.
task.wait(0.1)Attend 0.1 seconde pour éviter les bugs.

À toi de jouer !

  1. Copie le code dans ton script.
  2. Teste :
    • Entre dans la hitbox : la vue doit passer en première personne.
  3. 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.

Documentation Roblox Lua :


É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

  1. Crée un LocalScript dans StarterPlayerScripts.
  2. 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

LigneExplication
local player = Players.LocalPlayerRé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 !

  1. Crée un RemoteEvent nommé ReadyEvent dans ReplicatedStorage.
  2. Copie le code du LocalScript dans StarterPlayerScripts.
  3. Teste :
    • Entre dans la hitbox : le message doit s’afficher.
  4. 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).
  • c) Change la taille de l’interface.

Documentation Roblox Lua :


É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

LigneExplication
UserInputService.InputBegan:Connect(...)Écoute quand une touche est pressée.
input.KeyCode == Enum.KeyCode.FVérifie si la touche F a été pressée.
humanoidRootPart.CFrame.LookVectorRécupère la direction dans laquelle le joueur regarde.
fireInputEvent:FireServer(lookDirection)Envoie la direction au serveur via un RemoteEvent.

À toi de jouer !

  1. Crée un RemoteEvent nommé FireInputEvent dans ReplicatedStorage.
  2. Copie le code dans le LocalScript.
  3. Teste :
    • Entre dans la hitbox.
    • Appuie sur F : rien ne se passe encore (le serveur ne gère pas encore l’événement).
  4. 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.

Documentation Roblox Lua :


É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 :

  1. Vérifier que le joueur a le droit de lancer.
  2. Cloner la balle depuis ReplicatedStorage.
  3. 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

LigneExplication
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 < 2Vérifie si 2 secondes se sont écoulées depuis le dernier lancer.

À toi de jouer !

  1. Copie le code dans le Script côté serveur.
  2. Teste :
    • Entre dans la hitbox.
    • Appuie sur F : une balle doit être lancée !
  3. 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.

Documentation Roblox Lua :


É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 :

  1. Ajoute une Part (nommée ControlBall) dans le panier.
  2. Utilise .Touched pour détecter la collision.
  3. 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

LigneExplication
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 !

  1. Ajoute une Part nommée ControlBall dans le panier (dans Basket).
  2. Ajoute un son nommé claps dans Basket (tu peux importer un son depuis Roblox Library).
  3. Copie le code dans le Script.
  4. 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).
  • Affiche les points à l’écran avec un TextLabel.

Documentation Roblox Lua :