Menu
CoddyTech

Tuần tra

Bài học 16 trên 25 trong khóa học Trò chơi Roblox: thoát khỏi bóng tối của Coddy.

Sau khi mất dấu bạn, con quái vật đứng ở nơi cuối cùng nó nhìn thấy bạn. Một thợ săn giỏi hơn sẽ tuần tra: đi qua giữa căn phòng mà người chơi đang ở, rồi đến căn phòng cách họ hai phòng, qua lại giữa hai nơi. Nhờ vậy, bạn có cơ hội lẻn qua, còn nó có cơ hội tìm thấy bạn.

-- Giữa căn phòng: cách lối vào 12 stud, ở độ cao ngang với quái vật
room.Entrance.CFrame * CFrame.new(0, 3.1, -12)

Người chơi ở căn phòng trước căn phòng mới nhất, vì vậy căn phòng cách họ back căn phòng phía sau có số thứ tự là count - 1 - back.

challenge icon

Thử thách

Trung bình

Khi không có ai để đuổi theo, Shadow đi tuần.

  1. Viết patrolPoint(back): lấy room.Entrance.CFrame * CFrame.new(0, 3.1, -12) của căn phòng có số count - 1 - back, dưới dạng Position, hoặc nil.
  2. Trong runMonster, giữ nguyên local back = 0. Thay vì đứng yên, hãy đi đến patrolPoint(back) or patrolPoint(0).
  3. Khi cách điểm đó trong vòng 2 studs, đổi back qua lại giữa 0 và 2.

Nhấn Chơi và tránh bị nhìn thấy: Shadow đi qua đi lại. Sau đó nhấn Kiểm tra.

Tự làm thử

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 -- phòng mới nhất
local count = 0 -- số phòng đã tạo đến lúc này
local monster = nil

-- Đêm xuống: không có ánh mặt trời, ánh sáng yếu khắp nơi và sương mù
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")

-- Vật dụng người chơi mang theo: trên tay hoặc trong Backpack
local function findTool(player, name)
	return (player.Character and player.Character:FindFirstChild(name)) or player.Backpack:FindFirstChild(name)
end

-- Mở cửa bằng cách xoay quanh bản lề
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

-- Người chơi còn sống gần nhất mà quái vật có thể bắt, không ẩn nấp, trong phạm vi MonsterSight, cùng khoảng cách đến người đó
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

-- Quái vật có nhìn thấy nhân vật này không? Một tia từ đầu quái vật chạm vào nhân vật
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

-- Đi tới một điểm: đi thẳng nếu không có vật cản, nếu không thì đi theo đường đi
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

-- Bài học 16: Thêm Patrol vào đây

-- Bộ não của quái vật: cứ mỗi phần tư giây lại đuổi theo một người
local function runMonster(shadow)
	local humanoid = shadow.Humanoid
	local root = shadow.HumanoidRootPart
	local path = PathfindingService:CreatePath()
	local lastSeen = nil -- nơi nó nhìn thấy một người lần cuối
	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
			-- bắt được: người chơi bị hạ, còn quái vật biến mất cho đến cửa tiếp theo
			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)
			-- Đến nơi nó nhìn thấy bạn lần cuối mà không thấy bạn: nó bỏ cuộc
			if not character and ((goal - root.Position) * Vector3.new(1, 0, 1)).Magnitude < 1.5 then
				lastSeen = nil
			end
		else
			humanoid:MoveTo(root.Position)
		end
		task.wait(0.25)
	end
end

-- Quái vật xuất hiện phía sau bạn: tại lối vào của căn phòng cách phòng mới nhất hai phòng
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 -- Viết bên dưới: một cánh cửa thêm một căn phòng, và mỗi căn phòng có một cánh cửa

-- Điều xảy ra khi người chơi mở cửa
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)

-- Đi vào một căn phòng: phòng đó trở thành Room của người chơi và điểm hồi sinh của họ
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)

-- Tủ đồ: nhấn E để trốn vào, nhấn E lần nữa để đi ra
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

-- Một căn phòng mới tại lối ra của phòng mới nhất
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)
	-- Phòng bị khóa giữ chìa khóa bên trong
	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
	-- Giữ phòng mới nhất và KeepRooms phòng phía sau
	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

-- Ra khỏi tủ thì bạn không còn ẩn nấp nữa
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)

-- Mỗi người chơi: Room trên bảng xếp hạng và một chiếc đèn pin mỗi lần hồi sinh
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)
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Trò chơi Roblox: thoát khỏi bóng tối

Tự luyện tập: Playground Roblox