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	
		-- 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 continue end
		-- Assez proche : arrêt
		if data.stopDistance > 0 and distance <= data.stopDistance then
			enemy.AssemblyLinearVelocity = Vector3.zero
			continue
		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
			continue
		end
		-- Calcul de la direction vers le joueur
		local direction = (root.Position - enemy.Position).Unit
		
		-- 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 :

L’ennemi est une boule

Ajoute dans le code pour un nouveau comportement pour faire rouler le boule :

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
		enemy.AssemblyLinearVelocity = Vector3.zero
		enemy.AssemblyAngularVelocity = Vector3.zero
		humanoid:TakeDamage((enemiesData[enemy].damage))
		-- 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 continue 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 continue end
		-- Assez proche : arrêt
		if data.stopDistance > 0 and distance <= data.stopDistance then
			enemy.AssemblyLinearVelocity = Vector3.zero
			continue
		end
		-- Calcul de la direction vers le joueur
		
		if enemy.Shape == Enum.PartType.Ball then
			enemy.Anchored = false
			-- Direction vers le joueur (ignorant Y pour rester au sol)
			local direction = Vector3.new(
				root.Position.X - enemy.Position.X,
				0,
				root.Position.Z - enemy.Position.Z
			).Unit

			-- Force appliquée vers le joueur
			local mass      = enemy.AssemblyMass
			local targetVelocity = direction * data.speed
			local correction     = targetVelocity - Vector3.new(
				enemy.AssemblyLinearVelocity.X,
				0,
				enemy.AssemblyLinearVelocity.Z
			)
			enemy:ApplyImpulse(correction * mass)

			-- Rotation de roulement
			local rollAxis  = Vector3.new(-direction.Y, 0, -direction.X)
			local rollSpeed = data.speed / (enemy.Size.Y / 2)
			if enemy.Size.X > enemy.Size.Y then
				rollSpeed = data.speed / (enemy.Size.X / 2)
			end
			enemy.AssemblyAngularVelocity = rollAxis * rollSpeed
		else
			local direction = (root.Position - enemy.Position).Unit

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

Des ennemis qui ne traversent pas les murs (Raycast) :

-- recherche des services
local RunService = game:GetService("RunService")
local Players    = game:GetService("Players")
local Debris     = game:GetService("Debris")

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

-- recherche des ennemis dans le workspace sous le folder "Monstre"
local enemies          = workspace.GAME:WaitForChild("Monstre")

-- Paramètres dynamiques par ennemi
local enemiesData = {}

-- Initialisation de tous les ennemis
for _, enemy in enemies:GetChildren() do

	-- contrôle si c'est une part qui représente un ennemi
	if not enemy:IsA("BasePart") then continue end

	enemy.Anchored = true

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

	-- dégats sur le joueur si il touche l'ennemi'
	enemy.Touched:Connect(function(hit)
		local character = hit.Parent
		local humanoid  = character and character:FindFirstChildOfClass("Humanoid")
		if not humanoid or humanoid.Health <= 0 then return end
		if enemiesData[enemy].cooldown then return end
		enemiesData[enemy].cooldown = true
		humanoid:TakeDamage(10)
		task.delay(0.5, function() enemiesData[enemy].cooldown = false end)
	end)
end

--[[ Configuration du Raycast (exclure les ennemis eux-mêmes et le personnage)
     Pour éviter que les parts traversent les murs, il faut faire un Raycast entre la part et le joueur. 
     Si le rayon touche un mur avant le joueur on stoppe la part.
]]

local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

RunService.Heartbeat:Connect(function(deltaTime)
	local player = Players:GetPlayers()[1]
	if not player then return end
	local character = player.Character
	if not character then return end
	local root = character:FindFirstChild("HumanoidRootPart")
	if not root then return end

	-- Met à jour les exclusions à chaque frame (Player + tous les ennemis exclus)
	local excluded = { character }
	for enemy in pairs(enemiesData) do
		if enemy and enemy.Parent then
			table.insert(excluded, enemy)
		end
	end
	raycastParams.FilterDescendantsInstances = excluded

	for enemy, data in pairs(enemiesData) do
		if not enemy or not enemy.Parent then
			enemiesData[enemy] = nil
			continue
		end
		
		-- Calcul de la direction et de la distance entre l'ennemi et le joueur
		local toPlayer    = root.Position - enemy.Position
		local distance    = toPlayer.Magnitude
		local direction   = toPlayer.Unit

		-- Trop loin : la part attend
		if distance > data.detectionDistance then
			enemy.AssemblyLinearVelocity = Vector3.zero
			continue
		end

		-- Assez proche : arrêt
		if data.stopDistance > 0 and distance <= data.stopDistance then
			enemy.AssemblyLinearVelocity = Vector3.zero
			continue
		end

		-- Raycast entre l'ennemi et le joueur
		local rayResult = workspace:Raycast(enemy.Position, direction * distance, raycastParams)

		if rayResult then
			-- Un mur est détecté avant le joueur : on stoppe
			enemy.AssemblyLinearVelocity = Vector3.zero
			-- Optionnel : orienter quand même vers le joueur
			enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position)
		else
			-- Chemin libre : déplacement normal
			enemy.AssemblyLinearVelocity = direction * data.speed
			enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position) 
				+ direction * data.speed * deltaTime
		end
	end
end)

Les ennemies sont des RIG

Ajoute des rig sous une structure suivante :

Pour ajouter un RIG choisis :

Ajoute sous chaque Rig un script que tu renommes Animate, ce script anime le Rig pour qu’il marche ou qu’il attende en fonction de la situation :

-- recherche des services
local RunService = game:GetService("RunService")
local Players    = game:GetService("Players")
local Debris     = game:GetService("Debris")

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

-- recherche des ennemis dans le workspace sous le folder "Monstre"
local enemies          = workspace.GAME:WaitForChild("Monstre")

-- Paramètres dynamiques par ennemi
local enemiesData = {}

-- Initialisation de tous les ennemis
for _, enemy in enemies:GetChildren() do

	-- contrôle si c'est une part qui représente un ennemi
	if not enemy:IsA("BasePart") then continue end

	enemy.Anchored = true

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

	-- dégats sur le joueur si il touche l'ennemi'
	enemy.Touched:Connect(function(hit)
		local character = hit.Parent
		local humanoid  = character and character:FindFirstChildOfClass("Humanoid")
		if not humanoid or humanoid.Health <= 0 then return end
		if enemiesData[enemy].cooldown then return end
		enemiesData[enemy].cooldown = true
		character:PivotTo(CFrame.new(-10, 1.5, -69))
		task.delay(0.5, function() enemiesData[enemy].cooldown = false end)
	end)
end

--[[ Configuration du Raycast (exclure les ennemis eux-mêmes et le personnage)
     Pour éviter que les parts traversent les murs, il faut faire un Raycast entre la part et le joueur. 
     Si le rayon touche un mur avant le joueur on stoppe la part.
]]

local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

RunService.Heartbeat:Connect(function(deltaTime)
	local player = Players:GetPlayers()[1]
	if not player then return end
	local character = player.Character
	if not character then return end
	local root = character:FindFirstChild("HumanoidRootPart")
	if not root then return end

	-- Met à jour les exclusions à chaque frame (Player + tous les ennemis exclus)
	local excluded = { character }
	for enemy in pairs(enemiesData) do
		if enemy and enemy.Parent then
			table.insert(excluded, enemy)
		end
	end
	raycastParams.FilterDescendantsInstances = excluded

	for enemy, data in pairs(enemiesData) do
		if not enemy or not enemy.Parent then
			enemiesData[enemy] = nil
			continue
		end
		
		-- Calcul de la direction et de la distance entre l'ennemi et le joueur
		local toPlayer    = root.Position - enemy.Position
		local distance    = toPlayer.Magnitude
		local direction   = toPlayer.Unit

		-- Trop loin : la part attend
		if distance > data.detectionDistance then
			enemy.AssemblyLinearVelocity = Vector3.zero
			continue
		end

		-- Assez proche : arrêt
		if data.stopDistance > 0 and distance <= data.stopDistance then
			enemy.AssemblyLinearVelocity = Vector3.zero
			continue
		end

		-- Raycast entre l'ennemi et le joueur
		local rayResult = workspace:Raycast(enemy.Position, direction * distance, raycastParams)

		if rayResult then
			-- Un mur est détecté avant le joueur : on stoppe
			enemy.AssemblyLinearVelocity = Vector3.zero
			-- Optionnel : orienter quand même vers le joueur
			enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position)
		else
			-- Chemin libre : déplacement normal
			enemy.AssemblyLinearVelocity = direction * data.speed
			enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position) 
				+ direction * data.speed * deltaTime
		end
	end
end)

Sous ton folder principal, créer un script et ajoute ce code :

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

-- Configuration
local DETECTION_DISTANCE = 60
local STOP_DISTANCE      = 3
local SPEED              = 10
local UPDATE_INTERVAL    = 0.5  -- réduit pour un mouvement plus fluide
local DAMAGE             = 5
local RESET_DELAY        = 0.5

local rigsFolder    = script.Parent
local enemieRigData = {}

-- Crée une hitbox soudée sur le HumanoidRootPart
local function createHitbox(rig)
	local root = rig:FindFirstChild("HumanoidRootPart")
	if not root then return nil end

	local hitbox             = Instance.new("Part")
	hitbox.Name              = "Hitbox_" .. rig.Name
	hitbox.Shape             = Enum.PartType.Ball
	hitbox.Size              = Vector3.new(5, 5, 5)
	hitbox.Transparency      = 0.8
	hitbox.CanCollide        = false
	hitbox.CanTouch          = true
	hitbox.Anchored          = false
	hitbox.CastShadow        = false
	hitbox.Massless          = true
	hitbox.Color             = Color3.fromRGB(255, 0, 0)
	hitbox.Material          = Enum.Material.Neon
	hitbox.CFrame            = root.CFrame * CFrame.new(0, 1, -0.5)
	hitbox.Parent            = workspace

	local weld       = Instance.new("WeldConstraint")
	weld.Part0       = root
	weld.Part1       = hitbox
	weld.Parent      = hitbox

	-- Nettoyage automatique si le rig est détruit
	rig.AncestryChanged:Connect(function()
		if not rig.Parent then
			hitbox:Destroy()
			enemieRigData[rig] = nil
		end
	end)

	return hitbox
end

-- Arrête le rig
local function stopRig(data)
	if not data.isMoving then return end
	data.humanoid.WalkSpeed = 0
	data.isMoving           = false
end

-- Déplace le rig vers une cible
local function chaseTarget(data, targetPosition)
	local now = time()
	if now - data.lastUpdate < data.updateInterval then return end
	data.lastUpdate = now

	data.humanoid.WalkSpeed = data.speed
	data.humanoid:MoveTo(targetPosition)
	data.isMoving = true
end

-- Initialisation des rigs
for _, rig in ipairs(rigsFolder:GetChildren()) do
	if not rig:IsA("Model") then continue end

	local humanoid = rig:FindFirstChildOfClass("Humanoid")
	local rootPart = rig:FindFirstChild("HumanoidRootPart")
	if not humanoid or not rootPart then continue end

	local hitbox = createHitbox(rig)
	if not hitbox then continue end

	enemieRigData[rig] = {
		detectionDistance = rig:GetAttribute("DetectionDistance") or DETECTION_DISTANCE,
		speed             = rig:GetAttribute("Speed")             or SPEED,
		stopDistance      = rig:GetAttribute("StopDistance")      or STOP_DISTANCE,
		updateInterval    = rig:GetAttribute("UpdateInterval")    or UPDATE_INTERVAL,
		damage            = rig:GetAttribute("Damage")            or DAMAGE,
		hitbox            = hitbox,
		humanoid          = humanoid,
		rootPart          = rootPart,
		lastUpdate        = 0,
		isMoving          = false,
		lastHit           = 0,
	}

	-- Détection de collision avec le joueur
	hitbox.Touched:Connect(function(hit)
		local data = enemieRigData[rig]
		if not data then return end

		-- Vérifie que c'est bien le joueur (pas une autre hitbox)
		local character = hit:FindFirstAncestorOfClass("Model")
		if not character then return end

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

		-- Cooldown pour éviter les dégâts multiples
		local now = time()
		if now - data.lastHit < RESET_DELAY then return end
		data.lastHit = now

		humanoidTarget:TakeDamage(data.damage)
		print(rig.Name, "inflige", data.damage, "dégâts à", player.Name)
	end)
end

-- Heartbeat : déplacement de tous les rigs
RunService.Heartbeat:Connect(function()
	local player = Players:GetPlayers()[1]
	if not player then return end

	local character = player.Character
	if not character then return end

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

	for rig, data in pairs(enemieRigData) do
		if not rig.Parent then continue end

		local distance = (root.Position - data.rootPart.Position).Magnitude

		if distance > data.detectionDistance or distance <= data.stopDistance then
			stopRig(data)
			continue  
		end

		chaseTarget(data, root.Position)
	end
end)

Les Rig disposent d’une Hitbox pour gérer la collision avec le joueur, pour rendre complétement transparente cette Hitbox, modifie sa Tranparency :

-- Crée une hitbox soudée sur le HumanoidRootPart
local function createHitbox(rig)
	local root = rig:FindFirstChild("HumanoidRootPart")
	if not root then return nil end

	local hitbox             = Instance.new("Part")
	hitbox.Name              = "Hitbox_" .. rig.Name
	hitbox.Shape             = Enum.PartType.Ball
	hitbox.Size              = Vector3.new(5, 5, 5)
	hitbox.Transparency      = 1

Exemple d’un code avec des ennemis qui ne peuvent pas traverser un mur, utilisation de Raycast :

-- recherche des services
local RunService = game:GetService("RunService")
local Players    = game:GetService("Players")
local Debris     = game:GetService("Debris")

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

-- recherche des ennemis dans le workspace sous le folder "Monstre"
local enemies          = workspace.GAME:WaitForChild("Monstre")

-- Paramètres dynamiques par ennemi
local enemiesData = {}

-- Initialisation de tous les ennemis
for _, enemy in enemies:GetChildren() do

	-- contrôle si c'est une part qui représente un ennemi
	if not enemy:IsA("BasePart") then continue end

	enemy.Anchored = true

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

	-- dégats sur le joueur si il touche l'ennemi'
	enemy.Touched:Connect(function(hit)
		local character = hit.Parent
		local humanoid  = character and character:FindFirstChildOfClass("Humanoid")
		if not humanoid or humanoid.Health <= 0 then return end
		if enemiesData[enemy].cooldown then return end
		enemiesData[enemy].cooldown = true
		humanoid:TakeDamage(10)
		task.delay(0.5, function() enemiesData[enemy].cooldown = false end)
	end)
end

--[[ Configuration du Raycast (exclure les ennemis eux-mêmes et le personnage)
     Pour éviter que les parts traversent les murs, il faut faire un Raycast entre la part et le joueur. 
     Si le rayon touche un mur avant le joueur on stoppe la part.
]]

local raycastParams = RaycastParams.new()
raycastParams.FilterType = Enum.RaycastFilterType.Exclude

RunService.Heartbeat:Connect(function(deltaTime)
	local player = Players:GetPlayers()[1]
	if not player then return end
	local character = player.Character
	if not character then return end
	local root = character:FindFirstChild("HumanoidRootPart")
	if not root then return end

	-- Met à jour les exclusions à chaque frame (Player + tous les ennemis exclus)
	local excluded = { character }
	for enemy in pairs(enemiesData) do
		if enemy and enemy.Parent then
			table.insert(excluded, enemy)
		end
	end
	raycastParams.FilterDescendantsInstances = excluded

	for enemy, data in pairs(enemiesData) do
		if not enemy or not enemy.Parent then
			enemiesData[enemy] = nil
			continue
		end
		
		-- Calcul de la direction et de la distance entre l'ennemi et le joueur
		local toPlayer    = root.Position - enemy.Position
		local distance    = toPlayer.Magnitude
		local direction   = toPlayer.Unit

		-- Trop loin : la part attend
		if distance > data.detectionDistance then
			enemy.AssemblyLinearVelocity = Vector3.zero
			continue
		end

		-- Assez proche : arrêt
		if data.stopDistance > 0 and distance <= data.stopDistance then
			enemy.AssemblyLinearVelocity = Vector3.zero
			continue
		end

		-- Raycast entre l'ennemi et le joueur
		local rayResult = workspace:Raycast(enemy.Position, direction * distance, raycastParams)

		if rayResult then
			-- Un mur est détecté avant le joueur : on stoppe
			enemy.AssemblyLinearVelocity = Vector3.zero
			-- Optionnel : orienter quand même vers le joueur
			enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position)
		else
			-- Chemin libre : déplacement normal
			enemy.AssemblyLinearVelocity = direction * data.speed
			enemy.CFrame = CFrame.lookAt(enemy.Position, root.Position) 
				+ direction * data.speed * deltaTime
		end
	end
end)
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    = 10   -- secondes avant respawn
local FADE_SPEED       = 0.05 -- vitesse de disparition
local COIN_LEADERBOARD = "Coins" --leaderstats

-- 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
	coin.Transparency = 0
	coin.CanTouch   = true
	coin.CanCollide   = false

	-- 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(coin.Size.X, coin.Size.Y, coin.Size.Z)
	hitbox.Transparency = 0.5
	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
	particule.Speed            = NumberRange.new(20, 60)
	particule.Rate			   = 40
	particule.ShapeInOut	   = Enum.ParticleEmitterShapeInOut.InAndOut

	-- 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, coin.Size.Y/2 + 1, 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.CanTouch 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    = 10   -- secondes avant respawn
local FADE_SPEED       = 0.05 -- vitesse de disparition
local COIN_LEADERBOARD = "Coins" --leaderstats

-- 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
	coin.Transparency = 0
	coin.CanTouch   = true
	coin.CanCollide   = false

	-- 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(coin.Size.X, coin.Size.Y, coin.Size.Z)
	hitbox.Transparency = 0.5
	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
	particule.Speed            = NumberRange.new(20, 60)
	particule.Rate			   = 40
	particule.ShapeInOut	   = Enum.ParticleEmitterShapeInOut.InAndOut

	-- 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, coin.Size.Y/2 + 1, 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.CanTouch   = 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.CanTouch 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.CanTouch   = 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)
			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.CanTouch 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
PAGES MEMBRES

Protégé : CLEMENT PASSARD

Ce contenu est protégé par un mot de passe. Pour le voir, veuillez saisir votre mot de passe ci-dessous :

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