Menu
CoddyTech

Piles

Leçon 17 sur 25 du cours Jeu Roblox : Échappe-toi des ténèbres de Coddy.

Une lampe torche qui brille éternellement facilite les choses dans l’obscurité. Elle possède désormais une Battery, un attribut de l’outil compris entre 0 et Settings.BatteryMax, qui diminue de Settings.BatteryDrain par seconde lorsque le faisceau est allumé. Une fois vide, le faisceau s’éteint et ne se rallume pas.

Une boucle sur le serveur décharge chaque lampe torche tenue et allumée, quatre fois par seconde, de sorte que chaque passage consomme l’équivalent d’un quart de seconde :

local battery = math.max(0, flashlight:GetAttribute("Battery") - Settings.BatteryDrain * 0.25)

La batterie est un attribut, donc le LocalScript du joueur peut le lire et l’afficher sur le HUD. math.floor supprime les décimales : 87.5 s’affiche comme 87.

challenge icon

Défi

Difficile

La lampe torche fonctionne avec une batterie.

  1. Définis l’attribut Battery de chaque nouvelle lampe torche sur Settings.BatteryMax ; un clic ne l’allume que s’il reste de la batterie.
  2. Une boucle task.spawn : toutes les 0.25 secondes, retire Settings.BatteryDrain * 0.25 à chaque lampe torche portée et allumée ; lorsque la batterie atteint 0, le faisceau s’éteint.
  3. Dans RunService.Heartbeat de DarkClient, affiche Battery 76% dans hud.Battery (avec math.floor).

Appuie sur « Jouer » et allume-la : le HUD affiche le compte à rebours. Puis appuie sur « Vérifier ».

Essayez vous-même

local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Lighting = game:GetService("Lighting")
local TweenService = game:GetService("TweenService")
local PathfindingService = game:GetService("PathfindingService")
local Settings = require(game.ReplicatedStorage.Settings)

local rooms = workspace.Rooms
local lastRoom = rooms.Lobby -- la pièce la plus récente
local count = 0 -- pièces créées jusqu’à présent
local monster = nil

-- La nuit tombe : plus de soleil, un peu de lumière partout et du brouillard
Lighting.ClockTime = Settings.ClockTime
Lighting.Ambient = Settings.Ambient
Lighting.OutdoorAmbient = Settings.Ambient
Lighting.FogColor = Color3.new(0, 0, 0)
Lighting.FogEnd = Settings.FogEnd
print("Lights out")

-- Un outil que porte un joueur : dans sa main ou dans son Backpack
local function findTool(player, name)
	return (player.Character and player.Character:FindFirstChild(name)) or player.Backpack:FindFirstChild(name)
end

-- Leçon 17 : Batteries va ici

-- Ouvrir une porte en la faisant pivoter sur ses gonds
local function openDoor(door)
	door:SetAttribute("Open", true)
	door.Panel.Prompt.Enabled = false
	local hinge, panel = door.Hinge, door.Panel
	local goal = hinge.CFrame * CFrame.Angles(0, math.rad(90), 0) * hinge.CFrame:ToObjectSpace(panel.CFrame)
	TweenService:Create(panel, TweenInfo.new(0.6), { CFrame = goal }):Play()
end

-- Le joueur vivant le plus proche que le monstre pourrait attraper (non caché), à portée de MonsterSight, et sa distance
local function nearestCharacter(position)
	local best, bestDistance = nil, Settings.MonsterSight
	for _, player in Players:GetPlayers() do
		local character = player.Character
		local root = character and character:FindFirstChild("HumanoidRootPart")
		if root and character.Humanoid.Health > 0 and not character:GetAttribute("Hidden") then
			local distance = (root.Position - position).Magnitude
			if distance < bestDistance then
				best, bestDistance = character, distance
			end
		end
	end
	return best, bestDistance
end

-- Le monstre peut-il voir ce personnage ? Un rayon partant de sa tête et atteignant le personnage
local function canSee(shadow, character)
	local from = shadow.Head.Position
	local params = RaycastParams.new()
	params.FilterDescendantsInstances = { shadow }
	local hit = workspace:Raycast(from, character.HumanoidRootPart.Position - from, params)
	return hit ~= nil and hit.Instance:IsDescendantOf(character)
end

-- Avancer vers un point : tout droit si la voie est libre, sinon en suivant un chemin
local function walkToward(shadow, goal, path)
	local root = shadow.HumanoidRootPart
	local params = RaycastParams.new()
	params.FilterDescendantsInstances = { shadow }
	local hit = workspace:Raycast(root.Position, goal - root.Position, params)
	if not hit or hit.Instance.Parent:FindFirstChildOfClass("Humanoid") then
		shadow.Humanoid:MoveTo(goal)
		return
	end
	path:ComputeAsync(root.Position, goal)
	local points = path:GetWaypoints()
	if path.Status == Enum.PathStatus.Success and points[2] then
		shadow.Humanoid:MoveTo(points[2].Position)
	end
end

-- Le milieu d’une pièce située `back` pièces avant celle où se trouvent les joueurs (celle
-- qui précède la plus récente), pour la patrouille du monstre
local function patrolPoint(back)
	for _, room in rooms:GetChildren() do
		if room:GetAttribute("Number") == count - 1 - back then
			return (room.Entrance.CFrame * CFrame.new(0, 3.1, -12)).Position
		end
	end
	return nil
end

-- Le comportement du monstre : toutes les quarts de seconde, à la poursuite de quelqu’un
local function runMonster(shadow)
	local humanoid = shadow.Humanoid
	local root = shadow.HumanoidRootPart
	local path = PathfindingService:CreatePath()
	local lastSeen = nil -- là où il a vu quelqu’un pour la dernière fois
	local back = 0 -- sa patrouille : 0 correspond à la salle des joueurs, 2 à celle située deux salles derrière
	while shadow.Parent do
		local character = nearestCharacter(root.Position)
		if character and canSee(shadow, character) then
			lastSeen = character.HumanoidRootPart.Position
		else
			character = nil
		end
		if character and (character.HumanoidRootPart.Position - root.Position).Magnitude <= Settings.CatchDistance then
			-- attrapé : le joueur est vaincu, et le monstre disparaît jusqu’à la prochaine porte
			character.Humanoid.Health = 0
			shadow:Destroy()
			monster = nil
			return
		end
		local goal = character and character.HumanoidRootPart.Position or lastSeen
		if goal then
			walkToward(shadow, goal, path)
			-- à l’endroit où il vous a vu pour la dernière fois, si vous n’y êtes pas : il abandonne
			if not character and ((goal - root.Position) * Vector3.new(1, 0, 1)).Magnitude < 1.5 then
				lastSeen = nil
			end
		else
			-- personne en vue : il patrouille en faisant des allers-retours
			local point = patrolPoint(back) or patrolPoint(0)
			if point then
				walkToward(shadow, point, path)
				if ((point - root.Position) * Vector3.new(1, 0, 1)).Magnitude < 2 then
					back = back == 0 and 2 or 0
				end
			end
		end
		task.wait(0.25)
	end
end

-- Le monstre arrive derrière vous : à l’entrée de la salle située deux salles avant la plus récente
local function spawnMonster()
	local behind = nil
	for _, room in rooms:GetChildren() do
		if room:GetAttribute("Number") == count - 2 then
			behind = room
		end
	end
	if not behind then
		return
	end
	monster = ServerStorage.Shadow:Clone()
	monster:PivotTo(behind.Entrance.CFrame + Vector3.new(0, 3.1, 0))
	monster.Humanoid.WalkSpeed = Settings.MonsterSpeed
	monster.Parent = workspace
	task.spawn(runMonster, monster)
end

local addRoom -- défini plus bas : une porte ajoute une salle, et chaque salle a une porte

-- Ce qui se passe lorsqu’un joueur ouvre une porte
local function setupDoor(door)
	door.Panel.Prompt.Triggered:Connect(function(player)
		if door:GetAttribute("Open") then
			return
		end
		if door:GetAttribute("Locked") then
			local key = findTool(player, "Key")
			if not key then
				return
			end
			key:Destroy()
			door:SetAttribute("Locked", nil)
		end
		if door.Parent == lastRoom then
			addRoom()
		end
		openDoor(door)
	end)
end
setupDoor(workspace.Rooms.Lobby.Door)

-- Entrer dans une salle : elle devient la Room du joueur et son point de réapparition
local function setupEntry(room)
	room.Entry.Touched:Connect(function(hit)
		local player = Players:GetPlayerFromCharacter(hit.Parent)
		if player then
			local number = room:GetAttribute("Number")
			player.leaderstats.Room.Value = number
			player.RespawnLocation = room.Checkpoint
		end
	end)
end
setupEntry(workspace.Rooms.Lobby)

-- Un placard : E pour s’y cacher, E à nouveau pour en sortir
local function setupCloset(closet)
	closet.Front.Prompt.Triggered:Connect(function(player)
		local character = player.Character
		if character:GetAttribute("Hidden") then
			character:PivotTo(closet.Front.CFrame * CFrame.new(-3, -1, 0) * CFrame.Angles(0, math.rad(90), 0))
			character:SetAttribute("Hidden", nil)
		else
			character:PivotTo(closet.Inside.CFrame + Vector3.new(0, 2.5, 0))
			character:SetAttribute("Hidden", true)
		end
	end)
end

-- Une nouvelle salle à la sortie de la plus récente
function addRoom()
	count += 1
	local name = Settings.Order[(count - 1) % #Settings.Order + 1]
	if count >= Settings.RoomsToEscape then
		name = "Exit"
	end
	local room = ServerStorage.Rooms[name]:Clone()
	room:PivotTo(lastRoom.Exit.CFrame)
	room:SetAttribute("Number", count)
	room.Sign.SurfaceGui.Number.Text = "Room " .. count
	room.Parent = rooms
	lastRoom = room
	setupDoor(room.Door)
	setupEntry(room)
	-- une salle verrouillée contient sa clé
	local keySpot = room:FindFirstChild("KeySpot")
	if keySpot then
		local key = ServerStorage.Key:Clone()
		key.Handle.CFrame = keySpot.CFrame
		key.Parent = room
		key.Handle.Prompt.Triggered:Connect(function(player)
			key.Handle.Prompt.Enabled = false
			key.Parent = player.Backpack
		end)
	end
	local closet = room:FindFirstChild("Closet")
	if closet then
		setupCloset(closet)
	end
	-- garder la salle la plus récente et KeepRooms salles derrière elle
	for _, old in rooms:GetChildren() do
		local number = old:GetAttribute("Number")
		if number < count - Settings.KeepRooms then
			print("Room " .. number .. " is gone")
			old:Destroy()
		end
	end
	if count >= Settings.MonsterFrom and not monster then
		spawnMonster()
	end
	return room
end

-- Une fois sorti du placard, vous n’êtes plus caché
task.spawn(function()
	while true do
		task.wait(0.25)
		for _, player in Players:GetPlayers() do
			local character = player.Character
			if character and character:GetAttribute("Hidden") then
				local closest = math.huge
				for _, inside in workspace.Rooms:GetDescendants() do
					if inside.Name == "Inside" then
						closest = math.min(closest, (inside.Position - character.HumanoidRootPart.Position).Magnitude)
					end
				end
				if closest > 4 then
					character:SetAttribute("Hidden", nil)
				end
			end
		end
	end
end)

-- Chaque joueur : Room dans le classement et une lampe de poche à chaque vie
Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local room = Instance.new("IntValue")
	room.Name = "Room"
	room.Parent = leaderstats

	player.CharacterAdded:Connect(function()
		local flashlight = ServerStorage.Flashlight:Clone()
		flashlight.Parent = player.Backpack
		local beam = flashlight.Handle.Beam
		flashlight.Activated:Connect(function()
			beam.Enabled = not beam.Enabled
		end)
	end)
end)

Toutes les leçons de Jeu Roblox : Échappe-toi des ténèbres

Entraînez-vous par vous-même : Playground Roblox