roblox-luau

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Roblox Luau Scripting

Roblox Luau 脚本编写

Script a Roblox experience in Luau: services,
Instance
s, events, the server/client split, and secure cross-boundary communication. Targets the current Roblox engine and Studio.
使用Luau编写Roblox体验脚本:服务、
Instance
、事件、服务器/客户端分离,以及安全的跨边界通信。针对当前Roblox引擎与Studio。

When to use

使用场景

  • Use when writing Roblox scripts: getting services, creating/parenting instances, connecting events, deciding server vs client, or wiring
    RemoteEvent
    /
    RemoteFunction
    communication.
  • Use when the project has
    Script
    /
    LocalScript
    /
    ModuleScript
    objects,
    .rbxl(x)
    places, or a Rojo
    *.project.json
    , and code calls
    game:GetService(...)
    .
When not to use: persisting data across sessions →
roblox-datastores
. Generic Lua questions unrelated to the Roblox API. Engine-agnostic input/save architecture →
input-systems
/
save-systems
.
  • 编写Roblox脚本时使用:获取服务、创建/设置实例父对象、连接事件、判断代码运行在服务器还是客户端,或者配置
    RemoteEvent
    /
    RemoteFunction
    通信。
  • 项目包含
    Script
    /
    LocalScript
    /
    ModuleScript
    对象、
    .rbxl(x)
    场景文件,或Rojo
    *.project.json
    ,且代码调用
    game:GetService(...)
    时使用。
**不适用场景:**跨会话持久化数据 → 使用
roblox-datastores
。与Roblox API无关的通用Lua问题。与引擎无关的输入/存储架构 → 使用
input-systems
/
save-systems

Core workflow

核心工作流程

  1. Get services with
    game:GetService("Name")
    .
    Common ones:
    Players
    ,
    Workspace
    ,
    ReplicatedStorage
    (shared client+server),
    ServerScriptService
    (server-only code),
    ServerStorage
    ,
    RunService
    ,
    UserInputService
    (client).
  2. Know where code runs. A
    Script
    runs on the server; a
    LocalScript
    runs on a client (in
    StarterPlayerScripts
    ,
    StarterGui
    , or the player's character). A
    ModuleScript
    is shared code you
    require
    .
  3. Create instances deliberately.
    local p = Instance.new("Part")
    , set its properties, then set
    p.Parent
    last (parenting triggers replication).
  4. React with events.
    :Connect
    to signals like
    Players.PlayerAdded
    ,
    part.Touched
    , or
    RunService.Heartbeat
    . Disconnect when done to avoid leaks.
  5. Cross the client/server boundary with Remotes — and never trust the client. Clients request via
    RemoteEvent:FireServer(...)
    ; the server validates and applies. The server is authoritative for all game state.
  6. Test in Studio with Play / Play Here / server+client Start; use the Output window and the server/client view toggle to confirm where code ran.
  1. 使用
    game:GetService("Name")
    获取服务
    。常见服务包括:
    Players
    Workspace
    ReplicatedStorage
    (客户端与服务器共享)、
    ServerScriptService
    (仅服务器代码)、
    ServerStorage
    RunService
    UserInputService
    (客户端)。
  2. 明确代码运行位置
    Script
    运行在服务器
    LocalScript
    运行在客户端(位于
    StarterPlayerScripts
    StarterGui
    或玩家角色中)。
    ModuleScript
    是可通过
    require
    调用的共享代码。
  3. 谨慎创建实例
    local p = Instance.new("Part")
    ,设置其属性,最后再设置
    p.Parent
    (设置父对象会触发复制)。
  4. 通过事件响应操作。使用
    :Connect
    连接
    Players.PlayerAdded
    part.Touched
    RunService.Heartbeat
    等信号。使用完毕后断开连接以避免内存泄漏。
  5. 通过Remotes实现跨客户端/服务器通信——绝不信任客户端。客户端通过
    RemoteEvent:FireServer(...)
    发起请求;服务器验证后执行操作。服务器对所有游戏状态拥有权威控制权。
  6. 在Studio中测试:使用Play / Play Here / 服务器+客户端启动;使用输出窗口和服务器/客户端视图切换按钮确认代码运行位置。

Patterns

模式示例

1. Server Script: react to players joining (leaderstats)

1. 服务器脚本:响应玩家加入(排行榜)

lua
-- ServerScriptService/Leaderboard.server.luau  (a Script = runs on the server)
local Players = game:GetService("Players")

local function onPlayerAdded(player: Player)
    local stats = Instance.new("Folder")
    stats.Name = "leaderstats"          -- this name makes it show on the leaderboard

    local coins = Instance.new("IntValue")
    coins.Name = "Coins"
    coins.Value = 0
    coins.Parent = stats

    stats.Parent = player               -- parent LAST
end

Players.PlayerAdded:Connect(onPlayerAdded)
lua
-- ServerScriptService/Leaderboard.server.luau  (Script = 在服务器运行)
local Players = game:GetService("Players")

local function onPlayerAdded(player: Player)
    local stats = Instance.new("Folder")
    stats.Name = "leaderstats"          -- 此名称会让数据显示在排行榜上

    local coins = Instance.new("IntValue")
    coins.Name = "Coins"
    coins.Value = 0
    coins.Parent = stats

    stats.Parent = player               -- 最后设置父对象
end

Players.PlayerAdded:Connect(onPlayerAdded)

2. Create and configure an instance

2. 创建并配置实例

lua
local Workspace = game:GetService("Workspace")

local part = Instance.new("Part")
part.Size = Vector3.new(4, 1, 4)
part.Position = Vector3.new(0, 10, 0)
part.Anchored = true                    -- won't fall under gravity
part.BrickColor = BrickColor.new("Bright blue")
part.Parent = Workspace                 -- set Parent last so it replicates once, fully
lua
local Workspace = game:GetService("Workspace")

local part = Instance.new("Part")
part.Size = Vector3.new(4, 1, 4)
part.Position = Vector3.new(0, 10, 0)
part.Anchored = true                    -- 不会受重力影响掉落
part.BrickColor = BrickColor.new("Bright blue")
part.Parent = Workspace                 -- 最后设置Parent,确保实例以完整状态仅复制一次

3. Connect an event (and disconnect to avoid leaks)

3. 连接事件(并断开连接避免泄漏)

lua
local debounce = false
local connection
connection = part.Touched:Connect(function(hit: BasePart)
    local character = hit.Parent
    local humanoid = character and character:FindFirstChildOfClass("Humanoid")
    if not humanoid or debounce then return end
    debounce = true
    humanoid.Health -= 10
    task.wait(1)                        -- task.wait, NOT the deprecated wait()
    debounce = false
end)

-- Later, when the part is removed or the round ends:
-- connection:Disconnect()
lua
local debounce = false
local connection
connection = part.Touched:Connect(function(hit: BasePart)
    local character = hit.Parent
    local humanoid = character and character:FindFirstChildOfClass("Humanoid")
    if not humanoid or debounce then return end
    debounce = true
    humanoid.Health -= 10
    task.wait(1)                        -- 使用task.wait,而非已弃用的wait()
    debounce = false
end)

-- 后续当部件被移除或回合结束时:
-- connection:Disconnect()

4. Client → server with a RemoteEvent (validate on the server!)

4. 通过RemoteEvent实现客户端→服务器通信(务必在服务器验证!)

lua
-- ReplicatedStorage: create a RemoteEvent named "BuyItem" (in Studio or via code).
-- CLIENT (LocalScript): request a purchase. The client can lie — this is only a request.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buyItem = ReplicatedStorage:WaitForChild("BuyItem")  -- wait: may not have replicated yet
buyButton.MouseButton1Click:Connect(function()
    buyItem:FireServer("sword")        -- send the item id only; never the price/result
end)
lua
-- SERVER (Script): the ONLY place the transaction is decided.
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buyItem = ReplicatedStorage:WaitForChild("BuyItem")
local PRICES = { sword = 100, shield = 75 }

buyItem.OnServerEvent:Connect(function(player: Player, itemId)
    -- TRUST NOTHING from the client. Validate types and values.
    if type(itemId) ~= "string" then return end
    local price = PRICES[itemId]
    if not price then return end                         -- unknown item
    local coins = player.leaderstats.Coins
    if coins.Value < price then return end               -- can't afford
    coins.Value -= price                                 -- server applies the change
    grantItem(player, itemId)
end)
lua
-- 在ReplicatedStorage中创建名为"BuyItem"的RemoteEvent(可在Studio中创建或通过代码创建)。
-- 客户端(LocalScript):发起购买请求。客户端可能造假——这只是请求。
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buyItem = ReplicatedStorage:WaitForChild("BuyItem")  -- 使用WaitForChild:对象可能尚未完成复制
buyButton.MouseButton1Click:Connect(function()
    buyItem:FireServer("sword")        -- 仅发送物品ID;绝不要发送价格或结果
end)
lua
-- 服务器(Script):交易的唯一决策地点。
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local buyItem = ReplicatedStorage:WaitForChild("BuyItem")
local PRICES = { sword = 100, shield = 75 }

buyItem.OnServerEvent:Connect(function(player: Player, itemId)
    -- 绝不信任客户端传来的任何内容。验证参数的类型与值。
    if type(itemId) ~= "string" then return end
    local price = PRICES[itemId]
    if not price then return end                         -- 未知物品
    local coins = player.leaderstats.Coins
    if coins.Value < price then return end               -- 资金不足
    coins.Value -= price                                 -- 服务器执行扣款操作
    grantItem(player, itemId)
end)

5. A per-frame loop with RunService

5. 使用RunService实现逐帧循环

lua
local RunService = game:GetService("RunService")
-- Heartbeat fires every frame AFTER physics; dt is seconds since the last step.
RunService.Heartbeat:Connect(function(dt)
    spinner.CFrame *= CFrame.Angles(0, math.rad(90) * dt, 0)  -- 90deg/sec, frame-independent
end)
lua
local RunService = game:GetService("RunService")
-- Heartbeat在每帧物理计算后触发;dt为自上一帧以来的秒数。
RunService.Heartbeat:Connect(function(dt)
    spinner.CFrame *= CFrame.Angles(0, math.rad(90) * dt, 0)  -- 每秒90度,与帧率无关
end)

6. Shared code in a ModuleScript

6. ModuleScript中的共享代码

lua
-- ReplicatedStorage/GameConfig (a ModuleScript) — usable by server and client.
local GameConfig = {}
GameConfig.MaxHealth = 100
function GameConfig.damageFor(weapon: string): number
    return ({ sword = 25, bow = 15 })[weapon] or 0
end
return GameConfig
lua
local GameConfig = require(game:GetService("ReplicatedStorage"):WaitForChild("GameConfig"))
print(GameConfig.MaxHealth)
lua
-- ReplicatedStorage/GameConfig(ModuleScript)——服务器与客户端均可使用。
local GameConfig = {}
GameConfig.MaxHealth = 100
function GameConfig.damageFor(weapon: string): number
    return ({ sword = 25, bow = 15 })[weapon] or 0
end
return GameConfig
lua
local GameConfig = require(game:GetService("ReplicatedStorage"):WaitForChild("GameConfig"))
print(GameConfig.MaxHealth)

Pitfalls

常见陷阱

  • Trusting the client is an exploit → clients can send any arguments to a
    RemoteEvent
    /
    RemoteFunction
    . Validate every argument's type and range on the server and keep the server authoritative over health, currency, and inventory.
  • LocalScript
    doesn't run where you put it
    → LocalScripts run in
    StarterPlayerScripts
    ,
    StarterCharacterScripts
    ,
    StarterGui
    , or tools — not in
    Workspace
    or
    ServerScriptService
    . Server
    Script
    s belong in
    ServerScriptService
    /
    Workspace
    .
  • Deprecated globals → use
    task.wait
    /
    task.spawn
    /
    task.delay
    , not the old
    wait()
    /
    spawn()
    /
    delay()
    (worse scheduling and throttling).
  • Parenting first, then setting properties → set properties first and
    Parent
    last so the instance replicates once in its final state.
  • nil
    on the client right after join
    → objects stream/replicate over time; use
    parent:WaitForChild("Name")
    instead of indexing directly on the client.
  • Connections never disconnected → long-lived
    :Connect
    handlers leak and can fire on destroyed objects; store the connection and
    :Disconnect()
    (or use
    Instance:GetAttributeChangedSignal
    /
    :Once
    where appropriate).
  • Using a RemoteFunction where a RemoteEvent fits
    RemoteFunction
    blocks waiting for a return and a malicious/slow client can stall the server; prefer one-way
    RemoteEvent
    s unless you genuinely need a reply.
  • 信任客户端会导致漏洞 → 客户端可向
    RemoteEvent
    /
    RemoteFunction
    发送任意参数。务必在服务器验证每个参数的类型与范围,服务器需对生命值、货币和物品拥有权威控制权。
  • LocalScript未在预期位置运行 → LocalScript仅在
    StarterPlayerScripts
    StarterCharacterScripts
    StarterGui
    或工具中运行——不会在
    Workspace
    ServerScriptService
    中运行。服务器
    Script
    应放在
    ServerScriptService
    /
    Workspace
    中。
  • 已弃用的全局函数 → 使用
    task.wait
    /
    task.spawn
    /
    task.delay
    ,而非旧版
    wait()
    /
    spawn()
    /
    delay()
    (调度性能更差且存在节流问题)。
  • 先设置父对象再设置属性 → 应先设置属性,最后设置
    Parent
    ,确保实例以最终状态仅复制一次。
  • 客户端刚加入时对象为nil → 对象会随时间流式传输/复制;在客户端使用
    parent:WaitForChild("Name")
    而非直接索引。
  • 连接从未断开 → 长期存在的
    :Connect
    处理程序会导致内存泄漏,且可能在对象销毁后仍触发;需保存连接并调用
    :Disconnect()
    (或在合适场景使用
    Instance:GetAttributeChangedSignal
    /
    :Once
    )。
  • 在适合用RemoteEvent的场景使用RemoteFunction
    RemoteFunction
    会阻塞等待返回结果,恶意或响应缓慢的客户端可能导致服务器卡顿;除非确实需要回复,否则优先使用单向的
    RemoteEvent

References

参考资料

  • For the full client/server model (replication,
    RemoteFunction
    vs
    RemoteEvent
    ,
    :WaitForChild
    timing,
    BindableEvent
    for same-context messaging, attributes,
    CollectionService
    tags, and
    :Once
    /connection cleanup), read
    references/client-server.md
    .
  • 如需了解完整的客户端/服务器模型(复制机制、
    RemoteFunction
    RemoteEvent
    对比、
    :WaitForChild
    时机、同上下文通信的
    BindableEvent
    、属性、
    CollectionService
    标签,以及
    :Once
    /连接清理),请阅读
    references/client-server.md

Related skills

相关技能

  • roblox-datastores
    — persist player data across sessions (server-only).
  • save-systems
    — engine-agnostic persistence concepts.
  • game-ai
    /
    input-systems
    — portable AI and input patterns to implement in Luau.
  • roblox-datastores
    —— 跨会话持久化玩家数据(仅服务器可用)。
  • save-systems
    —— 与引擎无关的持久化概念。
  • game-ai
    /
    input-systems
    —— 可在Luau中实现的通用AI与输入模式。