Menu
CoddyTech

Thời gian tốt nhất

Bài học 19 trên 21 trong khóa học Trò chơi Roblox: Tháp bất tận của Coddy.

Người chơi speedrun kéo dài thử thách obby. Hãy tính thời gian mỗi lần leo và lưu thành tích tốt nhất của từng người chơi trên bảng xếp hạng.

time() là số giây trò chơi đã chạy. Ghi lại thời điểm vòng chơi bắt đầu; khi lên đến đỉnh, lấy hiệu hai thời điểm để tính thời gian leo. Làm tròn đến một chữ số thập phân để hiển thị trên bảng:

local lap = math.floor((time() - lapStart) * 10) / 10 -- 7.46 giây thành 7.4 giây

Thời gian tốt nhất là thời gian ngắn nhất, còn 0 nghĩa là chưa có thành tích nào. NumberValue lưu giá trị này vì IntValue chỉ lưu được số nguyên.

challenge icon

Thử thách

Khó

Ghi thời gian mỗi lần leo và lưu thành tích tốt nhất của từng người chơi.

  1. Thêm một NumberValue BestTime vào leaderstats; startRound đặt roundStart = time() ở cấp cao nhất.
  2. Trong reachTop: local seconds = math.floor((time() - roundStart) * 10) / 10; nếu BestTime bằng 0 hoặc lớn hơn, hãy đặt giá trị đó thành seconds.
  3. Thay vào đó, in ra player.Name .. " reached the top in " .. seconds .. " seconds".

Nhấn Chơi và giành chiến thắng: thời gian của bạn sẽ hiển thị trên bảng xếp hạng. Sau đó, nhấn Kiểm tra.

Tự làm thử

local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local TweenService = game:GetService("TweenService")
local Settings = require(game.ReplicatedStorage.Settings)

local stages = ServerStorage.Stages
local lobbySpawn = workspace.Lobby.SpawnLocation
local tower = nil
local towerStages = {}
local lava = nil
local finished = {}

print("Each tower has " .. Settings.StageCount .. " stages")

-- Mỗi người chơi có Wins và Stage trên bảng xếp hạng
Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local wins = Instance.new("IntValue")
	wins.Name = "Wins"
	wins.Parent = leaderstats

	local stage = Instance.new("IntValue")
	stage.Name = "Stage"
	stage.Parent = leaderstats

	-- Người chơi bị rơi sẽ bắt đầu lại ở màn cuối cùng họ đã đến,
	-- trừ khi dung nham đã phủ kín màn đó: khi ấy họ chờ ở sảnh
	player.CharacterAdded:Connect(function(character)
		task.wait()
		local checkpoint = towerStages[stage.Value]
		local covered = checkpoint and lava and checkpoint.Start.Position.Y < lava.Position.Y + lava.Size.Y / 2
		if checkpoint and not covered and not finished[player] then
			character:PivotTo(checkpoint.Start.CFrame + Vector3.new(0, 4, 0))
		end
	end)
end)

-- Bảng thông báo trong sảnh
local gui = Instance.new("SurfaceGui")
gui.Parent = workspace.Lobby.Board

local boardLabel = Instance.new("TextLabel")
boardLabel.Name = "Status"
boardLabel.Size = UDim2.fromScale(1, 1)
boardLabel.BackgroundColor3 = Color3.fromRGB(25, 25, 35)
boardLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
boardLabel.TextScaled = true
boardLabel.Text = "Endless Tower"
boardLabel.Parent = gui

local function setBoard(text)
	boardLabel.Text = text
end

-- Chạm vào bất cứ thứ gì mang nhãn Kill sẽ mất một mạng
local function killOnTouch(hit)
	local humanoid = hit.Parent:FindFirstChild("Humanoid")
	if humanoid then
		humanoid.Health = 0
	end
end

-- Bậc biến mất mờ dần ngay sau khi có người đáp xuống, rồi hiện lại
local function makeVanish(part)
	part.Touched:Connect(function(hit)
		if part:GetAttribute("Gone") or not hit.Parent:FindFirstChild("Humanoid") then
			return
		end
		part:SetAttribute("Gone", true)
		task.wait(Settings.VanishDelay)
		part.Transparency = 0.8
		part.CanCollide = false
		task.wait(Settings.VanishTime)
		part.Transparency = 0
		part.CanCollide = true
		part:SetAttribute("Gone", nil)
	end)
end

-- Tia laser trượt đến vị trí lệch theo Slide rồi quay lại, lặp mãi
local function makeSlide(part)
	local info = TweenInfo.new(Settings.SlideTime, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut, -1, true)
	local goal = { Position = part.Position + part:GetAttribute("Slide") }
	TweenService:Create(part, info, goal):Play()
end

-- Tấm bật đẩy người đáp xuống lên không trung
local function bounce(hit)
	local root = hit.Parent:FindFirstChild("HumanoidRootPart")
	if root then
		root.AssemblyLinearVelocity = Vector3.new(0, Settings.BouncePower, 0)
	end
end

-- Các vật xoay nhích một chút sau mỗi khung hình
RunService.Heartbeat:Connect(function(dt)
	for _, spinner in CollectionService:GetTagged("Spinner") do
		if spinner:IsDescendantOf(workspace) then
			spinner:PivotTo(spinner:GetPivot() * CFrame.Angles(0, math.rad(Settings.SpinSpeed) * dt, 0))
		end
	end
end)

-- Kích hoạt mọi chướng ngại vật trong một màn dựa trên nhãn của chúng
local function setupStage(stage, index)
	for _, part in stage:GetDescendants() do
		if part:HasTag("Kill") then
			part.Touched:Connect(killOnTouch)
		end
		if part:HasTag("Vanish") then
			makeVanish(part)
		end
		if part:HasTag("Conveyor") then
			part.AssemblyLinearVelocity = part.CFrame.LookVector * Settings.BeltSpeed
		end
		if part:HasTag("Slider") then
			makeSlide(part)
		end
		if part:HasTag("Bounce") then
			part.Touched:Connect(bounce)
		end
	end
	-- Chạm đến Start của một màn sẽ cập nhật bảng xếp hạng
	stage.Start.Touched:Connect(function(hit)
		local player = Players:GetPlayerFromCharacter(hit.Parent)
		if player and player.leaderstats.Stage.Value < index then
			player.leaderstats.Stage.Value = index
		end
	end)
end

-- Các màn càng lên cao càng khó: một phần ba phía dưới dễ, một phần ba phía trên khó
local function pickStage(index)
	local difficulty = math.ceil(index / Settings.StageCount * 3)
	local choices = {}
	for _, stage in stages:GetChildren() do
		if stage:GetAttribute("Difficulty") == difficulty then
			table.insert(choices, stage)
		end
	end
	return choices[math.random(1, #choices)]
end

-- Đặt bản sao của mô hình lên đỉnh tháp; trả về bản sao và độ cao mới của đỉnh tháp
local function stack(template, bottom)
	local copy = template:Clone()
	local box, size = copy:GetBoundingBox()
	local lift = bottom - (box.Position.Y - size.Y / 2)
	copy:PivotTo(copy:GetPivot() + Vector3.new(0, lift, 0))
	copy.Parent = tower
	return copy, bottom + size.Y
end

-- Đỉnh tháp: ghi nhận một lần thắng rồi trở về sảnh
local function reachTop(hit)
	local player = Players:GetPlayerFromCharacter(hit.Parent)
	if not player or finished[player] then
		return
	end
	finished[player] = true
	player.leaderstats.Wins.Value += 1
	-- Bài học 19: thêm Best Times vào đây
	print(player.Name .. " reached the top")
	hit.Parent:PivotTo(lobbySpawn.CFrame + Vector3.new(0, 4, 0))
end

-- Tạo tháp mới ngẫu nhiên từ dưới lên
local function buildTower()
	tower = Instance.new("Model")
	tower.Name = "Tower"
	tower.Parent = workspace
	towerStages = {}
	local top = 0
	for i = 1, Settings.StageCount do
		local stage, newTop = stack(pickStage(i), top)
		top = newTop
		setupStage(stage, i)
		towerStages[i] = stage
	end
	local finish = stack(ServerStorage.Finish, top)
	finish.FinishPad.Touched:Connect(reachTop)
	return top
end

-- Dung nham dâng từ mặt đất lên sát dưới đỉnh tháp
local function startLava(top)
	lava = Instance.new("Part")
	lava.Name = "Lava"
	lava.Anchored = true
	lava.CanCollide = false
	lava.Size = Vector3.new(30, 2, 30)
	lava.Position = Vector3.new(0, -2, 0)
	lava.Color = Color3.fromRGB(255, 90, 20)
	lava.Material = Enum.Material.Neon
	lava.Parent = workspace
	lava.Touched:Connect(killOnTouch)
	local info = TweenInfo.new(top / Settings.LavaSpeed, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut, 0, false, Settings.LavaDelay)
	TweenService:Create(lava, info, { Position = Vector3.new(0, top - 2, 0) }):Play()
end

local function startRound()
	finished = {}
	for _, player in Players:GetPlayers() do
		player.leaderstats.Stage.Value = 0
	end
	local top = buildTower()
	startLava(top)
	for _, player in Players:GetPlayers() do
		if player.Character then
			player.Character:PivotTo(towerStages[1].Start.CFrame + Vector3.new(0, 4, 0))
		end
	end
end

local function endRound()
	for _, player in Players:GetPlayers() do
		if player.Character and not finished[player] then
			player.Character:PivotTo(lobbySpawn.CFrame + Vector3.new(0, 4, 0))
		end
		player.leaderstats.Stage.Value = 0
	end
	tower:Destroy()
	lava:Destroy()
	towerStages = {}
end

-- Vòng lặp của mỗi lượt chơi
while true do
	for i = Settings.Intermission, 1, -1 do
		setBoard("Next tower in " .. i)
		task.wait(1)
	end
	startRound()
	for i = Settings.RoundLength, 1, -1 do
		setBoard("Climb! " .. i .. " seconds left")
		task.wait(1)
	end
	endRound()
end

Tất cả bài học trong Trò chơi Roblox: Tháp bất tận

Tự luyện tập: Playground Roblox