关于 roblox-networking
roblox-networking 是帮助 Roblox 开发者设计并保护客户端与服务器通信的技能,重点包括服务器权威状态、载荷验证、复制策略和多人滥用测试。
主要功能
- 01梳理现有远程协议
- 02按消息语义选择传输方式
- 03验证载荷与玩家状态
- 04在服务器应用游戏状态变更
- 05限制请求频率
- 06测试多人和滥用场景
适用场景
- 01防止客户端指定伤害或价格
- 02保护以 Instance 为目标的远程调用
- 03减少过量远程流量
- 04处理重生后的过期请求
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ gamedev-skills/ awesome-gamedev-agent-skills --skill roblox-networking安装后在 Claude Code 中输入/roblox-networking确认已加载。
附带文件agents/openai.yamlreferences/validation-and-testing.md
详细介绍
Roblox 网络
构建明确的请求与复制契约,由服务器决定权威游戏状态,客户端提供输入或意图。面向 Roblox 持续更新的平台 API。本技能比 roblox-luau 中的网络入门内容更深入。
适用场景
- 用于设计、实现、调试或保护跨边界的 Roblox 通信。
- 用于远程调用信任客户端值、攻击者可指定任意 Instance、消息使服务过载、流式传输的对象缺失,或客户端与服务器状态不一致的情况。
不适用场景: 基础 Luau/服务相关内容请参阅 roblox-luau;持久化状态请参阅
roblox-datastores;物理所有权机制也可结合 roblox-physics 使用。
工作流
- 检查现有协议。 找出每个远程对象及其两个端点;记录方向、发送方、载荷、频率、权限、验证方式和使用方。复用规范的远程对象文件夹——不要因为漏做查找而创建重复对象。
- 对每条消息分类。 客户端请求、服务器事实或临时外观样本。根据语义而非便利性选择可靠事件、不可靠事件或请求/响应。
- 精简载荷。 发送稳定标识符和意图。不要发送服务器可推导出的价格、伤害、所有权结果、任意路径或计算结果。
- 分层验证。 在执行耗时操作前,检查类型/结构/有限性、允许列表中的值、Instance 类别和祖先关系、玩家权限/状态、相关情况下的距离/视线、服务器冷却时间以及速率预算。
- 在服务器上执行。 由服务器解析目标并修改生命值、物品栏、货币、冷却时间和进度。客户端检查可改善用户体验,但不构成信任依据。
- 谨慎复制。 私有或本地事实使用
FireClient;仅广播共享事实。除非客户端需要独立的呈现事件,否则避免再次发送已复制的属性。 - 处理时间和生命周期。 请求可能在死亡、重生、流式传输变化或断开连接后到达。处理请求时解析当前角色/状态,并清理每位玩家的限流数据。
- 使用 Server & Clients 验证。 测试正常、格式错误、垃圾请求、超出范围、角色过期、快速重生、离开、同时有多名玩家、定向和广播等情况。分别检查服务器和每个客户端的 Output。
选择传输方式
| 原语 | 用途 | 不要用于 |
|---|---|---|
RemoteEvent | 有序、可靠的单向请求/事实 | 新样本会取代旧样本的连续数据 |
UnreliableRemoteEvent | 可容忍丢失和乱序的临时外观/连续状态 | 购买、伤害判定、物品栏、单次状态转换 |
RemoteFunction | 确实需要立即回复的、有界客户端到服务器查询 | 服务器到客户端调用;耗时或结果不确定的工作;常规命令 |
绝不要在服务器中同步调用客户端。客户端可能断开连接、出错或始终不返回。优先使用服务器端 RemoteEvent:FireClient(),并在需要时使用单独的响应事件。
模式:在解析游戏逻辑前进行验证
-- ServerScriptService/CombatRequests.server.luau
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local attack = ReplicatedStorage.Remotes.Attack
local lastRequest: {[Player]: number} = {}
local RANGE = 12
local COOLDOWN = 0.25
attack.OnServerEvent:Connect(function(player: Player, target: unknown)
local now = Workspace:GetServerTimeNow()
if now - (lastRequest[player] or -math.huge) < COOLDOWN then return end
lastRequest[player] = now
if typeof(target) ~= "Instance" or not target:IsA("Model") then return end
if not target:IsDescendantOf(Workspace.Characters) then return end
local targetHumanoid = target:FindFirstChildOfClass("Humanoid")
local targetRoot = target:FindFirstChild("HumanoidRootPart")
local character = player.Character
local root = character and character:FindFirstChild("HumanoidRootPart")
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
if not targetHumanoid or not targetRoot or not root or not humanoid then return end
if humanoid.Health <= 0 or targetHumanoid.Health <= 0 then return end
if (root.Position - targetRoot.Position).Magnitude > RANGE then return end
if not serverCombatStateAllowsAttack(player, now) then return end
targetHumanoid:TakeDamage(serverDamageFor(player))
end)
Players.PlayerRemoving:Connect(function(player)
lastRequest[player] = nil
end)
这仍然只是一个简洁示例:真实的近战系统可能需要服务器已知的攻击时段、视线/形状检查、队伍规则和延迟处理策略。不要把单次距离检查当作安全保障。
模式:在边界使用令牌桶
type Bucket = {tokens: number, updatedAt: number}
local buckets: {[Player]: Bucket} = {}
local CAPACITY, REFILL_PER_SECOND = 6, 3
local function consume(player: Player, cost: number): boolean
local now = os.clock()
local bucket = buckets[player] or {tokens = CAPACITY, updatedAt = now}
bucket.tokens = math.min(CAPACITY,
bucket.tokens + (now - bucket.updatedAt) * REFILL_PER_SECOND)
bucket.updatedAt = now
if bucket.tokens < cost then buckets[player] = bucket; return false end
bucket.tokens -= cost
buckets[player] = bucket
return true
end
根据对服务器的影响分配成本。在进行数据存储调用、克隆、射线检测或大范围复制前,以低成本拒绝请求。记录汇总后的滥用信号,不要针对每个被拒绝的数据包都发出警告。
复制、流式传输与预测
- 已复制的实例/属性本身就是状态通道。将远程事件用于意图、私有状态或呈现提示,而不要无条件地并行复制 DataModel。
- 启用实例流式传输后,客户端上可能不存在有效的服务器 Instance。发送稳定 ID 并容忍实例缺失;不要无限等待可选的流式内容。
- 高频率的装饰性数据可以使用
UnreliableRemoteEvent;每个样本都应包含完整信息,因为无法保证送达或顺序。超过 1000 bytes 的负载会被丢弃(Studio Output 会报告超出的字节数)。RemoteEvent和UnreliableRemoteEvent还共享一个限制:每种类型的所有远程事件合计,每个客户端每秒大约 500 次调用。这是正常玩家会先于任何攻击者触及的限制。 - 只对延迟敏感、可撤销的呈现进行预测。包含客户端序列/命令 ID;服务器返回权威状态和确认信息;客户端平滑修正。绝不要让预测增加伤害、货币、物品栏内容或进度。
- 网络所有权能提高响应速度,但也让客户端可以影响物理模拟。在服务器上验证游戏结果;拥有所有权不等于拥有授权。
常见故障
| 症状 | 可能原因 | 解决方法 |
|---|---|---|
| 利用漏洞的玩家自行选择伤害/价格 | 接受来自客户端的结果 | 发送意图/ID;在服务器上推导并应用结果 |
| 任意对象都能被删除 | 只检查了 typeof(Instance) | 验证类、祖先关系、所有权、状态及允许列表中的操作 |
| 服务器因某个玩家而停滞 | 服务器调用客户端的 RemoteFunction | 改用异步事件 |
| 正常玩家触发限流 | 每帧发送可靠消息 | 降低频率,使用状态复制、批处理或不可靠的装饰性数据 |
| 旧数据包反转了新效果 | 将无序的不可靠样本当作命令处理 | 使样本可替换/带版本号;状态转换使用可靠事件 |
| 重生后远程事件失效 | 缓存了角色/根部实例 | 处理请求时解析当前角色,并拒绝过时状态 |
| 私有数据泄露 | 默认使用了 FireAllClients | 使用 FireClient 并尽量减少负载 |
| 距离检查被绕过 | 客户端拥有的对象被移至目标附近 | 固定关键对象/将其设为服务器所有,并验证完整的服务器上下文 |
资源
- 阅读
references/validation-and-testing.md,了解负载规则、Instance/有限性检查、复制设计以及必需的多客户端滥用测试矩阵。
相关技能
roblox-luau— 执行位置和 RemoteEvent 的基本机制。roblox-characters— 重生安全的角色解析。roblox-physics— 网络所有权、射线/重叠验证和物理结果。roblox-studio-workflow— Server & Clients 测试和 Output 检查。
主要参考资料
https://create.roblox.com/docs/scripting/events/remotehttps://create.roblox.com/docs/scripting/security/client-server-boundaryhttps://create.roblox.com/docs/physics/network-ownershiphttps://create.roblox.com/docs/studio/testing-modes
评论
支持 Markdown