/roblox-luau
Script a Roblox experience in Luau: get services, create and parent Instances, connect events, run server Scripts vs client LocalScripts, and communicate across the client/server boundary with RemoteEvents/RemoteFunctions (server-authoritative). Use when building or debugging
$ npx -y skills add gamedev-skills/awesome-gamedev-agent-skills --skill roblox-luau --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/roblox-luau
Context preview
The summary Claude sees to decide when to auto-load this skill.
Script a Roblox experience in Luau: get services, create and parent Instances, connect events, run server Scripts vs client LocalScripts, and communicate across the client/server boundary with RemoteEvents/RemoteFunctions (server-authoritative). Use when building or debugging
SKILL.md
roblox-luau.SKILL.mdname: roblox-luau
description: >
Script a Roblox experience in Luau: get services, create and parent Instances,
connect events, run server Scripts vs client LocalScripts, and communicate across
the client/server boundary with RemoteEvents/RemoteFunctions (server-authoritative).
Use when building or debugging Roblox Studio scripts — when the user mentions
Roblox, Luau, services, RemoteEvent, Instance.new, PlayerAdded, or client vs
server. For saving player data use roblox-datastores.
Roblox Luau Scripting
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.
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`.
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.
Patterns
1. Server Script: react to players joining (leaderstats)
-- 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)2. Create and configure an instance
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, fully3. Connect an event (and disconnect to avoid leaks)
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()4. Client → server with a RemoteEvent (validate on the server!)
-- 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)-- 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)5. A per-frame loop with RunService
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)6. Shared code in a ModuleScript
-- 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 GameConfiglocal Ga
Read more
name: roblox-luau description: > Script a Roblox experience in Luau: get services, create and parent Instances, connect events, run server Scripts vs client LocalScripts, and communicate across the client/server boundary with RemoteEvents/RemoteFunctions (server-authoritative). Use when building or debugging Roblox Studio scripts — when the user mentions Roblox, Luau, services, RemoteEvent, Instance.new, PlayerAdded, or client vs server. For saving player data use roblox-datastores.
Roblox Luau Scripting
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.
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`.
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.
Patterns
1. Server Script: react to players joining (leaderstats)
-- 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)2. Create and configure an instance
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, fully3. Connect an event (and disconnect to avoid leaks)
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()4. Client → server with a RemoteEvent (validate on the server!)
-- 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)-- 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)5. A per-frame loop with RunService
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)6. Shared code in a ModuleScript
-- 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 GameConfiglocal Ga
<img src="docs/assets/banner.png" width="820" alt="awesome-gamedev-agent-skills — game-dev skills for AI coding agents.
Repo: gamedev-skills/awesome-gamedev-agent-skills
Other skills on awesome-gamedev-agent-skills.
- /audio-design
Implement game audio practice — bus/mixer architecture and gain in decibels, ducking (sidechain), adaptive/dynamic music via layering and re-sequencing, SFX variation, and beat synchronization. Engine-neutral. Use when the user mentions audio mixing, audio buses,
Open skill - /camera-systems
Build game cameras that feel good — 2D follow with a deadzone, look-ahead, smoothing, and level-bounds clamping; 3D third-person orbit with collision and first-person look; plus multi-target framing and a shake hook. Engine-neutral techniques that pair with the engine's camera
Open skill - /create-game-assets
Plan, generate, source, normalize, and validate cohesive visual game assets. Use for art direction, style bibles, sprites, tilesets, backgrounds, UI art, icons, textures, concept art, or 3D asset briefs.
Open skill - /dialogue-systems
Build branching dialogue and narrative — a node/choice graph with conditions, variables, and localization hooks — and choose between authoring tools Ink and Yarn Spinner or a custom data-driven runner. Engine-neutral. Use when the user mentions dialogue system, branching
Open skill - /game-ai
Design NPC and enemy decision-making with finite state machines, behavior trees, steering behaviors, and A* pathfinding — engine-neutral algorithms that pair with the detected engine's navigation API. Use when building enemy AI, an FSM or behavior tree, steering/flocking, or
Open skill - /game-feel
Add "juice" and game feel that makes actions satisfying — screen shake, hit-stop/freeze frames, tweened/eased motion, squash & stretch, knockback, and layered audio-visual feedback — as engine-neutral techniques that pair with the detected engine's tween, particle, and camera
Open skill

