mcpField
R
roblox-luau

roblox-luau

github.com/el4cteo开发59602 次安装2 周前更新
代码仓库

关于 roblox-luau

roblox-luau 是用于编写和审查 Roblox 现代 Luau 脚本的编码技能,强调严格类型、当前 API、安全异步操作、资源清理和性能实践。

主要功能

  1. 01采用严格类型和现代 Luau 语法
  2. 02建议使用当前 Roblox API,避免弃用接口
  3. 03涵盖错误处理与 task 并发
  4. 04指导连接和实例清理
  5. 05提供高负载代码的性能实践

适用场景

  1. 01编写新的 Roblox ModuleScripts
  2. 02更新使用弃用 API 的脚本
  3. 03检查异步调用和资源清理

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/el4cteo/roblox-skills --skill roblox-luau

安装后在 Claude Code 中输入/roblox-luau确认已加载。

附带文件references/stdlib-cheatsheet.mdreferences/type-system.md

详细介绍

面向 Roblox 的现代 Luau

Luau 是 Roblox 对 Lua 5.1 的渐进类型化衍生版本。引擎和语言迭代很快;许多 来自旧教程(以及旧训练数据)的模式已被弃用。除非项目明确采用其他约定, 否则请对你编写的每一个脚本套用以下默认规则。

所有生成代码的默认规则

  1. 在新的 ModuleScript(以及可行时的 Script)顶部添加 --!strict。为公开函数添加类型标注。
  2. 在文件顶部使用 game:GetService("Name") 获取服务,绝不要用 game.Name。
  3. 使用 task:task.wait、task.spawn、task.defer、task.delay、task.cancel。 绝不要用 wait、spawn、delay(已弃用、受限流、不精确)。
  4. 可能失败的让出式引擎调用(DataStore、HTTP、Marketplace、Teleport、*Async)要放在 pcall 内。
  5. 优先使用 API 的 *Async 变体;Roblox 重命名了大多数让出式方法 (LoadCharacterAsync、GetProductInfoAsync、UserHasBadgeAsync……)。见下表。
  6. 断开连接 / 销毁你创建的实例(见下文“清理”)。
  7. 命名:服务、模块、类、类型和实例名用 PascalCase; 局部变量和函数用 camelCase;常量用 SCREAMING_SNAKE_CASE。
  8. 缩进用制表符(Roblox 生态中 StyLua 的默认值),字符串用双引号。

现代语法速查

local Players = game:GetService("Players")

local count = 0
count += 1 -- compound assignment: += -= *= /= //= %= ^= ..=

local label = if count > 1 then "many" else "one" -- if-expression (no `and/or` pitfalls)
print(`Player count: {#Players:GetPlayers()} ({label})`) -- string interpolation

for index, player in Players:GetPlayers() do -- generalized iteration: no pairs/ipairs needed
	if player.AccountAge < 1 then
		continue -- continue is a keyword
	end
	print(index, player.Name)
end

local half = 7 // 2 -- floor division → 3
local frozen = table.freeze({ Speed = 16 }) -- read-only table; writes throw
local list = table.create(100, 0) -- preallocated array
local copy = table.clone(frozen) -- shallow copy (unfrozen)
local mid = math.lerp(0, 10, 0.5) -- also: math.map, math.clamp, math.round, math.sign
print(half, list[1], copy.Speed, mid)
  • pairs/ipairs 仍可用;更推荐广义迭代,它会按顺序遍历数组。
  • #t 仅对无空洞的数组有定义。在中间设置 t[i] = nil 会产生空洞。
  • 数字为双精度浮点数:整数在 2^53 以内精确。整数运算请用 math.floor///。
  • 不推荐 tick():基准测试用 os.clock(),跨客户端/服务端的同步时间用 workspace:GetServerTimeNow(),UTC 墙上时钟用 os.time()/DateTime.now()。

类型(新类型求解器)

Roblox 的新类型求解器已正式可用(2025 年 11 月),由 Workspace.UseNewLuauTypeSolver 控制;Workspace.LuauTypeCheckMode 设置默认模式。编写能在 --!strict 下通过检查的代码。

--!strict
export type Item = {
	id: string,
	name: string,
	stack: number,
	tags: { string }?, -- optional field
}

type Rarity = "Common" | "Rare" | "Legendary" -- singleton (literal) union

local function totalStack(items: { Item }, filter: ((Item) -> boolean)?): number
	local total = 0
	for _, item in items do
		if filter == nil or filter(item) then
			total += item.stack
		end
	end
	return total
end

local function first<T>(list: { T }): T? -- generics
	return list[1]
end

local rarity: Rarity = "Rare"
local part = workspace:FindFirstChild("Door") :: BasePart? -- cast with ::
print(totalStack({}, nil), first({ 1, 2 }), rarity, part)
  • 使用前先收窄:if part then ... end、if typeof(x) == "Instance" and x:IsA("BasePart") then。
  • typeof(v) 认识 Roblox 类型("Vector3"、"Instance"……);type(v) 只认识 Lua 类型。
  • 实例类型来自 IsA 收窄或 FindFirstChildOfClass/FindFirstChildWhichIsA。
  • 避免 any,除非在信任边界处(远程载荷、解码的 JSON)——在那里做校验,然后再收窄。

详情:泛型、类型包、read/write 属性、用户定义类型函数、为 OOP 类添加类型, 以及将遗留代码迁移到严格模式:references/type-system.md。

模块与 require

ModuleScript 在每个环境(服务端或每个客户端)中运行一次并返回一个值,为后续每次 require 缓存。位于 ReplicatedStorage 中的模块若被两端 require,会运行两次,每端各一次, 状态相互独立。

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Signal = require(ReplicatedStorage.Packages.Signal) -- instance path (always works)
local Config = require("./Config") -- require-by-string: sibling of this script (script.Parent)
local Utils = require("@self/Utils") -- child of this script
local Shared = require("@game/ReplicatedStorage/Shared") -- absolute from game
print(Signal, Config, Utils, Shared)
  • 字符串路径:./ = script.Parent,../ = script.Parent.Parent,@self/ = script, @game/ = game。它们不会等待实例存在。当模块可能尚未复制过来时, 请用实例路径配合 WaitForChild。
  • 绝不要创建循环 require;它们会死锁/报错。用依赖注入或事件打破循环。
  • 不要在模块顶层让出(不要对可能永不存在的对象做 WaitForChild 链,不要调用 DataStore):每个 requirer 都会阻塞直到模块返回。
模块 / 类模式
--!strict
local Counter = {}
Counter.__index = Counter

export type Counter = typeof(setmetatable({} :: { value: number, step: number }, Counter))

function Counter.new(step: number?): Counter
	return setmetatable({ value = 0, step = step or 1 }, Counter)
end

function Counter.increment(self: Counter): number
	self.value += self.step
	return self.value
end

return Counter

优先使用函数 + 数据表的普通模块,而非深层继承。使用组合。

错误与重试

local DataStoreService = game:GetService("DataStoreService")
local store = DataStoreService:GetDataStore("PlayerData")

local function retry<T...>(attempts: number, fn: () -> T...): (boolean, T...)
	local delaySeconds = 1
	for attempt = 1, attempts do
		local result = table.pack(pcall(fn))
		if result[1] then
			return table.unpack(result, 1, result.n)
		end
		if attempt < attempts then
			warn(`attempt {attempt} failed: {result[2]}`)
			task.wait(delaySeconds)
			delaySeconds *= 2 -- exponential backoff
		end
	end
	return false
end

local ok, data = retry(3, function()
	return store:GetAsync("Player_1")
end)
print(ok, data)
  • error("message", 2) 将错误归咎于调用方;error({ code = "X" }) 抛出表以实现结构化错误。
  • xpcall(fn, debug.traceback) 会保留堆栈回溯。
  • 不要把所有东西都包在 pcall 里——只包那些可能因你代码之外的原因而失败的调用。

并发:任务库

调用行为
task.spawn(fn, ...)立即在新线程上运行(直到第一次让出)。
task.defer(fn, ...)在当前恢复周期结束时运行。适合"在这完成之后"的场景。
task.delay(t, fn, ...)在 t 秒后运行。返回一个可用 task.cancel 取消的线程。
task.wait(t?)让出 ≥ t 秒(省略时为一阵);返回经过的时间。
task.cancel(thread)停止通过 spawn/delay 创建的线程。
task.desynchronize() / task.synchronize()并行 Luau(在 Actors 内)——参见 roblox-performance。
  • 每帧工作:RunService.Heartbeat(物理之后)、RunService.PreSimulation/PostSimulation、 RunService:BindToRenderStep(客户端,渲染之前,有序)。不要用 while true do task.wait() end 来做 每帧逻辑;连接到事件并使用其 deltaTime。
  • 编写能在延迟信号行为(Workspace.SignalBehavior)下工作的处理器:处理器会在 帧中稍后运行,而非内联。新模板位置使用 Deferred,Default 将切换到它, 且服务器权威要求它。不要依赖处理器的副作用在 :Fire() 之后立即可见。
  • 永远不要在元方法或必须同步的 Changed 风格回调中让出。

清理(内存泄漏是 Roblox 的头号 bug)

local Players = game:GetService("Players")

local connections: { RBXScriptConnection } = {}

table.insert(
	connections,
	Players.PlayerRemoving:Connect(function(player)
		print(`{player.Name} left`)
	end)
)

local function cleanup()
	for _, connection in connections do
		connection:Disconnect()
	end
	table.clear(connections)
end

cleanup()
  • Instance:Destroy() 会断开该实例的连接并锁定 Parent;它不会 清除你的 Lua 引用——在 PlayerRemoving 时把持有玩家/实例的表置为 nil。
  • 使用 :Once() 处理一次性连接。使用 Trove/Janitor/Maid 风格的辅助工具处理成组连接。
  • 长期存在的表中以 Player/Instance 对象作为键,除非在离开/销毁时移除,否则会泄漏。

Buffer 和向量(性能敏感的数据)

local packet = buffer.create(10)
buffer.writeu16(packet, 0, 513) -- item id
buffer.writef32(packet, 2, 12.5) -- amount
buffer.writei32(packet, 6, -4)
print(buffer.readu16(packet, 0), buffer.readf32(packet, 2), buffer.len(packet))

local v = vector.create(1, 2, 3) -- native 3-float vector (same type as Vector3 at runtime)
print(vector.magnitude(v), vector.normalize(v), vector.dot(v, vector.one))

buffer 是处理紧凑网络载荷、大型网格和二进制序列化的正确工具。 标准库速查:references/stdlib-cheatsheet.md。

旧写法 → 当前写法(最常见的错误)

不要写改为写
wait()、spawn()、delay()task.wait()、task.spawn()、task.delay()
game.Workspace、game.Playersworkspace、game:GetService("Players")
Instance.new("Part", parent)创建、设置属性,然后最后设置 .Parent
Humanoid:LoadAnimation()Animator:LoadAnimation()
Model:SetPrimaryPartCFrame()Model:PivotTo() / Model:GetPivot()
workspace:FindPartOnRay(Ray.new(...))workspace:Raycast(origin, direction, params)
BodyVelocity、BodyPosition、BodyGyroLinearVelocity、AlignPosition、AlignOrientation
player:LoadCharacter()player:LoadCharacterAsync()
MarketplaceService:GetProductInfo():GetProductInfoAsync()
BadgeService:AwardBadge():AwardBadgeAsync()
player:GetRankInGroup()GroupService:GetRolesInGroupAsync() / player:IsInGroupAsync()
TeleportService:Teleport*() 各变体TeleportService:TeleportAsync()
Chat 服务 / ChatService 模块TextChatService
GuiObject.DraggableUIDragDetector
table.getn、table.foreach、getfenv#t、for ... in、无

完整的弃用对照表位于 roblox-code-review 技能中。

性能惯用法(只在关键处使用)

  • 先测量(debug.profilebegin/profileend、MicroProfiler、Script Profiler)。
  • 在脚本顶部使用 --!native,或在函数上使用 @native,可将热点数值代码原生编译。 用在数学密集的模块上,而非到处使用(编译时间 + 内存限制)。类型标注对它有帮助。
  • 在循环外缓存 Instance 查找和服务引用;避免每帧进行 FindFirstChild 链式调用。
  • 用 table.concat 或 buffer 构建大字符串,不要在循环中反复使用 ..。
  • 复用 RaycastParams/OverlapParams 对象,而不是每次调用都创建。

相关技能

roblox-architecture(项目结构、客户端/服务端)、roblox-networking(远程通信)、 roblox-performance(性能分析、Parallel Luau)、roblox-code-review(审查清单、完整弃用映射)。

评论

支持 Markdown