永远不要信任客户端
Coddy Roblox 游戏:宠物模拟器 课程的第 7 课,共 20 课。
玩家控制自己的设备,因此恶意利用者可以随意触发任何远程事件,发送任意内容,从任何地方发起请求,频率也由他们决定。在地图另一端每秒调用一百次 Hit:FireServer(pile),就会清空游戏中的所有堆。
因此,服务器会在执行操作前检查每个请求:
- 它是一个真实的堆(位于
workspace.Piles中的 Instance), - 玩家与它的距离不超过
Settings.Reach, - 而且距离上一次命中至少已经过了
Settings.HitCooldown秒。
if typeof(pile) ~= "Instance" or pile.Parent ~= piles then
return
end按下“检查”时,测试会模拟恶意利用者的操作。你编写的每个远程事件都要遵循一条规则:客户端提出请求,服务器做出决定。
挑战
中等让服务器拒绝公平玩家无法做到的击打。
- 在
Hit处理程序开头,如果pile不是一个 Parent 为piles的 Instance,就返回。 - 如果缺少他们的
HumanoidRootPart,或距离堆太远,超过Settings.Reachstuds,就返回。 - 保留
local lastHit = {};如果他们在不到Settings.HitCooldown秒前击打过,就返回,否则存储time()。
按下 Play,然后从远处点击一个堆:什么也不会发生。接着按下 Check。
亲手试试
local Players = game:GetService("Players")
local ServerStorage = game:GetService("ServerStorage")
local Settings = require(game.ReplicatedStorage.Settings)
local remotes = game.ReplicatedStorage.Remotes
local piles = workspace.Piles
local pileSpot = {} -- 每个金币堆 -> 它所在的位置
print("A Meadow pile gives " .. Settings.PileValue .. " coins")
-- 每个玩家的排行榜上都有 Coins
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
end)
-- 位置上的金币堆:ServerStorage.CoinPile 的副本,其生命值
-- 和值都作为属性
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)
pileSpot[pile] = spot
pile.Parent = piles
return pile
end
-- 每次击打使金币堆的生命值减少 1;
-- 生命值降到 0 时,金币堆碎裂并支付对应的值
local function hitPile(player, pile)
local health = pile:GetAttribute("Health") - 1
if health > 0 then
pile:SetAttribute("Health", health)
return
end
player.leaderstats.Coins.Value += pile:GetAttribute("Value")
local spot = pileSpot[pile]
pileSpot[pile] = nil
pile:Destroy()
-- 几秒后,同一位置会出现新的金币堆
task.delay(Settings.RespawnTime, spawnPile, spot)
end
-- Meadow 的每个位置都有一个金币堆
for _, spot in workspace.Zones.Meadow.Spawns:GetChildren() do
spawnPile(spot)
end
-- 客户端请求击打金币堆
remotes.Hit.OnServerEvent:Connect(function(player, pile)
-- 第 7 课:Never Trust the Client 的内容写在这里
hitPile(player, pile)
end)
Roblox 游戏:宠物模拟器中的所有课
自己练习: Roblox 在线编辑器