开始之前
完成第 16–17 课。新建 Lesson18 Baseplate,练习两种脚本位置。
Roblox Studio · 第 18 课
用 RemoteEvent 连接按钮与服务器,练习请求、校验、限流和状态反馈。
Course overview
这一课把你的个人按钮连接到共享规则。玩家按下按钮,客户端发送“我要补充”,服务器检查冷却后决定是否增加能量。
我们不让客户端传“请给我一万点”。请求只表达动作,能量上限与增加量都写在服务器脚本里。
完成第 16–17 课。新建 Lesson18 Baseplate,练习两种脚本位置。
一个补充能量按钮,服务器每两秒最多接受一次,每次加 10,上限 100。
快速连点不会绕过冷却;两个玩家的能量分别记录;晚出现的界面能读到当前值。

Learning route
每完成一小步,就测试一次。先说出你预计会看到什么,再用 Play 和 Output 检查,不用急着一次写完所有代码。
RemoteEvent 像一条消息通道。ReplicatedStorage 同时对服务器和客户端可见,因此适合放这条通道。
OnServerEvent 的第一个 player 由 Roblox 提供,不需要客户端自己传身份。服务端冷却按玩家分别记录,并在离开时清理。
FireServer() 只发送请求,不代表申请已经成功。按钮显示的值应来自服务器更新后的 Attribute。
这是一台免费训练补给机,任何存活玩家都能申请,不需要距离限制。如果改成“靠近营地才能补充”,就必须在服务器再检查到营地的距离。
安全检查也要有可重复的预期:十次快速点击不能获得 100 能量;慢慢等待并点击,最终能到 100,但不能超过。
Step by step
RemoteEvent 像一条消息通道。ReplicatedStorage 同时对服务器和客户端可见,因此适合放这条通道。
下面的 Energy 是 Player 的 Attribute(自定义属性),可以保存一个小数值并让客户端读取。第 19 课会进一步使用它制作任务。
Step by step
OnServerEvent 的第一个 player 由 Roblox 提供,不需要客户端自己传身份。服务端冷却按玩家分别记录,并在离开时清理。
SetAttribute 更新状态;GetAttribute 读取状态。角色必须存活,且满足两秒间隔,服务器才会增加固定的 10 点。
local Players = game:GetService("Players")
local remote = game:GetService("ReplicatedStorage"):WaitForChild("RechargeRequest")
local lastRequest = {}
local function setup(player)
player:SetAttribute("Energy", 0)
end
Players.PlayerAdded:Connect(setup)
for _, player in ipairs(Players:GetPlayers()) do setup(player) end
remote.OnServerEvent:Connect(function(player)
local now = os.clock()
if now - (lastRequest[player] or -math.huge) < 2 then return end
lastRequest[player] = now
local character = player.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not humanoid or humanoid.Health <= 0 then return end
local energy = player:GetAttribute("Energy") or 0
player:SetAttribute("Energy", math.min(100, energy + 10))
end)
Players.PlayerRemoving:Connect(function(player)
lastRequest[player] = nil
end)

Step by step
FireServer() 只发送请求,不代表申请已经成功。按钮显示的值应来自服务器更新后的 Attribute。
先连接变化事件,再调用一次 render:既能接住以后发生的改变,也能读到界面打开前已有的当前状态。暂时没有值时显示等待,而不是假装加载完成。
local Players = game:GetService("Players")
local player = Players.LocalPlayer
local remote = game:GetService("ReplicatedStorage"):WaitForChild("RechargeRequest")
local gui = Instance.new("ScreenGui")
gui.Name = "EnergyGui"
gui.ResetOnSpawn = false
local button = Instance.new("TextButton")
button.Size = UDim2.fromOffset(280, 64)
button.Position = UDim2.fromOffset(16, 16)
button.TextSize = 20
button.Parent = gui
gui.Parent = player:WaitForChild("PlayerGui")
local function render()
local energy = player:GetAttribute("Energy")
button.Text = energy == nil and "等待能量数据…" or ("能量 " .. energy .. "/100 · 补充")
end
player:GetAttributeChangedSignal("Energy"):Connect(render)
button.Activated:Connect(function()
remote:FireServer()
end)
render()
Step by step
这是一台免费训练补给机,任何存活玩家都能申请,不需要距离限制。如果改成“靠近营地才能补充”,就必须在服务器再检查到营地的距离。
如果以后允许客户端传入物品名,要检查类型、允许列表、价格与玩家是否有资格。不要信任客户端传来的金币数或“我已经完成任务”。
Step by step
安全检查也要有可重复的预期:十次快速点击不能获得 100 能量;慢慢等待并点击,最终能到 100,但不能超过。
本课只做课堂规模的限流实验,不代表完整线上反作弊系统。它展示的核心习惯是把规则放在可信的执行位置。
Homework
在服务器把增量改为 20,保持上限 100,并更新测试预期。
用三句话说明按钮、RemoteEvent、服务器各做什么。
在按钮旁写明“每两秒可补充一次”,让玩家知道为什么连点没有效果。
Exit questions
不需要,Roblox 自动提供调用客户端对应的 Player。
不能把发出请求当作成功,应显示服务器确认后的状态。
避免错过后续变化,同时读取已经存在的当前状态。
不够,客户端界面可以被绕过,规则和限流必须在服务器执行。
Explore further
正文代码可以直接复制。先确认脚本类型、放置位置,以及这段代码是完整版本还是小实验;不要把同一脚本的多个版本叠加运行。界面参考图已标注来源,图中的对象名可能与本课不同,请以操作步骤为准。