Menu
CoddyTech

Cổng sa mạc

Bài học 16 trên 20 trong khóa học Trò chơi Roblox: mô phỏng thú cưng của Coddy.

Bức tường đỏ ở phía sau là cổng dẫn đến Sa mạc, có lời nhắc "Unlock". Thanh toán Settings.GatePrice sẽ mở cổng, nhưng chỉ mở cho người chơi đã thanh toán.

Cần có cả hai phía. Máy chủ lấy tiền xu và ghi nhận trạng thái mở khóa dưới dạng thuộc tính Desert của người chơi để mọi quy tắc đều có thể kiểm tra. Client theo dõi thuộc tính đó và mở cổng trên màn hình của riêng mình: trong Roblox, thiết bị của mỗi người chơi điều khiển nhân vật của chính họ, vì vậy một bức tường mà LocalScript làm cho không còn va chạm sẽ cho người chơi đó đi qua, trong khi bức tường vẫn có va chạm với mọi người khác.

-- máy khách
workspace.Gate.CanCollide = false
workspace.Gate.Transparency = 0.7

Ẩn lời nhắc ở cổng theo cách tương tự sẽ ngăn lời nhắc đó đề xuất mở khóa lần thứ hai.

challenge icon

Thử thách

Khó

Mở cổng Sa mạc cho người chơi trả phí.

  1. Trong PetServer, tại workspace.Gate.ProximityPrompt.Triggered: dừng lại nếu người chơi có thuộc tính Desert hoặc có ít xu hơn Settings.GatePrice; nếu không, trừ số xu tương ứng với giá và đặt Desert thành true.
  2. Trong PetClient, openGate(): nếu me:GetAttribute("Desert"), đặt CanCollide của cổng thành false và Transparency thành 0.7. Gọi hàm này ngay bây giờ và khi Desert thay đổi.

Nhấn “Chơi”, kiếm 100 xu để mở khóa: bức tường mờ đi. Sau đó nhấn “Kiểm tra”.

Tự làm thử

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

local remotes = game.ReplicatedStorage.Remotes
local piles = workspace.Piles
local petParts = workspace.PetParts
local pileSpot = {} -- mỗi đống xu -> vị trí của nó
local lastHit = {} -- mỗi người chơi -> thời điểm đánh gần nhất

print("A Meadow pile gives " .. Settings.PileValue .. " coins")

-- Settings.Pets theo tên
local petInfo = {}
for _, info in Settings.Pets do
	petInfo[info.Name] = info
end

-- Thú cưng: một StringValue trong thư mục Pets của người chơi, lưu tên cùng
-- độ hiếm và sức mạnh dưới dạng thuộc tính
local function givePet(player, name, equipped)
	local info = petInfo[name]
	local pet = Instance.new("StringValue")
	pet.Name = "Pet"
	pet.Value = name
	pet:SetAttribute("Rarity", info.Rarity)
	pet:SetAttribute("Power", info.Power)
	pet:SetAttribute("Equipped", equipped)
	pet.Parent = player.Pets
	return pet
end

-- số thú cưng người chơi đang đưa ra
local function equippedCount(player)
	local count = 0
	for _, pet in player.Pets:GetChildren() do
		if pet:GetAttribute("Equipped") then
			count += 1
		end
	end
	return count
end

-- sức mạnh thú cưng cộng thêm vào mỗi cú đánh
local function petPower(player)
	local power = 0
	for _, pet in player.Pets:GetChildren() do
		if pet:GetAttribute("Equipped") then
			power += pet:GetAttribute("Power")
		end
	end
	return power
end

-- Mỗi người chơi có Coins 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 coins = Instance.new("IntValue")
	coins.Name = "Coins"
	coins.Parent = leaderstats

	-- những thú cưng họ sở hữu
	local pets = Instance.new("Folder")
	pets.Name = "Pets"
	pets.Parent = player
end)

-- Đống xu tại một vị trí: bản sao của ServerStorage.CoinPile, lưu máu
-- và giá trị dưới dạng thuộc tính
local function spawnPile(spot)
	local pile = ServerStorage.CoinPile:Clone()
	pile.Position = spot.Position
	pile:SetAttribute("MaxHealth", Settings.PileHealth)
	pile:SetAttribute("Health", Settings.PileHealth)
	pile:SetAttribute("Value", Settings.PileValue)
	pile.HealthGui.Label.Text = pile:GetAttribute("Health") .. "/" .. pile:GetAttribute("MaxHealth")
	pileSpot[pile] = spot
	pile.Parent = piles
	return pile
end

-- Mỗi cú đánh trừ 1 máu của đống xu, cộng thêm sức mạnh thú cưng của người chơi;
-- Khi giá trị sức khỏe về 0, đống xu vỡ ra và trả số xu tương ứng với giá trị của nó
local function hitPile(player, pile)
	local health = pile:GetAttribute("Health") - (1 + petPower(player))
	if health > 0 then
		pile:SetAttribute("Health", health)
		pile.HealthGui.Label.Text = health .. "/" .. pile:GetAttribute("MaxHealth")
		return
	end
	player.leaderstats.Coins.Value += pile:GetAttribute("Value")
	local spot = pileSpot[pile]
	pileSpot[pile] = nil
	pile:Destroy()
	-- Vài giây sau, một đống xu mới xuất hiện tại cùng vị trí
	task.delay(Settings.RespawnTime, spawnPile, spot)
end

-- Mỗi vị trí trong Meadow có một đống xu
for _, spot in workspace.Zones.Meadow.Spawns:GetChildren() do
	spawnPile(spot)
end

-- Máy khách yêu cầu đánh một đống xu. Đừng bao giờ tin máy khách: bất kỳ ai cũng có thể kích hoạt
-- remote này với bất kỳ dữ liệu nào, từ bất kỳ đâu, nhanh tùy ý.
remotes.Hit.OnServerEvent:Connect(function(player, pile)
	-- đống xu có thật
	if typeof(pile) ~= "Instance" or pile.Parent ~= piles then
		return
	end
	-- trong tầm với
	local character = player.Character
	local root = character and character:FindFirstChild("HumanoidRootPart")
	if not root or (root.Position - pile.Position).Magnitude > Settings.Reach then
		return
	end
	-- không quá nhanh
	local now = time()
	if lastHit[player] and now - lastHit[player] < Settings.HitCooldown then
		return
	end
	lastHit[player] = now
	hitPile(player, pile)
end)

-- Chọn ngẫu nhiên theo trọng số: Chance của mỗi thú cưng (trên thang 100) là phần tương ứng trên dải lựa chọn
local function rollPet()
	local roll = math.random() * 100
	for _, info in Settings.Pets do
		roll -= info.Chance
		if roll <= 0 then
			return info.Name
		end
	end
	return Settings.Pets[1].Name
end

-- Ấp trứng: dùng lời nhắc của quả trứng và trả bằng xu
workspace.Egg.ProximityPrompt.Triggered:Connect(function(player)
	local coins = player.leaderstats.Coins
	if coins.Value < Settings.EggPrice then
		return
	end
	coins.Value -= Settings.EggPrice
	local name = rollPet()
	-- cho ra ngoài ngay nếu còn chỗ
	local pet = givePet(player, name, equippedCount(player) < Settings.MaxEquipped)
	print(player.Name .. " hatched a " .. name)
end)

-- Thú cưng đang ở ngoài đi theo chủ: mỗi con có một bộ phận trong thế giới, luôn được giữ
-- thành một hàng phía sau người chơi ở mỗi khung hình
local petModels = {} -- mỗi thú cưng tương ứng với một bộ phận của nó trong thế giới
RunService.Heartbeat:Connect(function()
	for _, player in Players:GetPlayers() do
		local root = player.Character and player.Character:FindFirstChild("HumanoidRootPart")
		local pets = player:FindFirstChild("Pets")
		local slot = 0
		for _, pet in pets and pets:GetChildren() or {} do
			local model = petModels[pet]
			if pet:GetAttribute("Equipped") and root then
				slot += 1
				if not model then
					model = ServerStorage.PetModels[pet.Value]:Clone()
					model.Parent = petParts
					petModels[pet] = model
				end
				model.CFrame = root.CFrame * CFrame.new((slot - 2) * 3, -1.5, 4)
			elseif model then
				-- cất thú cưng đi: bộ phận của nó cũng biến mất
				model:Destroy()
				petModels[pet] = nil
			end
		end
	end
end)

-- Máy khách yêu cầu cho thú cưng ra ngoài hoặc cất đi. Máy chủ kiểm tra xem thú cưng có
-- thuộc về người chơi và số thú cưng đang ở ngoài không vượt quá MaxEquipped.
remotes.Equip.OnServerEvent:Connect(function(player, pet)
	if typeof(pet) ~= "Instance" or pet.Parent ~= player.Pets then
		return
	end
	if pet:GetAttribute("Equipped") then
		pet:SetAttribute("Equipped", false)
	elseif equippedCount(player) < Settings.MaxEquipped then
		pet:SetAttribute("Equipped", true)
	end
end)

Tất cả bài học trong Trò chơi Roblox: mô phỏng thú cưng

Tự luyện tập: Playground Roblox