La meilleure pièce
Leçon 20 sur 20 du cours Jeu Roblox : Échapper aux ténèbres de Coddy.
La dernière fonctionnalité : la pièce la plus profonde qu’un joueur ait jamais atteinte, Best dans le classement, conservée entre les visites dans un DataStore. Lisez cette valeur à son arrivée, augmentez-la lorsqu’il entre dans une pièce plus profonde, puis enregistrez-la lorsqu’il part et à la fermeture du serveur.
GetAsync attend le magasin. Une fonction qui attend à l’intérieur de PlayerAdded laisse le temps au premier personnage du joueur d’apparaître avant l’exécution de tout ce qui suit ; la connexion à CharacterAdded vient donc en premier et la lecture en dernier :
local ok, saved = pcall(function()
return bestStore:GetAsync(tostring(player.UserId))
end)Les magasins de données peuvent échouer, donc chaque appel est enveloppé dans pcall. Dans Roblox Studio, les DataStores fonctionnent une fois que l’expérience est publiée et que l’option Enable Studio Access to API Services est activée.
C’est tout le jeu : des pièces sombres, un monstre et une issue.
Défi
Difficile- Récupérez DataStoreService et
local bestStore = DataStoreService:GetDataStore("BestRoom"). - Dans PlayerAdded, après la connexion CharacterAdded : une IntValue
Bestdans leaderstats, puis la valeur enregistrée (avectostring(player.UserId), dans un pcall). - Dans
setupEntry, augmentez Best lorsque la salle est plus éloignée. - Enregistrez Best lors de
PlayerRemovinget dansgame:BindToClose, avecSetAsyncdans un pcall.
Appuyez sur Play, puis sur Check : le vérificateur quitte et revient.
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 remotes = game.ReplicatedStorage.Remotes
local rooms = workspace.Rooms
local lastRoom = rooms.Lobby -- la pièce la plus récente
local count = 0 -- pièces créées jusqu’ici
local monster = nil
local codes = {} -- porte du coffre-fort -> son code
local lastTry = {} -- joueur -> sa dernière tentative de saisie d’un code
-- 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
-- Les lampes de poche se déchargent quand elles éclairent et s’éteignent une fois vides
task.spawn(function()
while true do
task.wait(0.25)
for _, player in Players:GetPlayers() do
local flashlight = player.Character and player.Character:FindFirstChild("Flashlight")
if flashlight and flashlight.Handle.Beam.Enabled then
local battery = math.max(0, flashlight:GetAttribute("Battery") - Settings.BatteryDrain * 0.25)
flashlight:SetAttribute("Battery", battery)
if battery == 0 then
flashlight.Handle.Beam.Enabled = false
end
end
end
end
end)
-- Faire pivoter une porte sur ses gonds pour l’ouvrir
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
-- Dehors : la partie est terminée (avec le temps qu’elle a pris)
local function escape(player)
if player:GetAttribute("Escaped") then
return
end
player:SetAttribute("EscapeTime", workspace:GetServerTimeNow() - player:GetAttribute("StartedAt"))
player:SetAttribute("Escaped", true)
if monster then
monster:Destroy()
monster = nil
end
end
-- Le joueur vivant le plus proche qu’il 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 part de sa tête et atteint 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 : en ligne droite 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 centre d’une salle située `back` salles 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, il poursuit quelqu’un
local function runMonster(shadow)
local humanoid = shadow.Humanoid
local root = shadow.HumanoidRootPart
local path = PathfindingService:CreatePath()
local lastSeen = nil -- l’endroit où il a vu quelqu’un pour la dernière fois
local back = 0 -- sa patrouille : 0 correspond à la salle des joueurs, 2 à une salle 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 t’a vu pour la dernière fois, tu n’es pas là : 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 apparaît derrière toi : à 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 une salle a une porte
-- Ce que fait une porte quand un joueur l’ouvre
local function setupDoor(door)
door.Panel.Prompt.Triggered:Connect(function(player)
if door:GetAttribute("Open") then
return
end
if door:GetAttribute("Vault") and door:GetAttribute("Locked") then
return -- un coffre-fort s’ouvre avec son code, saisi sur le clavier
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:GetAttribute("Exit") then
escape(player)
openDoor(door)
return
end
if door.Parent == lastRoom then
addRoom()
end
openDoor(door)
end)
end
setupDoor(workspace.Rooms.Lobby.Door)
-- Quand un joueur entre dans une salle, elle devient sa Room 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
-- certaines salles contiennent une batterie
local batterySpot = room:FindFirstChild("BatterySpot")
if batterySpot then
local battery = ServerStorage.Battery:Clone()
battery.CFrame = batterySpot.CFrame
battery.Parent = room
battery.Prompt.Triggered:Connect(function(player)
local flashlight = findTool(player, "Flashlight")
if flashlight and battery.Parent then
flashlight:SetAttribute("Battery", math.min(Settings.BatteryMax, flashlight:GetAttribute("Battery") + Settings.BatteryRefill))
battery:Destroy()
end
end)
end
-- le code d’une chambre forte figure sur la note qui se trouve à l’intérieur
if room.Door:GetAttribute("Vault") then
local code = tostring(math.random(1000, 9999))
codes[room.Door] = code
room.Note.SurfaceGui.Code.Text = "Code " .. code
end
-- conserver la salle la plus récente et KeepRooms salles derrière elle
for _, old in rooms:GetChildren() do
if old:GetAttribute("Number") < count - Settings.KeepRooms then
old:Destroy()
end
end
if count >= Settings.MonsterFrom and not monster then
spawnMonster()
end
return room
end
-- Une fois sorti du placard, le joueur n’est 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)
-- Un code saisi sur le clavier d’une chambre forte. Le serveur vérifie qu’il s’agit d’une vraie chambre forte près
-- du joueur, qu’il n’y a qu’une tentative par CodeWait et que le code est correct
remotes.TryCode.OnServerInvoke = function(player, door, code)
if typeof(door) ~= "Instance" or not codes[door] or typeof(code) ~= "string" then
return false
end
local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
if not root or (root.Position - door.Panel.Position).Magnitude > 12 then
return false
end
local now = time()
if lastTry[player] and now - lastTry[player] < Settings.CodeWait then
return false
end
lastTry[player] = now
if code ~= codes[door] then
return false
end
codes[door] = nil
door:SetAttribute("Locked", nil)
if door.Parent == lastRoom then
addRoom()
end
openDoor(door)
return true
end
-- Pour 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()
if not player:GetAttribute("StartedAt") then
player:SetAttribute("StartedAt", workspace:GetServerTimeNow())
end
local flashlight = ServerStorage.Flashlight:Clone()
flashlight:SetAttribute("Battery", Settings.BatteryMax)
flashlight.Parent = player.Backpack
local beam = flashlight.Handle.Beam
flashlight.Activated:Connect(function()
if beam.Enabled then
beam.Enabled = false
elseif flashlight:GetAttribute("Battery") > 0 then
beam.Enabled = true
end
end)
end)
end)
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.