Menu
CoddyTech

Das beste Zimmer

Lektion 20 von 20 im Kurs Roblox-Spiel: Entkomme der Dunkelheit von Coddy.

Das letzte Feature: der tiefste Raum, den ein Spieler je erreicht hat, Best auf der Bestenliste, wird zwischen den Besuchen in einem DataStore gespeichert. Lies den Wert, wenn der Spieler beitritt, erhöhe ihn, wenn er einen tieferen Raum betritt, und speichere ihn, wenn er das Spiel verlässt und wenn der Server geschlossen wird.

GetAsync wartet auf den Store. Eine Funktion, die innerhalb von PlayerAdded wartet, gibt dem ersten Charakter des Spielers Zeit, zu erscheinen, bevor etwas darunter ausgeführt wird. Deshalb kommt die Verbindung zu CharacterAdded zuerst und das Auslesen zuletzt:

local ok, saved = pcall(function()
	return bestStore:GetAsync(tostring(player.UserId))
end)

Datastores können fehlschlagen, daher ist jeder Aufruf in pcall verpackt. In Roblox Studio funktionieren DataStores, sobald der Ort veröffentlicht ist und Enable Studio Access to API Services aktiviert ist.

Das ist das ganze Spiel: dunkle Räume, ein Monster und ein Weg nach draußen.

challenge icon

Aufgabe

Schwer
  1. Rufe DataStoreService ab und verwende local bestStore = DataStoreService:GetDataStore("BestRoom").
  2. In PlayerAdded, nach der CharacterAdded-Verbindung: ein IntValue Best in leaderstats, dann der gespeicherte Wert (über tostring(player.UserId), in einem pcall).
  3. Erhöhe in setupEntry Best, wenn der Raum weiter hinten liegt.
  4. Speichere Best bei PlayerRemoving und in game:BindToClose, mit SetAsync in einem pcall.

Drücke Play und dann Check: Die Prüfung wird beendet und erneut geöffnet.

Probier es selbst

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 -- der neueste Raum
local count = 0 -- bisher erstellte Räume
local monster = nil
local codes = {} -- Tresortür -> ihr Code
local lastTry = {} -- Spieler -> wann sie zuletzt einen Code versucht haben

-- Die Nacht bricht herein: keine Sonne, überall ein wenig Licht und Nebel
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")

-- Ein Gegenstand, den ein Spieler trägt: in der Hand oder im Backpack
local function findTool(player, name)
	return (player.Character and player.Character:FindFirstChild(name)) or player.Backpack:FindFirstChild(name)
end

-- Taschenlampen verbrauchen Energie, solange sie leuchten, und gehen aus, wenn sie leer sind
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)

-- Eine Tür an ihrem Scharnier aufschwingen lassen
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

-- Draußen: Der Durchlauf ist vorbei (und so lange hat er gedauert)
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

-- Der nächste lebende Spieler, den es fangen könnte (nicht versteckt), innerhalb von MonsterSight, und seine Entfernung
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

-- Kann das Monster diesen Charakter sehen? Ein Strahl von seinem Kopf aus, der ihn trifft
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

-- Auf einen Punkt zugehen: geradeaus, wenn der Weg frei ist, sonst entlang eines Pfads
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

-- Die Mitte eines Raums `back` Räume vor dem, in dem sich die Spieler befinden (dem Raum
-- vor dem neuesten), für die Patrouille des Monsters
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

-- Die Steuerung des Monsters: Es sucht jede Viertelsekunde nach jemandem
local function runMonster(shadow)
	local humanoid = shadow.Humanoid
	local root = shadow.HumanoidRootPart
	local path = PathfindingService:CreatePath()
	local lastSeen = nil -- wo es zuletzt jemanden gesehen hat
	local back = 0 -- seine Patrouille: 0 ist der Raum der Spieler, 2 ist zwei Räume dahinter
	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
			-- erwischt: Der Spieler ist besiegt, und das Monster verschwindet bis zur nächsten Tür
			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)
			-- am Ort, an dem es dich zuletzt gesehen hat, bist du nicht: Es gibt auf
			if not character and ((goal - root.Position) * Vector3.new(1, 0, 1)).Magnitude < 1.5 then
				lastSeen = nil
			end
		else
			-- niemand in Sicht: auf und ab patrouillieren
			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

-- Das Monster taucht hinter dir auf: am Eingang des Raums zwei Räume vor dem neuesten
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 -- weiter unten definiert: Eine Tür fügt einen Raum hinzu, und ein Raum hat eine Tür

-- Was eine Tür tut, wenn ein Spieler sie öffnet
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 -- ein Tresor öffnet sich mit seinem Code über das Tastenfeld
		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)

-- Beim Betreten eines Raums wird er zum Room des Spielers und zu seinem Checkpoint
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)

-- Ein Schrank: Mit E darin verstecken, mit E wieder herauskommen
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

-- Ein neuer Raum am Ausgang des neuesten Raums
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)
	-- Ein verschlossener Raum enthält seinen Schlüssel
	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
	-- Manche Räume enthalten eine 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
	-- Der Code eines Tresors steht auf dem Zettel darin
	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
	-- Den neuesten Raum und KeepRooms Räume dahinter behalten
	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

-- Außerhalb des Schranks bist du nicht mehr versteckt
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)

-- Ein auf dem Tastenfeld eines Tresors eingegebener Code. Der Server prüft: ein echter Tresor in der Nähe
-- des Spielers, ein Versuch pro CodeWait und der richtige Code
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

-- Für jeden Spieler: Room auf der Bestenliste und eine Taschenlampe bei jedem neuen Leben
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)
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Roblox-Spiel: Entkomme der Dunkelheit