Catégories
Jeu vidéo ROBLOX

Chemins invisibles

Dans ce tutoriel sur Roblox, tu vas pouvoir apprendre à créer un parcours de plateformes dynamique et dangereux. Le principe est simple : plusieurs chemins sont proposés au joueur pour atteindre la sortie du niveau. Mais attention, les différents chemins ne restent pas visibles éternellement ! Grâce à un script en Lua, certains chemins disparaissent régulièrement au fil du temps. Le joueur doit donc observer, réfléchir rapidement et changer de direction avant de tomber dans le vide.

Création de la structure des différents chemins

Dans un premier temps, créer une structure de différents chemins composés de plateformes.

Créer un Folder principal nommé PathsFinding puis des sous Folder qui représenteront tes différents chemins possibles Path01, Path02 … puis sous les différents chemins créer tes plateformes, puis un Script :

Programmation de l’animation des différents chemins

Saisie ce code dans ton script, ce script en Lua pour Roblox permet de créer plusieurs chemins de plateformes qui apparaissent et disparaissent automatiquement.

Au début, le programme récupère le service TweenService, utilisé pour créer des animations fluides comme les effets de disparition et d’apparition.

Les variables de configuration définissent combien de temps un chemin reste visible, le temps d’attente entre les transitions et la durée de l’animation :

  • local DISPLAY_DURATION = 3 — secondes d’affichage du chemin
  • local WAIT_TIME = 3 — secondes entre chaque transition
  • local FADE_DURATION = 0.8 — secondes pour le fade in/out

Le script récupère ensuite tous les dossiers contenant les chemins et les trie dans l’ordre (path1, path2, etc.).

Chaque plateforme est préparée : elle devient fixe (Anchored = true), invisible (Transparency = 1) et sans collision (CanCollide = false).

Les informations importantes des chemins sont stockées dans des tableaux pour rendre le script plus rapide et éviter des calculs inutiles.
La fonction fadeParts() sert à rendre les plateformes visibles ou invisibles grâce à une animation progressive appelée “fade”.
Au démarrage, seul le premier chemin apparaît et devient solide pour que le joueur puisse marcher dessus.

Ensuite, une boucle infinie fait apparaître le chemin suivant pendant que l’ancien disparaît progressivement.
Grâce à ce système, le joueur doit constamment changer de chemin pour éviter de tomber et réussir à trouver la sortie du niveau dans Roblox Studio.

local TweenService = game:GetService("TweenService")

-- Configuration
local DISPLAY_DURATION = 3    -- secondes d'affichage du chemin
local WAIT_TIME      = 3   -- secondes entre chaque transition
local FADE_DURATION    = 0.8  -- secondes pour le fade in/out
-- Récupérer tous les chemins du dossier
local pathsFinding   = script.Parent
-- trie des chemins par leur nom : path1, path2, path3, ...
local children = pathsFinding:GetChildren()
table.sort(children, function(a, b) return a.Name < b.Name end)
local maxPaths = 0
-- Cache des parts par niveau (évite GetChildren() en boucle)
local pathFinding = {}
-- Cache des données par niveau (évite GetAttribute à chaque frame)
local pathData = {}

-- Prépare les données des différents chemins
for i, path in ipairs(children) do
	if not path:IsA("Folder") then continue end
	local parts = {}
	for _, part in ipairs(path:GetChildren()) do
		if not part:IsA("BasePart") then continue end
		part.Anchored     = true
		part.CanCollide   = false
		part.Transparency = 1
		table.insert(parts, part)
	end
	maxPaths += 1
	pathFinding[maxPaths] = parts
	-- Stocke les attibuts en mémoire
	pathData[maxPaths] = {
		displayDuration 	= path:GetAttribute("DisplayDuration") 	or DISPLAY_DURATION,
		waitTime  		= path:GetAttribute("WaitTime") or WAIT_TIME,
	}
end

-- TweenInfo créé une seule fois
local tweenInfo = TweenInfo.new(FADE_DURATION, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut)

-- Rend visible ou invisible tous les les parts d'un chemin
local function fadeParts(parts, targetTransparency, canCollide)
	for _, part in ipairs(parts) do
		TweenService:Create(part, tweenInfo, {
			Transparency = targetTransparency
		}):Play()
		if canCollide ~= nil then
			part.CanCollide = canCollide
		end
	end
end

-- Affiche le premier chemin au démarrage
fadeParts(pathFinding[1], 0, true)

local currentIndex = 1
while true do
	-- Attendre avant de rendre in le chemin actuel
	task.wait(pathData[currentIndex].displayDuration)
	-- Calcule l'index du prochain chemin (boucle)
	local nextIndex = (currentIndex % maxPaths) + 1
	-- Transition simultanée : fade out ancien + fade in nouveau
	fadeParts(pathFinding[nextIndex],    0, true)	
	-- Attendre avant de rendre invisible le chemin actuel
	task.wait(pathData[currentIndex].waitTime)
	fadeParts(pathFinding[currentIndex], 1, false)
	-- Attendre le temps du fadout avant de continuer
	task.wait(FADE_DURATION)
	-- Mise à jour l'index pour la prochaine itération
	currentIndex = nextIndex

end

Paramétrage de l’animation

Saisir les deux attributs sur chaque Path :

  • DisplayDuration — secondes d’affichage du chemin
  • WaitTime — secondes entre chaque transition

Catégories
Jeu vidéo ROBLOX

Ennemies qui avancent !!

Dans ce tutoriel, tu vas apprendre à programmer des ennemis très simples dans Roblox. Les ennemis sont représentés par des blocs. Les ennemies détectent un joueur proche, avancent vers lui, et lui infligent des dégâts lorsqu’ils le touchent.
Tu vas construire ton script pour gérer plusieurs ennemis qui pourront avoir des comportements différents : distance proche différence, vitesse de déplacement différente, niveau de dommage différent.

Les ennemis s’approchent

Dans un premier temps construis l’arborescence suivante avec un Folder qui va regrouper tous tes ennemis, un script, des Part qui vont représenter tes ennemis :

Renomme les éléments de ton arborescence :

  • Folder : Enemies
  • Script : EnemiesScript
  • Part : Enemy

Puis saisie le code suivant dans ton script pour que tous les blocs ennemis s’approchent du joueur à partir d’une certaine distance entre le joueur et le bloc :

local RunService = game:GetService("RunService")
local Players    = game:GetService("Players")
local Debris            = game:GetService("Debris")

-- Configuration
local DETECTION_DISTANCE = 30   -- distance de détection du joueur (studs)
local SPEED              = 10   -- vitesse de déplacement (studs/s)
local STOP_DISTANCE      = 3    -- distance à laquelle la part s'arrête

local enemies          = script.Parent

-- Cache des données par platform (évite GetAttribute à chaque frame)
local enemiesData = {}

-- Initialisation
for _, enemy in enemies:GetChildren() do

	if not enemy:IsA("BasePart") then continue end

	enemy.Anchored = true

	-- Stocke les données en mémoire
	enemiesData[enemy] = {
		detectionDistance 	= enemy:GetAttribute("DetectionDistance") 	or DETECTION_DISTANCE,
		speed         		= enemy:GetAttribute("Speed")             	or SPEED,
		stopDistance 		= enemy:GetAttribute("StopDistance")      	or STOP_DISTANCE,
	}

end

RunService.Heartbeat:Connect(function(deltaTime)
	-- Récupère le joueur local (ou le premier joueur connecté côté serveur)
	local player = Players:GetPlayers()[1]
	if not player then return end

	local character = player.Character
	if not character then return end
	-- Récupère la position du joueur
	local root = character:FindFirstChild("HumanoidRootPart")
	if not root then return end

	for enemy, data in pairs(enemiesData) do

		-- Vérifie que la plateforme existe encore
		if not enemy or not enemy.Parent then
			enemyData[enemy] = nil
			continue
		end	
		-- Si la part est déjà touchée, on ne fait rien
		if enemy:FindFirstChild("Touched") then return end
		-- Vérifie que le joueur est à portée
		local distance = (root.Position - enemy.Position).Magnitude
		-- Trop loin : la part attend
		if distance > data.detectionDistance then return end
		-- Assez proche : arrêt
		if data.stopDistance > 0 and distance <= data.stopDistance then
			enemy.AssemblyLinearVelocity = Vector3.zero
			return
		end
		-- Calcul de la direction vers le joueur
		local direction = (root.Position - enemy.Position).Unit
		local newPosition = enemy.Position + direction * SPEED * deltaTime
		-- Mise à jour de la force à appliquer en fonction de la vitesse
		enemy.AssemblyLinearVelocity = direction * data.speed
		-- Mise à jour de la position de la part et son orientation	
		enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position) + direction * data.speed * deltaTime
	end		
end)

Lance le jeu : les blocs s’approchent du joueur mais restent à une certaine distance du joueur.

Les ennemis infligent des dommages au joueur

Dans le script suivant les blocs s’approchent à toucher le joueur, et lui infligent des dommages :

local RunService = game:GetService("RunService")
local Players    = game:GetService("Players")
local Debris            = game:GetService("Debris")

-- Configuration
local DETECTION_DISTANCE = 30   -- distance de détection du joueur (studs)
local SPEED              = 10   -- vitesse de déplacement (studs/s)
local STOP_DISTANCE      = 0    -- distance à laquelle la part s'arrête
local DAMAGE             = 5    -- dégâts infligés aux joueurs
local RESET_DELAY        = 0.5  -- délai avant de pouvoir toucher à nouveau (secondes)

local enemies          = script.Parent

-- Cache des données par platform (évite GetAttribute à chaque frame)
local enemiesData = {}

-- Initialisation
for _, enemy in enemies:GetChildren() do

	if not enemy:IsA("BasePart") then continue end
	
	enemy.Anchored = true
	enemy.CanCollide = true

	-- Stocke les données en mémoire
	enemiesData[enemy] = {
		detectionDistance 	= enemy:GetAttribute("DetectionDistance") 	or DETECTION_DISTANCE,
		speed         		= enemy:GetAttribute("Speed")             	or SPEED,
		stopDistance 		= enemy:GetAttribute("StopDistance")      	or STOP_DISTANCE,
		damage				= enemy:GetAttribute("Damage")				or DAMAGE,
		resetDelay			= enemy:GetAttribute("ResetDelay")			or RESET_DELAY,
	}
	
	enemy.Touched:Connect(function(otherPart)
		-- Vérifie que l'objet touché est un personnage
		local character = otherPart.Parent
		if not Players:GetPlayerFromCharacter(character) then return end
		local humanoid = character:FindFirstChildOfClass("Humanoid")
		if not humanoid or humanoid.Health <= 0 then return end
		-- Vérifie que la part n'a pas déjà été touchée
		if enemy:FindFirstChild("Touched") then return end
		-- Applique les dégâts et empêche la répétition
		humanoid:TakeDamage((enemiesData[enemy].damage))
		enemy.AssemblyLinearVelocity = Vector3.zero
		-- Marque la part comme touchée
		local tag = Instance.new("Folder")
		tag.Name  = "Touched"
		tag.Parent = enemy
		Debris:AddItem(tag, enemiesData[enemy].resetDelay)
	end)
end

RunService.Heartbeat:Connect(function(deltaTime)
	-- Récupère le joueur local (ou le premier joueur connecté côté serveur)
	local player = Players:GetPlayers()[1]
	if not player then return end

	local character = player.Character
	if not character then return end
	-- Récupère la position du joueur
	local root = character:FindFirstChild("HumanoidRootPart")
	if not root then return end
	
	for enemy, data in pairs(enemiesData) do
	
		-- Vérifie que la plateforme existe encore
		if not enemy or not enemy.Parent then
			enemyData[enemy] = nil
			continue
		end	
		-- Si la part est déja touchée, on ne fait rien
		if enemy:FindFirstChild("Touched") then return end
		-- Vérifie que le joueur est à portée
		local distance = (root.Position - enemy.Position).Magnitude
		-- Trop loin : la part attend
		if distance > data.detectionDistance then return end
		-- Assez proche : arrêt
		if data.stopDistance > 0 and distance <= data.stopDistance then
			enemy.AssemblyLinearVelocity = Vector3.zero
			return
		end
		-- Calcul de la direction vers le joueur
		local direction = (root.Position - enemy.Position).Unit
		local newPosition = enemy.Position + direction * SPEED * deltaTime
		-- Mise à jour de la force à appliquer en fonction de la vitesse
		enemy.AssemblyLinearVelocity = direction * data.speed
		-- Mise à jour de la position de la part et son orientation	
		enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position) + direction * data.speed * deltaTime
	end		
end)

Paramétrer chaque ennemis

Ajoute aux propriétés de chaque ennemi les attributs suivants avec des valeurs différentes :

  • DetectionDistance : distance de détection du joueur (studs)
  • Speed : vitesse de déplacement (studs/s)
  • StopDistance : distance à laquelle la part s’arrête si 0 l’ennemi vient toucher le joueur
  • Damage : dégâts infligés aux joueurs
  • ResetDelay : délai avant de pouvoir toucher à nouveau (secondes)

Puis saisie le Nom de l’attribut ainsi que son type number :

Puis donne une valeur pour chaque attribut :

Catégories
Jeu vidéo ROBLOX

Créer des textures

Dans Roblox, les textures servent à donner un aspect plus réaliste aux objets du jeu et donne du relief au jeu. Au lieu d’avoir seulement une couleur simple, on peut ajouter des images pour représenter du bois, de la pierre, du métal ou encore de l’herbe.
La texture principale correspond à l’image visible sur l’objet, appelée souvent “Color” ou “Albedo”, qui donne les couleurs et les détails.
Il existe aussi des images spéciales comme la Normal Map : elle ne change pas la forme réelle de l’objet, mais elle crée une illusion de relief avec la lumière, par exemple des bosses, des fissures ou des briques.
D’autres textures peuvent contrôler la brillance, les reflets ou les zones transparentes, ce qui permet de créer des matériaux beaucoup plus réalistes dans un jeu Roblox.

Comment affecter une texture existante

Crée un part :

Associe au part une texture existante :

Puis choisis une couleur :

Essaye les autres textures.

Comment créer une nouvelle texture

Tu peux utiliser ce site pour créer une nouvelle texture ou ce site pour trouver des textures libre de droit :

Choisir une texture sur un site :

Modifie la taille de l’image dans Paint pour une taille de 512×512 pixels :

Puis générer une image « normalMap » pour le relief avec ce site :

Clic avec le bouton gauche de ta souris pour charger ton image :

Modifie les différents paramètres pour obtenir l’image que tu souhaites :

Puis saisis le nom de ton fichier puis clic sur Download :

Maintenant que tes deux images sont prêtes, dans ROBLOX demande l’ouverture du gestionnaire des matériaux :

Crée un nouveau matériau en cliquant sur le + en haut à gauche :

Donne un nom à ton nouveau matériau :

Importe ton image de base :

Ajoute ta nouvelle texture au bloc :

Rajoute ton image normalMap :

Pour retrouver tes nouvelles textures :

Et dans les propriétés du part :

Catégories
Jeu vidéo ROBLOX

ROBLOX Minecraft

Dans ce tutoriel, tu vas apprendre à programmer un système de construction comme dans Minecraft avec Roblox et le langage Lua.
Le joueur pourra créer des blocs pour construire des structures de blocs et détruire des blocs.
Tu découvriras comment utiliser la souris, le clavier et les scripts pour interagir avec les objets dans Roblox Studio.
Ce projet permet d’apprendre les bases importantes de la programmation de jeux vidéo : événements, conditions, coordonnées et objets 3D, les scripts en local et les scripts sur le serveur.
À la fin du tutoriel, tu auras créé un véritable système de construction interactif dans ton propre jeu Roblox.

Dans un premier temps, tu vas récupérer les actions du joueur comme les clics de souris et les touches du clavier dans LocalScript.
Dans un deuxième temps, ces informations seront ensuite envoyées par un système ROBLOX de RemoteEvent au serveur afin de vérifier et traiter les demandes du joueur.
Dans un troisième temps, tu créeras un Script sur le serveur pour créer ou détruir les blocs dans le monde du jeu pour que tous les joueurs puissent voir les modifications en même temps.

Tu découvriras ainsi comment fonctionne la communication entre le joueur et le serveur dans un vrai jeu multijoueur Roblox.

Récupération des événements souris et clavier

Sous StarterPlayer puis StarterCharacterScripts crée un Folder que tu renommes Minecraft, puis un LocalScript que tu renommes MCInput :

Le joueur interagit avec le monde du jeu comme dans Minecraft grâce à la souris et au clavier. Un clic sur le bouton gauche de la souris permet de construire un nouveau bloc dans le jeu. Un clic sur le bouton droit de la souris permet de détruire un bloc existant. Cependant, pour laisser la possibilité d’utiliser la souris pour les déplacements du joueur et de la caméra, la construction ou la destruction ne sera possible uniquement si le joueur maintient en même temps la touche Majuscule gauche (Left Shift) du clavier.

Dans MCInput, saisie cette programmation pour récupérer les événements clavier et souris :

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

RunService.RenderStepped:Connect(function()
	-- Appuyer sur la touche la touche majuscule pour que la souris soit prise en compteif
	if not UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) then return end
	-- Touche gauche de la souris pour construire
	if UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) then
		print("Mouse left pressed")
	-- Touche droite de la souris pour détruire
	elseif UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) then
		print("Mouse right pressed")
	end
end)

Lance le jeu et vérifie la prise en compte des boutons gauche et droit de la souris :

Comment remonter les actions du local vers le serveur

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

Deux événements sont remontés construire et détruire.

Crée deux Folder sous ReplicatedStorage Minecraft puis Remotes.

Sous le folder Remotes crée deux RemoteEvent MCBuild et MCDestroy :

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

Saisie ce code dans MCSystem :

local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- Références
local minecraft = ReplicatedStorage:WaitForChild("Minecraft")
local remotes = minecraft:WaitForChild("Remotes")


-- Remote : poser un bloc
remotes.MCBuild.OnServerEvent:Connect(function(player, data)
	print("Action Build")
end)

-- Remote : détruire un bloc
remotes.MCDestroy.OnServerEvent:Connect(function(player, data)
  	print("Action Destroy")
end)

Modifie ton LocalScript MCInput pour envoyer les actions :

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

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local minecraft = ReplicatedStorage:WaitForChild("Minecraft")
local remotes = minecraft:WaitForChild("Remotes")

RunService.RenderStepped:Connect(function()
	-- Appuyer sur la touche la touche majuscule pour que la souris soit prise en compte
	if not UserInputService:IsKeyDown(Enum.KeyCode.LeftShift) then return end
	-- Touche gauche de la souris pour construire
	if UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) then
		print("Mouse left pressed")
		remotes.MCBuild:FireServer()
	-- Touche droite de la souris pour détruire
	elseif UserInputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) then
		print("Mouse right pressed")
		remotes.MCDestroy:FireServer()
	end
end)

Lance ton jeu et vérifie que le serveur reçoit bien les deux actions :

Lorsque le joueur maintient un bouton de la souris appuyé, Roblox envoie les événements plusieurs fois de suite très rapidement.
Sans protection, le serveur pourrait alors créer ou détruire un très grand nombre de blocs en quelques secondes. Cela peut provoquer des bugs, ralentir le jeu ou permettre à un joueur de construire trop vite.
Nous allons donc mettre en place un mécanisme de limitation, appelé souvent “cooldown” ou temporisation, pour attendre un petit délai entre deux actions.
Ainsi, le serveur traitera les demandes de manière plus propre et le jeu restera fluide pour tous les joueurs.

Modifie le script serveur MCSystem :

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Debris            = game:GetService("Debris")

-- Références
local minecraft = ReplicatedStorage:WaitForChild("Minecraft")
local remotes = minecraft:WaitForChild("Remotes")

-- Système anti rebond via un Folder temporaire dans le Character du player
local function withCooldown(character, actionName, callback)
	if character:FindFirstChild(actionName) then return end

	local tag = Instance.new("Folder")
	tag.Name  = actionName
	tag.Parent = character
	Debris:AddItem(tag, COOLDOWN_DURATION)

	callback()
end

-- Remote : poser un bloc
remotes.MCBuild.OnServerEvent:Connect(function(player, data)
	local character = player.Character
	withCooldown(character, "Build", function()
		print("Action Build")
	end)

end)

-- Remote : détruire un bloc
remotes.MCDestroy.OnServerEvent:Connect(function(player, data)
	local character = player.Character
	withCooldown(character, "Destroying", function()
		print("Action Destroy")
	end)
end)

Tu remarques que dans la sortie, tu as une seule action pour plusieurs événements souris :

Récupération de la position du bloc à créer

Maintenant, nous devons récupérer la position de la souris dans le monde du jeu et non sa position sur l’écran de l’ordinateur.
En effet, pour construire ou détruire des blocs, le jeu doit savoir exactement quel endroit du monde 3D le joueur vise avec sa souris.
Le LocalScript va donc utiliser un système appelé “Raycast” pour projeter un rayon invisible depuis la caméra vers la scène du jeu.
Grâce à cela, nous pourrons obtenir les coordonnées précises du point visé dans l’espace ainsi que l’objet touché par la souris, par exemple un bloc ou la baseplate.
Ces informations seront ensuite envoyées au serveur pour créer ou supprimer les blocs au bon endroit.

Modifie le script serveur MCSystem :

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Debris            = game:GetService("Debris")

-- Références
local minecraft = ReplicatedStorage:WaitForChild("Minecraft")
local remotes = minecraft:WaitForChild("Remotes")

-- Système anti rebond via un Folder temporaire dans le Character du player
local function withCooldown(character, actionName, callback)
	if character:FindFirstChild(actionName) then return end

	local tag = Instance.new("Folder")
	tag.Name  = actionName
	tag.Parent = character
	Debris:AddItem(tag, COOLDOWN_DURATION)

	callback()
end

-- Valide les données communes aux deux remotes
local function parseData(player, data)
	local character = player.Character
	if not character then return nil end
	if not character:FindFirstChildOfClass("Humanoid") then return nil end

	local position3D  = data[1]
	local direction   = data[2]
	local partTouchee = data[3]
	if not partTouchee then return nil end
	print("Position3D:", position3D, "Direction:", direction, "Part touchée:", partTouchee.Name)
	return character, position3D, direction, partTouchee
end

-- Remote : poser un bloc
remotes.MCBuild.OnServerEvent:Connect(function(player, data)
	local character, position3D, direction = parseData(player, data)
	if not character then return end
	withCooldown(character, "Build", function()
		print("Action Build")
	end)

end)

-- Remote : détruire un bloc
remotes.MCDestroy.OnServerEvent:Connect(function(player, data)
	local character, _, _, partTouchee = parseData(player, data)
	if not character then return end
	withCooldown(character, "Destroying", function()
		print("Action Destroy")
	end)
end)

Lance ton jeu et vérifie que le serveur reçoit la position pointée par la souris, le direction et la part touchée.

Création d’un bloc Minecraft

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

Crée un part qui sera ta brique de base Minecraft :

Fais en sorte que le part soit cubique :

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

Puis continue à modifier le script serveur MCSystem pour créer les blocs :

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Debris            = game:GetService("Debris")

-- Références
local minecraft = ReplicatedStorage:WaitForChild("Minecraft")
local remotes = minecraft:WaitForChild("Remotes")
local blockTemplate = minecraft:WaitForChild("MCBlock")

-- Configuration
local GRID_SIZE      = blockTemplate.Size.X
local ANCHOR_DELAY   = 0.5  -- délai avant tentative d'ancrage
local VELOCITY_THRESHOLD = 0.1  -- seuil de vitesse pour ancrage
local COOLDOWN_DURATION  = 0.5  -- durée du cooldown build/destroy
local FOLDERMC = "Minecraft"

-- Système anti rebond via un Folder temporaire dans le Character du player
local function withCooldown(character, actionName, callback)
	if character:FindFirstChild(actionName) then return end

	local tag = Instance.new("Folder")
	tag.Name  = actionName
	tag.Parent = character
	Debris:AddItem(tag, COOLDOWN_DURATION)

	callback()
end

-- Valide les données communes aux deux remotes
local function parseData(player, data)
	local character = player.Character
	if not character then return nil end
	if not character:FindFirstChildOfClass("Humanoid") then return nil end

	local position3D  = data[1]
	local direction   = data[2]
	local partTouchee = data[3]
	if not partTouchee then return nil end
	print("Position3D:", position3D, "Direction:", direction, "Part touchée:", partTouchee.Name)
	return character, position3D, direction, partTouchee
end

-- Aligne une position sur la grille en fonction du cube de référence
local function snapToGrid(position)
	local function snap(v) return math.floor(v / GRID_SIZE + 0.5) * GRID_SIZE end
	return Vector3.new(snap(position.X), snap(position.Y), snap(position.Z))
end

-- Ancrage progressif d'un bloc
local function anchorBlock(block)
	if block.Anchored then return end

	task.wait(ANCHOR_DELAY)

	repeat task.wait(0.1) until
	not block or
		not block.Parent or
		block.AssemblyLinearVelocity.Magnitude <= VELOCITY_THRESHOLD

	if not block or not block.Parent then return end

	block.Anchored               = true
	block.AssemblyLinearVelocity  = Vector3.zero
	block.AssemblyAngularVelocity = Vector3.zero
end

-- Récupère ou crée le folder Minecraft dans le workspace
local function getOrCreateFolderMC()
	local folder = workspace:FindFirstChild(FOLDERMC)
	if not folder then
		folder = Instance.new("Folder")
		folder.Name   = FOLDERMC
		folder.Parent = workspace
	end
	return folder
end

local folderMinecraft = getOrCreateFolderMC()
-- Remote : poser un bloc
remotes.MCBuild.OnServerEvent:Connect(function(player, data)
	local character, position3D, direction = parseData(player, data)
	if not character then return end
	withCooldown(character, "Build", function()
		local newPosition = snapToGrid(position3D + direction * GRID_SIZE)

		local newBlock = blockTemplate:Clone()
		newBlock.Position = newPosition
		newBlock.Anchored = false
		newBlock.Parent   = folderMinecraft

		local connection
		connection = newBlock.Touched:Connect(function(otherPart)
			if not otherPart.Anchored and otherPart.Name ~= "Baseplate" then return end
			connection:Disconnect()  -- un seul ancrage
			anchorBlock(newBlock)
		end)
	end)

end)

-- Remote : détruire un bloc
remotes.MCDestroy.OnServerEvent:Connect(function(player, data)
	local character, _, _, partTouchee = parseData(player, data)
	if not character then return end
	withCooldown(character, "Destroying", function()
		print("Action Destroy")
	end)
end)

Destruction d’un bloc

Dans le script serveur MCSystem modifie juste l’action MCDestroy :


-- Remote : détruire un bloc
remotes.MCDestroy.OnServerEvent:Connect(function(player, data)
	local character, _, _, partTouchee = parseData(player, data)
	if not character then return end
	withCooldown(character, "Destroying", function()
		if partTouchee:IsA("BasePart") and partTouchee.Name == blockTemplate.Name then
			partTouchee:Destroy()
		end
	end)
end)

Si tout fonctionne correctement, tu peux supprimer les « print » de ton code ou les mettre en commentaire :

-- Valide les données communes aux deux remotes
local function parseData(player, data)
	local character = player.Character
	if not character then return nil end
	if not character:FindFirstChildOfClass("Humanoid") then return nil end

	local position3D  = data[1]
	local direction   = data[2]
	local partTouchee = data[3]
	if not partTouchee then return nil end
	--print("Position3D:", position3D, "Direction:", direction, "Part touchée:", partTouchee.Name)
	return character, position3D, direction, partTouchee
end
Catégories
Jeu vidéo ROBLOX

Système d’ouverture de portes

Construis un système de portes que tu pourras répliquer partout dans ton jeu. Le joueur pourra ouvrir des portes et découvrir d’autres pièces ou d’autres mondes. Tu pourras choisir entre une fermeture manuelle de la porte ou automatique après un laps de temps. Plusieurs boutons synchronisés permettent de piloter une même porte. Le bouton peut-être installé à distance de la porte.

Construis cette arborescence dans ton workspace :

Sous un folder, insére un son pour l’ouverture de ta porte, un script, un part représentant ta porte, sous la porte un part pour représenter le bouton d’ouverture, puis un proxyprompt sous le bouton. Renomme chaque élément pour plus de clarté :

Les noms donnés n’ont pas d’importance par contre la structure de l’arborescence du folder doit être absolument respectée.

Saisie le code suivant dans le script :

-- Récupération du joueur
local Players = game:GetService("Players")

-- Récupération du dossier des portes
local doorsFolder = script.Parent

-- Initialisation de chaque porte
-- Le dossier doit comporte un part pour la porte
-- puis un part pour chaque bouton
-- puis proxymity prompt pour chaque bouton
for _, door in doorsFolder:GetChildren() do
	if not door:IsA("Part") then continue end
	door.Anchored     = true
	door.Transparency = 0
	door.CanCollide   = true

	for _, button in door:GetChildren() do
		if not button:IsA("Part") then continue end
		button.Anchored     = true
		button.Transparency = 0
		button.CanCollide   = true		
		local proxi = button:FindFirstChildOfClass("ProximityPrompt")
		if not proxi then continue end
		proxi.ActionText = LABEL_OPEN
		proxi.KeyboardKeyCode=Enum.KeyCode.E
		local delay = button:GetAttribute("TimeoutClose") or TIMEOUT_CLOSE

		proxi.Triggered:Connect(function(player)
			-- Ouverture ou fermeture de la porte
			
			print("ouverture porte")
		end)
	end
end

Lance ton jeu, quand tu actives le proximitry prompt, le message suivant s’affiche dans la console de sortie mais la porte ne s’ouvre pas encore :

Modifie ton code pour ouvrir la porte :

-- Récupération du joueur
local Players = game:GetService("Players")

-- Configuration
local TIMEOUT_CLOSE = 0
local LABEL_CLOSE   = "Fermer"
local LABEL_OPEN    = "Ouvrir"

-- Récupération du dossier des portes
local doorsFolder = script.Parent
local sound       = doorsFolder:FindFirstChildOfClass("Sound")

-- Lecture du son d'ouverture de la porte
local function playSound()
	if not sound then return end
	pcall(function() sound:Play() end)
end

-- Ouverture ou fermeture d'une porte
local function setDoor(door, isOpen)
	door.Transparency = isOpen and 1 or 0
	door.CanCollide   = not isOpen
end

-- Mise à jour du texte des proximity prompt des boutons
local function setActionText(door)
	-- Pas de fermeture automatique
	for _, button in door:GetChildren() do
		if button:IsA("Part") then
			local proxi = button:FindFirstChildOfClass("ProximityPrompt")
			if proxi then
				proxi.ActionText = door.CanCollide and LABEL_OPEN or LABEL_CLOSE
			end
		end
	end

end

-- Activation ou désactivation des proximity prompts des boutons
local function setEnabledProxi(door, isOpen)
	-- Pas de fermeture automatique
	for _, button in door:GetChildren() do
		if button:IsA("Part") then
			local proxi = button:FindFirstChildOfClass("ProximityPrompt")
			proxi.Enabled = isOpen			
		end
	end	
end

-- Initialisation de chaque porte
-- Le dossier doit comporte un part pour la porte
-- puis un part pour chaque bouton
-- puis proxymity prompt pour chaque bouton
for _, door in doorsFolder:GetChildren() do
	if not door:IsA("Part") then continue end
	door.Anchored     = true
	door.Transparency = 0
	door.CanCollide   = true

	for _, button in door:GetChildren() do
		if not button:IsA("Part") then continue end
		button.Anchored     = true
		button.Transparency = 0
		button.CanCollide   = true		
		local proxi = button:FindFirstChildOfClass("ProximityPrompt")
		if not proxi then continue end
		proxi.ActionText = LABEL_OPEN
		proxi.KeyboardKeyCode=Enum.KeyCode.E
		local delay = button:GetAttribute("TimeoutClose") or TIMEOUT_CLOSE

		proxi.Triggered:Connect(function(player)
			-- Ouverture ou fermeture de la porte
			playSound()
			setDoor(door, door.CanCollide)

			if delay > 0 then
				-- Décompte pour fermeture automatique
				setEnabledProxi(door, false)
				task.delay(delay, function()
					setDoor(door, false)
					setEnabledProxi(door, true)
					playSound()
				end)
			else
				-- Pas de fermeture automatique
				setActionText(door)
			end
		end)
	end
end

Lance ton jeu et regarde l’ouverture de ta porte.

Modifie dans ton script la valeur de cette constante :

local TIMEOUT_CLOSE = 3

Lance ton jeu et regarde le comportement de ta porte.

Duplique les boutons d’ouverture :

Lance ton jeu, et essaye tous les boutons d’ouverture.

Construis un décor autour de ta porte :

Crée une arborescence sous la forme d’un folder sous ta porte pour ranger la structure qui entoure celle-ci :

Tu peux dupliquer ta porte et ainsi avoir tout un système de portes dans ton jeu :

Puis différencier les portes :

Modifie ton code pour prendre en compte un ClickDetector sur un bouton :

Modifie ton script pour tester si le bouton a un promixityPrompt ou un clickDetector :

-- Récupération du joueur
local Players = game:GetService("Players")

-- Configuration
local TIMEOUT_CLOSE = 0
local LABEL_CLOSE   = "Fermer"
local LABEL_OPEN    = "Ouvrir"

-- Récupération du dossier des portes
local doorsFolder = script.Parent
local sound       = doorsFolder:FindFirstChildOfClass("Sound")

-- Lecture du son d'ouverture de la porte
local function playSound()
	if not sound then return end
	pcall(function() sound:Play() end)
end

-- Ouverture ou fermeture d'une porte
local function setDoor(door, isOpen)
	door.Transparency = isOpen and 1 or 0
	door.CanCollide   = not isOpen
end

-- Mise à jour du texte des proximity prompt des boutons
local function setActionText(door)
	-- Pas de fermeture automatique
	for _, button in door:GetChildren() do
		if button:IsA("Part") then
			local proxi = button:FindFirstChildOfClass("ProximityPrompt")
			if proxi then
				proxi.ActionText = door.CanCollide and LABEL_OPEN or LABEL_CLOSE
			end
		end
	end

end

-- Activation ou désactivation des proximity prompts des boutons
local function setEnabledProxi(door, isOpen)
	-- Pas de fermeture automatique
	for _, button in door:GetChildren() do
		if button:IsA("Part") then
			local proxi = button:FindFirstChildOfClass("ProximityPrompt")
			if proxi then 
				proxi.Enabled = isOpen
			end
		end
	end	
end

-- Logique de déclenchement commune
local function connectTrigger(door, button, onTriggered)
	local delay = button:GetAttribute("TimeoutClose") or TIMEOUT_CLOSE

	onTriggered(function()
		-- Ouverture ou fermeture de la porte
		playSound()
		setDoor(door, door.CanCollide)

		if delay > 0 then
			-- Décompte pour fermeture automatique
			setEnabledProxi(door, false)
			task.delay(delay, function()
				setDoor(door, false)
				setEnabledProxi(door, true)
				playSound()
			end)
		else
			-- Pas de fermeture automatique
			setActionText(door)
		end
	end)

end



-- Initialisation de chaque porte
-- Le dossier doit comporte un part pour la porte
-- puis un part pour chaque bouton
-- puis proxymity prompt pour chaque bouton
for _, door in doorsFolder:GetChildren() do
	if not door:IsA("Part") then continue end
	door.Anchored     = true
	door.Transparency = 0
	door.CanCollide   = true

	for _, button in door:GetChildren() do
		if not button:IsA("Part") then continue end
		button.Anchored     = true
		button.Transparency = 0
		button.CanCollide   = true	
		--test si le bouton a un proximity prompt ou un click detector
		local proxi = button:FindFirstChildOfClass("ProximityPrompt")
		local clicker = button:FindFirstChildOfClass("ClickDetector")

		if proxi then
			proxi.ActionText = LABEL_OPEN
			proxi.KeyboardKeyCode=Enum.KeyCode.E
			connectTrigger(door, button, function(callback)
				proxi.Triggered:Connect(function(player)
					callback()
				end)
			end)

		elseif clicker then
			connectTrigger(door, button, function(callback)
				clicker.MouseClick:Connect(function(player)
					callback()
				end)
			end)

		else
			warn("[DoorManager] Aucun ProximityPrompt ni ClickDetector sur :", button:GetFullName())
		end
		
	end
end
Catégories
Jeu vidéo ROBLOX

Barrières invisibles infranchissables

Crée dans ton jeu des barrières invisibles que le joueur ne peut pas franchir. Dès que le joueur touche une barrière invisible, celle-ci se matérialise et déclenche une alarme sonore. Tu peux répliquer autant de barrières invisibles et infranchissables que tu auras besoin pour ton jeu. Ces barrières permettent à ton joueur d’être guider et l’oblige à passer par certaines étapes de ton monde.

Crée dans ton workspace l’arborescence suivante dans un Folder avec un fichier son en option, un script, un part représentant un mur :

Puis saisie ce code dans ton script :

local Players = game:GetService("Players")

-- Configuration
local TRANSPARENCY_VISIBLE = 0.7
local TRANSPARENCY_HIDDEN  = 1
local FADE_DELAY           = 2

-- Récupération des objets du folder
local wallsFolder = script.Parent

-- Fonction appelée quand un joueur touche un mur
local function onWallTouched(otherPart, wall)
	-- Vérification joueur uniquement
	local character = otherPart.Parent
	if not Players:GetPlayerFromCharacter(character) then return end
	print("Collision avec le joueur")
end

-- Lecture de tous les murs du dossier
for _, wall in wallsFolder:GetChildren() do
	if not wall:IsA("Part") then continue end

	wall.Anchored     = true
	wall.Transparency = TRANSPARENCY_HIDDEN
	wall.Material     = Enum.Material.Neon
	wall.CanCollide   = true  -- mur bloquant

	wall.Touched:Connect(function(otherPart)
		onWallTouched(otherPart, wall)
	end)
end

Lance ton jeu, le mur est invisible, si ton joueur touche le mur il reste bloqué !!! Un message dans la sortie indique que le joueur est rentré en collision avec le mur :

Afin d’indiquer au joueur qu’il ne peut pas franchir le mur invisible, on va le faire apparaître et déclencher une alarme sonore.

Modifie le script :

local Players = game:GetService("Players")

-- Configuration
local TRANSPARENCY_VISIBLE = 0.7
local TRANSPARENCY_HIDDEN  = 1
local FADE_DELAY           = 2

-- Récupération des objets du folder
local wallsFolder = script.Parent

-- Chargement du fichier sonore (si présent)
local sound = wallsFolder:FindFirstChildOfClass("Sound")
if not sound then
	warn("[CoinManager] Aucun Sound trouvé dans ", coinsFolder.Name)
end

-- Fonction appelée quand un joueur touche un mur
local function onWallTouched(otherPart, wall)
	-- Vérification joueur uniquement
	local character = otherPart.Parent
	if not Players:GetPlayerFromCharacter(character) then return end
	-- Évite les appels multiples simultanés
	if wall.Transparency == TRANSPARENCY_VISIBLE then return end
	
	-- Affichage du mur en transparence
	wall.Transparency = TRANSPARENCY_VISIBLE
	-- Son d'alerte
	if sound then
		pcall(function() sound:Play() end)
	end
	task.wait(FADE_DELAY)
	wall.Transparency = TRANSPARENCY_HIDDEN
end

-- Lecture de tous les murs du dossier
for _, wall in wallsFolder:GetChildren() do
	if not wall:IsA("Part") then continue end

	wall.Anchored     = true
	wall.Transparency = TRANSPARENCY_HIDDEN
	wall.Material     = Enum.Material.Neon
	wall.CanCollide   = true  -- mur bloquant

	wall.Touched:Connect(function(otherPart)
		onWallTouched(otherPart, wall)
	end)
end

Duplique les murs invisibles infranchissables et déplace les dans ton espace de jeu :

Catégories
Jeu vidéo ROBLOX

Créer un système de gains

Crée un système de gain pour que ton joueur puisse gagner des points. Ce tuto montre comment programmer un système de gain en attrapant des pièces de valeurs différentes. Tu pourras répliquer autant de pièces que tu le souhaites dans ton monde.

Tu peux convertir cette proposition par un système de gains autre que par des pièces. A toi d’être imaginatif !!!

Crée la structure suivante « Coins » pour ton système de gain :

Crée un Model sous workspace que tu peux renommer Coins. Puis dessous ajoute un son, puis un script et également un part pour représenter ta pièce.

Puis modifie ton script pour générer ton système de gain, le script affiche la pièce et le gain potentiel. Pour l’instant le joueur ne peut pas récupérer les pièces donc son gain :

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

-- Configuration
local ROTATION_SPEED   = 5
local MAX_GAIN         = 10
local RESPAWN_DELAY    = 5   -- secondes avant respawn
local FADE_SPEED       = 0.01 -- vitesse de disparition
local COIN_LEADERBOARD = "Coins"

-- Répertoire de ton système de gains sous workspace
local coinsFolder = script.Parent
-- Chargement du fichier sonore (si présent)
local sound = coinsFolder:FindFirstChildOfClass("Sound")
if not sound then
	warn("[CoinManager] Aucun Sound trouvé dans ", coinsFolder.Name)
end

-- Initialise et retourne les composants d'une pièce
-- Ajoute à tes pièces la valeur du gain et un système de particules
local function setupCoin(coin)
	coin.Anchored = true

	-- Hitbox box de collision autour de la pièce
	local hitbox = Instance.new("Part")
	hitbox.Name         = "Hitbox"
	hitbox.Parent       = coin
	hitbox.Position     = coin.Position
	hitbox.Size         = Vector3.new(2, 4, 2)
	hitbox.Transparency = 1
	hitbox.CanCollide   = false
	hitbox.Anchored     = true

	-- Particule effet lorsque le pièce est touchée
	local particule = Instance.new("ParticleEmitter")
	particule.Parent           = coin
	particule.EmissionDirection = Enum.NormalId.Back
	particule.Enabled          = false

	-- Billboard pour afficher le gain
	local billboard = Instance.new("BillboardGui")
	billboard.Name        = "CoinLabel"
	billboard.Adornee     = coin
	billboard.Size        = UDim2.new(0, 40, 0, 20)
	billboard.StudsOffset = Vector3.new(0, 1.5, 0)
	billboard.AlwaysOnTop = false
	billboard.Parent      = coin

	local label = Instance.new("TextLabel")
	label.Parent               = billboard
	label.Size                 = UDim2.new(1, 0, 1, 0)
	label.BackgroundTransparency = 1
	label.Text                 = tostring(math.random(1, MAX_GAIN))
	label.TextColor3           = Color3.fromRGB(255, 255, 0)
	label.TextStrokeTransparency = 0
	label.TextScaled           = true

	return hitbox, particule, label, billboard
end

-- Connecte la logique de collecte sur une pièce
local function connectCoin(coin)
	local hitbox, particule, label, billboard = setupCoin(coin)
	local gain = tonumber(label.Text)

end

-- Rotation & disparition progressive (Heartbeat)
RunService.Heartbeat:Connect(function()
	for _, coin in coinsFolder:GetChildren() do
		if not coin:IsA("Part") then continue end

		if coin.CanCollide then
			-- Rotation active
			if coin.Transparency > 0 then
				coin.Transparency = 0
			end
			coin.CFrame *= CFrame.Angles(0, math.rad(ROTATION_SPEED), 0)
		else
			-- Disparition progressive
			coin.Transparency = math.min(coin.Transparency + FADE_SPEED, 1)		
		end
	end
end)

-- Initialisation de toutes les pièces
for _, coin in coinsFolder:GetChildren() do
	if coin:IsA("Part") then
		connectCoin(coin)
	end
end

Puis modifie ton script pour que le joueur puisse attraper les pièces :

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

-- Configuration
local ROTATION_SPEED   = 5
local MAX_GAIN         = 10
local RESPAWN_DELAY    = 5   -- secondes avant respawn
local FADE_SPEED       = 0.01 -- vitesse de disparition
local COIN_LEADERBOARD = "Coins"

-- Répertoire de ton système de gains sous workspace
local coinsFolder = script.Parent
-- Chargement du fichier sonore (si présent)
local sound = coinsFolder:FindFirstChildOfClass("Sound")
if not sound then
	warn("[CoinManager] Aucun Sound trouvé dans ", coinsFolder.Name)
end

-- Initialise et retourne les composants d'une pièce
-- Ajoute à tes pièces la valeur du gain et un système de particules
local function setupCoin(coin)
	coin.Anchored = true

	-- Hitbox box de collision autour de la pièce
	local hitbox = Instance.new("Part")
	hitbox.Name         = "Hitbox"
	hitbox.Parent       = coin
	hitbox.Position     = coin.Position
	hitbox.Size         = Vector3.new(2, 4, 2)
	hitbox.Transparency = 1
	hitbox.CanCollide   = false
	hitbox.Anchored     = true

	-- Particule effet lorsque le pièce est touchée
	local particule = Instance.new("ParticleEmitter")
	particule.Parent           = coin
	particule.EmissionDirection = Enum.NormalId.Back
	particule.Enabled          = false

	-- Billboard pour afficher le gain
	local billboard = Instance.new("BillboardGui")
	billboard.Name        = "CoinLabel"
	billboard.Adornee     = coin
	billboard.Size        = UDim2.new(0, 40, 0, 20)
	billboard.StudsOffset = Vector3.new(0, 1.5, 0)
	billboard.AlwaysOnTop = false
	billboard.Parent      = coin

	local label = Instance.new("TextLabel")
	label.Parent               = billboard
	label.Size                 = UDim2.new(1, 0, 1, 0)
	label.BackgroundTransparency = 1
	label.Text                 = tostring(math.random(1, MAX_GAIN))
	label.TextColor3           = Color3.fromRGB(255, 255, 0)
	label.TextStrokeTransparency = 0
	label.TextScaled           = true

	return hitbox, particule, label, billboard
end

-- Réinitialise une pièce pour le respawn
local function resetCoin(coin)
	coin.Transparency = 0
	coin.CanCollide   = true
end

-- Connecte la logique de collecte sur une pièce
local function connectCoin(coin)
	local hitbox, particule, label, billboard = setupCoin(coin)
	local gain = tonumber(label.Text)

	hitbox.Touched:Connect(function(otherPart)
		if not coin.CanCollide then return end

		local character = otherPart.Parent
		if not character then return end

		local player   = Players:GetPlayerFromCharacter(character)
		local humanoid = character:FindFirstChildOfClass("Humanoid")
		if not player or not humanoid then return end

		-- Collecte
		coin.CanCollide   = false
		particule.Enabled = true
		billboard.Enabled = false 
		
		-- Lecture protégée
		local soundDuration = 1 -- fallback si pas de son
		if sound then
			pcall(function() sound:Play() end)
			soundDuration = sound.TimeLength > 0 and sound.TimeLength or 1
		end
		
		-- Ajout des points
		local leaderstats = player:FindFirstChild("leaderstats")
		if leaderstats and leaderstats:FindFirstChild(COIN_LEADERBOARD) then
			leaderstats.Coins.Value += gain
		end

		-- Arrêt des particules à la fin du son
		task.delay(soundDuration, function()
			if not coin or not coin.Parent then return end
			particule.Enabled = false
			particule:Clear()
		end)
		
		-- Relance de la pièce après un délai
		if RESPAWN_DELAY > 0 then
			task.delay(RESPAWN_DELAY, function()
				-- Attente puis respawn
				if not coin or not coin.Parent then return end
				resetCoin(coin)
				billboard.Enabled = true  -- réaffiche le label au respawn
			end)

		end
	end)
end

-- Rotation & disparition progressive (Heartbeat)
RunService.Heartbeat:Connect(function()
	for _, coin in coinsFolder:GetChildren() do
		if not coin:IsA("Part") then continue end

		if coin.CanCollide then
			-- Rotation active
			if coin.Transparency > 0 then
				coin.Transparency = 0
			end
			coin.CFrame *= CFrame.Angles(0, math.rad(ROTATION_SPEED), 0)
		else
			-- Disparition progressive
			coin.Transparency = math.min(coin.Transparency + FADE_SPEED, 1)		
		end
	end
end)

-- Initialisation de toutes les pièces
for _, coin in coinsFolder:GetChildren() do
	if coin:IsA("Part") then
		connectCoin(coin)
	end
end

Tu peux changer ces paramètres :

local ROTATION_SPEED = 5 — Modifie la vitesse de rotation de tes pièces
local MAX_GAIN = 10 — Modifie le système de gain de 1 à 10
local RESPAWN_DELAY = 5 — Secondes avant respawn des pièces, 0 pas de respawn des pièces
local FADE_SPEED = 0.01 — vitesse de la disparition des pièces

Duplique les pièces et place les dans ton espace de jeu :

Catégories
Jeu vidéo ROBLOX

Créer un espace d’eau

Avec ce tuto tu peux créer un océan ou une espace d’eau confiné par exemple comme une piscine.

Par script, tu généreras un plan d’eau sur toute ta map comme pour un océan ou en fonction de la taille d’un part que tu disposeras sur ton jeu.

Dans un premier temps, crée un script sur le serveur qui va générer un espace d’eau soit un océan ou un espace contraint dans un part :

Crée un ModuleScript sous ServerScriptService :

Renomme le nom du moduleScript en waterGenerator :

Saisie le code suivant pour générer un espace d’eau comme un océan ou un espace d’eau contraint par un part comme une piscine :

local waterGenerator = {}

-- Valeurs par défaut
local TAILLE_DEFAUT  = 4048
local PROFONDEUR_DEFAUT = 200

-- Privée : fonction centrale de génération
local function createWater(terrain, centreX, centreY, centreZ, tailleX, tailleY, tailleZ)
	if not terrain then
		warn("[waterGenerator] Terrain manquant.")
		return
	end	
	local cframe = CFrame.new(centreX, centreY, centreZ)
	local size   = Vector3.new(tailleX, tailleY, tailleZ)
	terrain:FillBlock(cframe, size, Enum.Material.Water)
end

-- Génère une mer plate 
-- hauteur  : niveau Y de la surface
-- profondeur : épaisseur de l'eau vers le bas
-- taille   : largeur X et Z (optionnel, défaut 4048)
function waterGenerator.createSea(terrain, hauteur, profondeur, taille)
	if not hauteur or hauteur <= 0 then
		warn("[waterGenerator] Hauteur invalide ou manquante.")
		return
	end

	profondeur = profondeur or PROFONDEUR_DEFAUT
	if profondeur <= 0 then
		warn("[waterGenerator] Profondeur invalide.")
		return
	end

	taille = taille or TAILLE_DEFAUT
	local centreY = hauteur - profondeur / 2
	createWater(terrain, 0, centreY, 0, taille, profondeur, taille)
end

-- Génère de l'eau centrée sur une Part
-- La part définit position et taille (axes intentionnellement remappés X→Z, Z→Y, Y→X)
function waterGenerator.createPart(terrain, part)
	if not part then
		warn("[waterGenerator] Part manquante.")
		return
	end
	
	-- Préparation de la part : on la fait disparaître	
	part.Anchored = true
	part.CanCollide = false
	part.Transparency = 1

	createWater(
		terrain,
		part.Position.X,
		part.Position.Y,
		part.Position.Z,
		part.Size.X,  -- tailleX 
		part.Size.y,  -- tailleY 
		part.Size.Z   -- tailleZ 
	)
end

return waterGenerator

Comment créer un océan :

Dans ton workpace, crée un script :

local terrain      = workspace.Terrain

local waterGenerator = require(game.ServerScriptService.waterGenerator)

-- Génération de la mer 
waterGenerator.createSea(terrain, 5, 200)

Si tu lances ton jeu :

Comment créer une piscine :

Crée un part qui va contenir l’eau de ta piscine :

Crée un script avec le code suivant :

local terrain      = workspace.Terrain
local piscine      = script.Parent


local waterGenerator = require(game.ServerScriptService.waterGenerator)

waterGenerator.createPart(terrain, piscine)

Lance ton jeu, tu obtiens une masse d’eau contenu dans ton part :

Et avec un peu de décor, tu obtiendras une belle piscine :

Catégories
Jeu vidéo ROBLOX

Création d’une boutique

Améliore ton jeu par des dispositifs comme par exemple une boutique.


Créer une boutique

Dans beaucoup de jeux, les joueurs peuvent acheter des objets, des vêtements, des pouvoirs ou des accessoires. La boutique sert donc à donner plus d’objectifs au joueur : gagner de l’argent dans le jeu, choisir quoi acheter et améliorer son personnage. Une boutique rend aussi le jeu plus vivant et plus intéressant.

Dans Roblox Studio, on peut construire des décors, placer des objets et ajouter des menus à l’écran. Avec le langage de programmation Lua, nous apprendrons à donner des actions aux objets et à créer les règles du jeu.

La démarche pour la construction de ta boutique sera progressive :

  • D’abord, nous apprendrons à créer l’interface de la boutique : une fenêtre avec des boutons et les noms des objets à vendre.
  • Ensuite, nous programmerons l’argent du joueur avec une variable. Puis, nous coderons les achats : lorsqu’un joueur clique sur un bouton, le programme vérifie s’il a assez d’argent. Si oui, il paie et reçoit l’objet. Sinon, un message lui indique qu’il lui manque des pièces.

Ce projet te permettra de découvrir plusieurs notions importantes en programmation : les variables, les conditions, les événements, les fonctions et l’organisation d’un vrai projet numérique. À la fin, tu disposeras d’une boutique fonctionnelle dans Roblox.

Création de la boutique :

Pout ta boutique crée :

  • une fenêtre sur ta boutique avec les options à acheter
  • un bouton pour ouvrir et fermer la fenêtre de ta boutique
  • un localscript pour gérer l’ouverture de ta boutique

Création d’un écran utilisateur pour représenter la boutique, sous StaterGui demande la création d’un ScreenGui

Sous ScreenGui crée un Frame :

Déplace et agrandit le Frame pour prendre la place de ta boutique :

Modifie les propriétés, la couleur, le contour, la transparence du Frame :

Renomme les objets de ta botique :

Sous FrameStore rajoute un TextLabel pour le titre de ta boutique :

Dans le TextLabel rajoute le titre de la boutique :

Maintenant, tu vas rajouter des boutons pour chaque Item de ta boutique :

Tu dois obtenir ce résultat :

Tu peux rajouter une image pour certain élément de ta boutique :

Recherche une image dans la boîte à outil :

Récupère l’identifiant de l’image par un clic droit sur l’image :

Copie l’identifiant dans la propriété Image de ton ImageLabel :

Sous ScreenStore crée un ImageButton qui te permettra d’ouvrir ou fermer ta boutique :

Recherche dans la boîte à outil une image pour représenter ta boutique :

Par le clic droit demande à copier l’identifiant de l’image que tu colle dans la propriété Image de ton ImageButton :

Sous ScreenStore demande la création d’un LocalScript :

Renomme ton LocalScript et tu dois obtenir ce résultat :

Maintenant, tu peux modifier le script pour ouvrir ou fermer la boutique :

-- récupération du bouton pour ouvrir la boutique
local openButton = script.Parent.OpenButton
-- récupération du frame de la boutique	
local frameStore = script.Parent.FrameStore

-- on cache le frame de la boutique au lancement du jeu par defaut	
frameStore.Visible = false

-- on écoute l'événement de clic sur le bouton pour ouvrir ou fermer la boutique
openButton.MouseButton1Click:Connect(function()
	frameStore.Visible = not frameStore.Visible
end)

Crée un tableau de points

Objectif : Créé un tableau de scores de Points

Créer un script pour afficher un tableau de score au joueur sous ServerScriptService :

Renommer le script leaderstats :

Saisir le code suivant pour un affichage d’une barre de scores de Points :

-- récupérer le service sur les joueurs
local players = game:GetService("Players")

-- écouteur d'un nouveau joueur
players.PlayerAdded:Connect(function(player)
	-- créer un dossier leaderstats
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
end)

Si tu lances ton jeu une barre s’affiche :

Si le score ne s’affiche pas vérifie :

Rajoute à ton script un score à afficher :

-- récupérer le service sur les joueurs
local players = game:GetService("Players")

-- écouteur d'un nouveau joueur
players.PlayerAdded:Connect(function(player)
	-- créer un dossier leaderstats
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
	-- créer un score de Points
	local score = Instance.new("IntValue")
	score.Parent = leaderstats
	score.Name = "Points"
	score.Value = 10
end)

Le score s’affiche si tu lances ton jeu :


GAGNER DES POINTS

Créer un Part avec un ClickDetector afin que le joueur puisse gagner des points, exemple à reproduire sur tous les objets que tu souhaites dans ton jeu :

Exemple d’un script pour augmenter le score si le joueur click sur un ClickDetector :

Sasie le code suivant- dans le script :

-- récupérer le ClickDetector du Part
local clickDetector = script.Parent.ClickDetector

-- écouter l'événement de clic du Clickdetector
clickDetector.MouseClick:Connect(function(player)
	-- ajouter 10 Points au joueur
	player:WaitForChild("leaderstats").Points.Value += 10

end)

Lance ton jeu, maintenant à chaque clic le score de points augmente de 10 :


Acheter

Sur l’écran de sa boutique, le joueur clic sur l’item de son achat. Le LocalScript de la boutique demande via une RemoteFunction la possibilité de l’achat de l’item. Un script sur le serveur vérifie le nombre de point et la disponibilité de l’item.

Mis en place de l’échange entre le Localscript de la boutique et le serveur par un RemoteFunction :

Renomme RemoteFunction par RemoteBuy :

Puis modifie le script StoreScript lié à ta boutique :

-- récupération du bouton pour ouvrir la boutique
local openButton = script.Parent.OpenButton
-- récupération du frame de la boutique	
local frameStore = script.Parent.FrameStore

-- on cache le frame de la boutique au lancement du jeu par defaut	
frameStore.Visible = false

-- on écoute l'événement de clic sur le bouton pour ouvrir ou fermer la boutique
openButton.MouseButton1Click:Connect(function()
	frameStore.Visible = not frameStore.Visible
end)

-- on récupère le bouton de la lampe dans la boutique
local lampButton = script.Parent.FrameStore.LampButton
-- on récupère le RemoteEvent pour acheter la lampe
local remoteBuy = game:GetService("ReplicatedStorage"):WaitForChild("RemoteBuy")
-- on écoute le clic sur le bouton de la lampe pour l'acheter
lampButton.MouseButton1Click:Connect(function(itemName)
	-- on envoie une requête au serveur pour acheter la lampe
	local success, message = remoteBuy:InvokeServer("lamp")
	-- on affiche le message de retour du serveur 
	print(success, message)	
end)

Puis demande à créer un Script sur le serveur sous ServerScriptService afin de valider l’achat :

Renomme ton Script en storeScript:

Ecris ce code dans le script storeScript :

-- récupérer le RemoteFunction RemoteBuy
local remoteBuy = game:GetService("ReplicatedStorage"):WaitForChild("RemoteBuy")

-- écouter l'événement de requête d'achat du joueur
remoteBuy.OnServerInvoke = function(player, value)
	-- vérifier si le joueur a assez de points pour acheter l'objet
	local points = player:WaitForChild("leaderstats").Points.Value
	
	return true, " Achat confirmé !"
end

Lance ton jeu pour vérifier le bon fonctionnement de l’achat, tu trouveras le message de l’acquittement de l’achat dans la console.

Modifie le script storeScript pour vérifier si le joueur dispose d’assez de points pour l’achat et la disponibilité du stock :

-- récupérer le RemoteFunction RemoteBuy
local remoteBuy = game:GetService("ReplicatedStorage"):WaitForChild("RemoteBuy")

local ITEMS = {
	["lamp"] = { price = 5, stock = 2 },
	["run"] = { price = 10, stock = 1 },
	["visible"] = { price = 15, stock = 1 }
}

-- écouter l'événement de requête d'achat du joueur
remoteBuy.OnServerInvoke = function(player, value)
	-- vérifier si le joueur a assez de points pour acheter l'objet
	
	local item = ITEMS[value]

	-- Vérification stock
	if not item or item.stock <= 0 then
                return false, value .. " : Item indisponible "
	end

	-- Vérification points du joueur
	local points = player:WaitForChild("leaderstats").Points.Value
	if points < item.price then
		return false, "Points insuffisants (" .. points .. "/" .. item.price .. ")"
	end

	-- Transaction confirmée
	player.leaderstats.Points.Value -= item.price
	item.stock -= 1
	return true, "Achat confirmé !"
	
end

Rajoute sur l’écran de ta boutique un message pour indiquer au joueur la confirmation de son achat ou non :

Renomme ton TextLabel en MsgLabel :

Modifie le script storeScript pour afficher le message :

-- récupération du bouton pour ouvrir la boutique
local openButton = script.Parent.OpenButton
-- récupération du frame de la boutique	
local frameStore = script.Parent.FrameStore

-- on cache le frame de la boutique au lancement du jeu par defaut	
frameStore.Visible = false

-- on écoute l'événement de clic sur le bouton pour ouvrir ou fermer la boutique
openButton.MouseButton1Click:Connect(function()
	frameStore.Visible = not frameStore.Visible
end)

-- on récupère le bouton de la lampe dans la boutique
local lampButton = script.Parent.FrameStore.LampButton
-- on récupère le RemoteEvent pour acheter la lampe
local remoteBuy = game:GetService("ReplicatedStorage"):WaitForChild("RemoteBuy")

local msg = script.Parent.FrameStore.MsgLabel

-- on écoute l'événement de clic sur le bouton de la lampe pour l'acheter
lampButton.MouseButton1Click:Connect(function(itemName)
	-- on envoie une requête au serveur pour acheter la lampe
	local success, message = remoteBuy:InvokeServer("lamp")
	-- on affiche le message de retour du serveur sur l'acran de la boutique
	msg.Text = message
end)

Lance ton jeu pour vérifier si le message s’affiche correctement après un achat :


Création d’un outil

A mettre à la main du joueur et à présenter dans la boutique

Sous Workspace créer un outil Tool :

Renommer Tool en Lamp :

Maintenant, dessine une lampe en utilisant plusieurs parts que tu assembleras :

Colorise et choisis tes matériaux pour tes parts qui composera ta lampe, puis crée un Union de tes parts et renome l’objet obtenu en Handle :

Rajoute un SpotLight à ta lampe :

Modifie les propriétés de SpotLight pour orienter correctement le faisceau lumineux de ta lampe :

Lance ton jeu, le joueur doit pouvoirt attraper la lampe et vérifie que celle-ci est correctement orientée :

Je constate que la lampe n’est pas correctement orientée, je modifie le Grip de mon Tool pour l’orienter correctement :


AFFICHER L’OUTIL DANS LA BOUTIQUE

L’objectif est de présenter la lampe dans la boutique.

Dans un premier déplace ton outil Tool lampe sous ReplicatedStorage :

Puis tu vas modifier l’écran de ta boutique.

Supprimer le ImageLabel et remplace par un ViewportFrame :

Modifie le LocalScript StoreScript de la boutique :

-- récupération du bouton pour ouvrir la boutique
local openButton = script.Parent.OpenButton
-- récupération du frame de la boutique	
local frameStore = script.Parent.FrameStore

--[[
*** Rendre le viewport 3D de la lampe visible dans la boutique	
]]
local replicatedStorage = game:GetService("ReplicatedStorage")
local lamp = replicatedStorage:WaitForChild("Lamp")
local viewport = script.Parent.FrameStore.ViewportFrame
-- Nettoyage
viewport:ClearAllChildren()
-- Clone de l'objet
local clone = lamp:Clone()
clone.Parent = viewport
-- Caméra
local camera = Instance.new("Camera")
viewport.CurrentCamera = camera
camera.Parent = viewport
local center
local size
-- Calcul de la position de la caméra (adapté à la taille de l'objet)
if clone:IsA("Model") then
	size = clone:GetExtentsSize()
	center = clone:GetPivot().Position
else
	size = clone.Size
	center = clone.Position
end
-- Position de la caméra (à adapter selon la taille de l'objet)
camera.CFrame = CFrame.new(
	center + Vector3.new(size.X, size.Y, size.Z) * 0.8,
	center
)

-- on cache le frame de la boutique au lancement du jeu par defaut	
frameStore.Visible = false

-- on écoute l'événement de clic sur le bouton pour ouvrir ou fermer la boutique
openButton.MouseButton1Click:Connect(function()
	frameStore.Visible = not frameStore.Visible
end)

-- on récupère le bouton de la lampe dans la boutique
local lampButton = script.Parent.FrameStore.LampButton
-- on récupère le RemoteEvent pour acheter la lampe
local remoteBuy = game:GetService("ReplicatedStorage"):WaitForChild("RemoteBuy")

local msg = script.Parent.FrameStore.MsgLabel

-- on écoute l'événement de clic sur le bouton de la lampe pour l'acheter
lampButton.MouseButton1Click:Connect(function(itemName)
	-- on envoie une requête au serveur pour acheter la lampe
	local success, message = remoteBuy:InvokeServer("lamp")
	-- on affiche le message de retour du serveur sur l'acran de la boutique
	msg.Text = message
end)

Orienter l’image de l’outil pour une présentation optimum au joueur :


Mettre l’outil dans l’inventaire du joueur

Lors de l’achat de la lampe, mettre celle-ci dans l’inventaire.

Afin d’optimiser notre code, éviter des erreurs de saisie, tu vas centraliser tout ce que on appelle constante. Ces constantes sont des valeurs fixes et utilisées dans plusieurs parties du code.

Sous ReplicatedStorage crée un ModuleScript pour stocker toutes tes constantes :

local Constants = {}

Constants.POWER_RUN_MORE = "Run more"
Constants.LAMP = "Torch"
Constants.VISIBILITY = "Visibility"
Constants.MAX_SPEED = 50

return Constants

Modifie le LocalScript StoreScript de la boutique pour rajouter l’appel aux constantes :

local Constants = require(game.ReplicatedStorage.Constants)

-- récupération du bouton pour ouvrir la boutique
local openButton = script.Parent.OpenButton
-- récupération du frame de la boutique	
local frameStore = script.Parent.FrameStore

--[[
*** Rendre le viewport 3D de la lampe visible dans la boutique	
]]
local replicatedStorage = game:GetService("ReplicatedStorage")
local lamp = replicatedStorage:WaitForChild("Lamp")
local viewport = script.Parent.FrameStore.ViewportFrame
-- Nettoyage
viewport:ClearAllChildren()
-- Clone de l'objet
local clone = lamp:Clone()
clone.Parent = viewport
-- Caméra
local camera = Instance.new("Camera")
viewport.CurrentCamera = camera
camera.Parent = viewport
local center
local size
-- Calcul de la position de la caméra (adapté à la taille de l'objet)
if clone:IsA("Model") then
	size = clone:GetExtentsSize()
	center = clone:GetPivot().Position
else
	size = clone.Size
	center = clone.Position
end
-- Position de la caméra (à adapter selon la taille de l'objet)
camera.CFrame = CFrame.new(
	center + Vector3.new(size.X, size.Y, size.Z) * 0.8,
	center
)

-- on cache le frame de la boutique au lancement du jeu par defaut	
frameStore.Visible = false

-- on écoute l'événement de clic sur le bouton pour ouvrir ou fermer la boutique
openButton.MouseButton1Click:Connect(function()
	frameStore.Visible = not frameStore.Visible
end)

-- on récupère le bouton de la lampe dans la boutique
local lampButton = script.Parent.FrameStore.LampButton
-- on récupère le RemoteEvent pour acheter la lampe
local remoteBuy = game:GetService("ReplicatedStorage"):WaitForChild("RemoteBuy")

local msg = script.Parent.FrameStore.MsgLabel

-- on écoute l'événement de clic sur le bouton de la lampe pour l'acheter
lampButton.MouseButton1Click:Connect(function(itemName)
	-- on envoie une requête au serveur pour acheter la lampe
	local success, message = remoteBuy:InvokeServer("lamp")
	-- on affiche le message de retour du serveur sur l'acran de la boutique
	msg.Text = message
end)

Modifie le script storeScript sous ServerScriptService pour rajouter le module des constantes :

-- récupérer le module Constants
local Constants = require(game.ReplicatedStorage.Constants)

-- récupérer le RemoteFunction RemoteBuy
local remoteBuy = game:GetService("ReplicatedStorage"):WaitForChild("RemoteBuy")

-- écouter l'événement de requête d'achat du joueur
remoteBuy.OnServerInvoke = function(player, value)
	-- vérifier si le joueur a assez de points pour acheter l'objet
	local points = player:WaitForChild("leaderstats").Points.Value
	
	return true, " Achat confirmé !"
end

Modifie le script storeScript sous ServerScriptService, pour rajouter le contrôle des points, rajouter un tableau de tous les item de la boutique :

-- récupérer le module Constants
local Constants = require(game.ReplicatedStorage.Constants)
-- récupérer le RemoteFunction RemoteBuy
local remoteBuy = game:GetService("ReplicatedStorage"):WaitForChild("RemoteBuy")

local ITEMS = {
	[Constants.LAMP] = { price = 5, power = 10 , stock = 2 },
	[Constants.POWER_RUN_MORE] = { price = 10, power = 10, stock = 2 },
	[Constants.VISIBILITY] = { price = 15, power = 10, stock = 1 }
}

-- écouter l'événement de requête d'achat du joueur
remoteBuy.OnServerInvoke = function(player, value)
	-- vérifier si le joueur a assez de points pour acheter l'objet	
	local item = ITEMS[value]
	
        -- Vérification stock
	if not item or item.stock <= 0 then
		return false, value .. " : Item indisponible "
	end

	-- Vérification points du joueur
	local points = player:WaitForChild("leaderstats").Points.Value
	if points < item.price then
		return false, "Points insuffisants (" .. points .. "/" .. item.price .. ")"
	end

	-- Vérification si le joueur possède déjà l'item
	local newitem = player.leaderstats:FindFirstChild(Constants.POWER_RUN_MORE)
	if not newitem then
		newitem = Instance.new("IntValue")
		newitem.Parent = player.leaderstats
		newitem.Name = value	
	end
	newitem.Value += item.power
	
	-- Transaction confirmée
	player.leaderstats.Points.Value -= item.price
	item.stock -= 1

	return true, "Achat confirmé !"
	
end

Modifie le script pour rajouter la lampe dans l’inventaire du joueur :

local Constants = require(game.ReplicatedStorage.Constants)

-- récupération du bouton pour ouvrir la boutique
local openButton = script.Parent.OpenButton
-- récupération du frame de la boutique	
local frameStore = script.Parent.FrameStore

--[[
*** Rendre le viewport 3D de la lampe visible dans la boutique	
]]
local replicatedStorage = game:GetService("ReplicatedStorage")
local lamp = replicatedStorage:WaitForChild("Lamp")
local viewport = script.Parent.FrameStore.ViewportFrame
-- Nettoyage
viewport:ClearAllChildren()
-- Clone de l'objet
local clone = lamp:Clone()
clone.Parent = viewport
-- Caméra
local camera = Instance.new("Camera")
viewport.CurrentCamera = camera
camera.Parent = viewport
local center
local size
-- Calcul de la position de la caméra (adapté à la taille de l'objet)
if clone:IsA("Model") then
	size = clone:GetExtentsSize()
	center = clone:GetPivot().Position
else
	size = clone.Size
	center = clone.Position
end
-- Position de la caméra (à adapter selon la taille de l'objet)
camera.CFrame = CFrame.new(
	center + Vector3.new(size.X, size.Y, size.Z) * 0.8,
	center
)

-- on cache le frame de la boutique au lancement du jeu par defaut	
frameStore.Visible = false

-- on écoute l'événement de clic sur le bouton pour ouvrir ou fermer la boutique
openButton.MouseButton1Click:Connect(function()
	frameStore.Visible = not frameStore.Visible
end)

-- on récupère le bouton de la lampe dans la boutique
local lampButton = script.Parent.FrameStore.LampButton
-- on récupère le RemoteEvent pour acheter la lampe
local remoteBuy = game:GetService("ReplicatedStorage"):WaitForChild("RemoteBuy")

local msg = script.Parent.FrameStore.MsgLabel

local player = game.Players.LocalPlayer

-- on écoute l'événement de clic sur le bouton de la lampe pour l'acheter
lampButton.MouseButton1Click:Connect(function(itemName)
	if not player then return end
	if not lamp then return end
	
	-- on envoie une requête au serveur pour acheter la lampe
	local success, message = remoteBuy:InvokeServer(Constants.LAMP)
	-- on affiche le message de retour du serveur sur l'acran de la boutique
	msg.Text = message
        -- ajouter la lampe dans l'inventaire du joueur
	if success then
		local backpack = player:WaitForChild("Backpack")
		lamp.Parent = player:WaitForChild("Backpack")		
	end
end)

Courir plus vite

Modifie le localScript ScreenStore pour générer le pouvoir courir plus vite :

local Constants = require(game.ReplicatedStorage.Constants)

-- récupération du bouton pour ouvrir la boutique
local openButton = script.Parent.OpenButton
-- récupération du frame de la boutique	
local frameStore = script.Parent.FrameStore

--[[
*** Rendre le viewport 3D de la lampe visible dans la boutique	
]]
local replicatedStorage = game:GetService("ReplicatedStorage")
local lamp = replicatedStorage:WaitForChild("Lamp")
local viewport = script.Parent.FrameStore.ViewportFrame
-- Nettoyage
viewport:ClearAllChildren()
-- Clone de l'objet
local clone = lamp:Clone()
clone.Parent = viewport
-- Caméra
local camera = Instance.new("Camera")
viewport.CurrentCamera = camera
camera.Parent = viewport
local center
local size
-- Calcul de la position de la caméra (adapté à la taille de l'objet)
if clone:IsA("Model") then
	size = clone:GetExtentsSize()
	center = clone:GetPivot().Position
else
	size = clone.Size
	center = clone.Position
end
-- Position de la caméra (à adapter selon la taille de l'objet)
camera.CFrame = CFrame.new(
	center + Vector3.new(size.X, size.Y, size.Z) * 0.8,
	center
)

-- on cache le frame de la boutique au lancement du jeu par defaut	
frameStore.Visible = false

-- on écoute l'événement de clic sur le bouton pour ouvrir ou fermer la boutique
openButton.MouseButton1Click:Connect(function()
	frameStore.Visible = not frameStore.Visible
end)

-- on récupère le bouton de la lampe dans la boutique
local lampButton = script.Parent.FrameStore.LampButton
-- on récupère le RemoteEvent pour acheter la lampe
local remoteBuy = game:GetService("ReplicatedStorage"):WaitForChild("RemoteBuy")

local msg = script.Parent.FrameStore.MsgLabel

local player = game.Players.LocalPlayer

local humanoid
local leaderstats
local walkSpeed
local MAXSPEED = Constants.MAX_SPEED or 50
local function setup(character)
	humanoid = character:WaitForChild("Humanoid")
	leaderstats = player:FindFirstChild("leaderstats")
	walkSpeed = humanoid.WalkSpeed
end

player.CharacterAdded:Connect(setup)

if player and player.Character then
	setup(player.Character)
end
-- on écoute l'événement de clic sur le bouton de la lampe pour l'acheter
lampButton.MouseButton1Click:Connect(function(itemName)
	if not player then return end
	if not lamp then return end
	
	-- on envoie une requête au serveur pour acheter la lampe
	local success, message = remoteBuy:InvokeServer(Constants.LAMP)
	-- on affiche le message de retour du serveur sur l'acran de la boutique
	msg.Text = message
        -- ajouter la lampe dans l'inventaire du joueur
	if success then
		local backpack = player:WaitForChild("Backpack")
		lamp.Parent = player:WaitForChild("Backpack")		
	end
end)

-- on récupère le bouton pour le pouvoir de courir plus vite
runButton.MouseButton1Click:Connect(function(itemName)
	if not player then return end
	if not leaderstats then return end
	-- on envoie une requête au serveur pour le pouvoir courir plus vite
	local success, message = remoteBuy:InvokeServer(Constants.POWER_RUN_MORE)
	-- on affiche le message de retour du serveur sur l'acran de la boutique
	msg.Text = message
end)

Vérifie que tu disposes bien du pouvoir de courir plus vite :


Ajouter le pouvoir de courir plus vite


Sur la boutique programmer le bouton « Courrir plus vite »

Objectif :

✔ programmer le bouton de la boutique « courir plus vite »
✔ envoyer une demande au serveur pour acheter le pouvoir
✔ recevoir une réponse succès ou pas
✔ activer un pouvoir avec le clavier par la touche R

Le principe du pouvoir :

Le joueur peut :

  • acheter un pouvoir “courir plus vite”
  • appuyer sur R pour l’activer
  • utiliser ce pouvoir un certain nombre de fois

Programme le clic sur le bouton « courir plus vite » :

Modifie le localScript StoreScript et ajoute en fin du script :

-- on récupère le bouton pour le pouvoir de courir plus vite
runButton.MouseButton1Click:Connect(function(itemName)
	if not player then return end
	if not leaderstats then return end
	-- on envoie une requête au serveur pour le pouvoir courir plus vite
	local success, message = remoteBuy:InvokeServer(Constants.POWER_RUN_MORE)
	-- on affiche le message de retour du serveur sur l'acran de la boutique
	msg.Text = message
end)

Programme pour que le joueur court plus vite sur l’appui de la touche R :

Modifie le localScript StoreScript et ajoute en fin du script :

--[[
	Traitement des touches du clavier pour activer/désactiver les pouvoirs du joueur
]]
-- Connecte l'événement du clavier
local UserInputService = game:GetService("UserInputService")
local runMore = false
-- Récupère l'évènement du clavier
UserInputService.InputBegan:Connect(function(input, gameProcessed)
	if not player then return end
	if not humanoid then return end
	-- Vérifie si l'évènement est un clavier
	if input.KeyCode ~= Enum.KeyCode.Unknown then
		-- Ferme l'écran de la boutique
		frameStore.Visible = false
		-- Vérifie si la touche appuyée est R
		if input.KeyCode == Enum.KeyCode.R and not gameProcessed then
			if runMore then
				runMore = false
				humanoid.WalkSpeed = walkSpeed
			else
				-- Vérifie si le leaderstats existe et le pouvoir de courir plus vite
				if leaderstats and leaderstats:FindFirstChild(Constants.POWER_RUN_MORE) then
					local run = leaderstats:FindFirstChild(Constants.POWER_RUN_MORE)
					-- si le pouvoir est actif
					if run.Value > 0 then

						humanoid.WalkSpeed = MAXSPEED
						run.Value -= 1
						runMore	= true
					end
				end
			end
		end

	end

end)

A chaque utilisation de la touche R pour courir plus vite, le pouvoir diminue :


Fermeture automatique de la boutique

Fermer automatiquement la boutique en cliquant en dehors de la fen,être de la boutique ou en utilisant une touche du clavier.

Crée un TextButton dans un Frame qui couvre tout l’écran :

Renomme le TextButton en Background.

Donne une priorité à la fenêtre de la boutique et aux boutons de la boutique :

Modifie le localScript StoreScript :

-- on récupère le background de l'interface de la boutique
local background = script.Parent.FrameBackground.Background
--[[
	Affichage ou non de la boutique sur l'appui de l'icone de la boutique 
]]
-- on cache le frame de la boutique au lancement du jeu par defaut	
frameStore.Visible = false
background.Interactable = false

-- on écoute l'événement de clic sur le bouton pour ouvrir ou fermer la boutique
openButton.MouseButton1Click:Connect(function()
	frameStore.Visible = not frameStore.Visible
	background.Interactable = frameStore.Visible
	frameStore.MsgLabel.Text = "Choisis ton option"
end)

background.MouseButton1Click:Connect(function()
	frameStore.Visible = false
	-- rendre non cliquable
	background.Interactable = frameStore.Visible
end)

et lors de l’appui d’une touche :

--[[
	Traitement des touches du clavier pour activer/désactiver les pouvoirs du joueur
]]
-- Connecte l'événement du clavier
local UserInputService = game:GetService("UserInputService")
local runMore = false
-- Récupère l'évènement du clavier
UserInputService.InputBegan:Connect(function(input, gameProcessed)
	if not player then return end
	if not humanoid then return end
	-- Vérifie si l'évènement est un clavier
	if input.KeyCode ~= Enum.KeyCode.Unknown then
		-- Ferme l'écran de la boutique
		frameStore.Visible = false
		background.Interactable = frameStore.Visible	
		-- Vérifie si la touche appuyée est R
		if input.KeyCode == Enum.KeyCode.R and not gameProcessed then
			if runMore then
				runMore = false
				humanoid.WalkSpeed = walkSpeed
			else
				-- Vérifie si le leaderstats existe et le pouvoir de courir plus vite
				if leaderstats and leaderstats:FindFirstChild(Constants.POWER_RUN_MORE) then
					local run = leaderstats:FindFirstChild(Constants.POWER_RUN_MORE)
					-- si le pouvoir est actif
					if run.Value > 0 then

						humanoid.WalkSpeed = MAXSPEED
						run.Value -= 1
						runMore	= true
					end
				end
			end
		end

	end

end)
Catégories
Construct 3

Construct 3 Flappy Bird

Réalisation d’un jeu 2D pour comprendre le fonctionnement de Construct 3

Partie 01 mise en place des éléments graphiques

Partie 02 Animer les éléments graphiques

Partie 03 Animer les éléments graphiques suite

Partie 04 Programmer les règles du jeu