roblox-api-patterns
π―Skillfrom taozhuo/game-dev-skills
Provides expert guidance on navigating Roblox-specific API behaviors, performance optimizations, and common coding pitfalls in Lua scripting.
Installation
npx skills add https://github.com/taozhuo/game-dev-skills --skill roblox-api-patternsSkill Details
Provides Roblox-specific API patterns, gotchas, best practices, and platform knowledge. Use when you need to understand Roblox-specific behaviors, services, or common pitfalls.
Overview
# Roblox API Patterns & Gotchas
When working with Roblox APIs, be aware of these platform-specific behaviors and patterns.
Common Gotchas
Instance.new() - Set Parent Last
```lua
-- BAD: Setting parent first causes events to fire during setup
local part = Instance.new("Part")
part.Parent = workspace -- Events fire here!
part.Size = Vector3.new(5, 5, 5) -- Property changes after parenting
-- GOOD: Set all properties, then parent
local part = Instance.new("Part")
part.Size = Vector3.new(5, 5, 5)
part.Position = Vector3.new(0, 10, 0)
part.Anchored = true
part.Parent = workspace -- Only fires events once, fully configured
-- BETTER: Use Instance.new with parent argument (deprecated but still works)
-- Actually, the second argument is being phased out. Stick with setting parent last.
```
WaitForChild Timeout
```lua
-- BAD: Can hang indefinitely if child doesn't exist
local child = parent:WaitForChild("MaybeExists")
-- GOOD: Use timeout
local child = parent:WaitForChild("MaybeExists", 5) -- 5 second timeout
if child then
-- Child found
else
warn("Child not found after 5 seconds")
end
```
Touched Events Fire Multiple Times
```lua
-- BAD: Can trigger multiple times per contact
part.Touched:Connect(function(hit)
dealDamage(hit) -- May deal damage multiple times!
end)
-- GOOD: Debounce
local debounce = {}
part.Touched:Connect(function(hit)
local character = hit.Parent
if debounce[character] then return end
debounce[character] = true
dealDamage(hit)
task.delay(0.5, function()
debounce[character] = nil
end)
end)
```
os.clock() vs tick() vs time()
```lua
-- os.clock(): High-resolution timer, best for measuring elapsed time
local start = os.clock()
-- ... code ...
local elapsed = os.clock() - start
-- tick(): DEPRECATED, don't use in new code
-- time(): Returns time since game started, affected by time scale
local gameTime = time()
-- workspace:GetServerTimeNow(): Synchronized server time
local serverTime = workspace:GetServerTimeNow()
-- os.time(): Unix timestamp (seconds since 1970)
local unixTime = os.time()
```
Humanoid States
```lua
-- Humanoid states can be unreliable
-- Don't rely solely on StateChanged
local humanoid = character:WaitForChild("Humanoid")
-- Check state directly
local function isGrounded()
-- FloorMaterial is more reliable than state
return humanoid.FloorMaterial ~= Enum.Material.Air
end
-- Or use raycasts
local function isGroundedRaycast()
local hrp = character:FindFirstChild("HumanoidRootPart")
if not hrp then return false end
local result = workspace:Raycast(
hrp.Position,
Vector3.new(0, -3.1, 0), -- Slightly more than hip height
RaycastParams.new()
)
return result ~= nil
end
```
GetChildren vs GetDescendants
```lua
-- GetChildren: Only immediate children
local children = model:GetChildren()
-- GetDescendants: All descendants (recursive)
local allParts = model:GetDescendants()
-- Both return tables that are snapshots
-- Adding/removing during iteration is safe but won't be reflected
for _, child in ipairs(model:GetChildren()) do
child:Destroy() -- Safe, iterating over snapshot
end
```
Service Patterns
Players Service
```lua
local Players = game:GetService("Players")
-- Get local player (client only)
local localPlayer = Players.LocalPlayer
-- Player events
Players.PlayerAdded:Connect(function(player)
-- Note: Character may not exist yet
player.CharacterAdded:Connect(function(character)
-- Character loaded
end)
-- If character already exists (late connection)
if player.Character then
-- Handle existing character
end
end)
-- PlayerRemoving fires BEFORE player is removed
Players.PlayerRemoving:Connect(function(player)
-- Save data here, player still valid
end)
```
RunService
```lua
local RunService = game:GetService("RunService")
-- Different update events
RunService.PreRender:Connect(function(dt)
-- Before rendering (client only, after input)
end)
RunService.PreAnimation:Connect(function(dt)
-- Before animation update
end)
RunService.PreSimulation:Connect(function(dt)
-- Before physics
end)
RunService.PostSimulation:Connect(function(dt)
-- After physics (same as Stepped)
end)
RunService.Heartbeat:Connect(function(dt)
-- After physics and before rendering
-- Most common for game logic
end)
RunService.RenderStepped:Connect(function(dt)
-- Every frame, client only
-- Use for camera/visual updates
end)
-- Check context
if RunService:IsClient() then
-- Client-side code
end
if RunService:IsServer() then
-- Server-side code
end
if RunService:IsStudio() then
-- Running in Studio
end
```
TweenService
```lua
local TweenService = game:GetService("TweenService")
-- Create tween
local tweenInfo = TweenInfo.new(
1, -- Duration
Enum.EasingStyle.Quad, -- EasingStyle
Enum.EasingDirection.Out, -- EasingDirection
0, -- RepeatCount (0 = no repeat)
false, -- Reverses
0 -- DelayTime
)
local tween = TweenService:Create(part, tweenInfo, {
Position = Vector3.new(0, 10, 0),
Transparency = 0.5
})
tween:Play()
-- Wait for completion
tween.Completed:Wait()
-- or
tween.Completed:Connect(function(playbackState)
if playbackState == Enum.PlaybackState.Completed then
-- Finished normally
elseif playbackState == Enum.PlaybackState.Cancelled then
-- Was cancelled
end
end)
-- Cancel tween
tween:Cancel()
-- Tweening CFrame (doesn't lerp, use custom for smooth rotation)
-- Use CFrame:Lerp() manually for better control
```
DataStoreService Limits
```lua
local DataStoreService = game:GetService("DataStoreService")
-- Rate limits:
-- GetAsync: 60 + numPlayers * 10 per minute
-- SetAsync: 60 + numPlayers * 10 per minute
-- UpdateAsync: 60 + numPlayers * 10 per minute
-- GetSortedAsync: 5 + numPlayers per minute
-- SetSortedAsync: 60 + numPlayers * 10 per minute
-- Data limits:
-- Key length: 50 characters max
-- Value size: 4MB max
-- Key format: Only alphanumeric and underscores
-- Budget system
local budget = DataStoreService:GetRequestBudgetForRequestType(
Enum.DataStoreRequestType.GetAsync
)
print("GetAsync budget:", budget)
```
MarketplaceService
```lua
local MarketplaceService = game:GetService("MarketplaceService")
-- Check ownership
local function ownsGamepass(player, gamepassId)
local success, owns = pcall(function()
return MarketplaceService:UserOwnsGamePassAsync(player.UserId, gamepassId)
end)
return success and owns
end
-- Developer Products (consumable)
MarketplaceService.ProcessReceipt = function(receiptInfo)
-- MUST return a value
-- PurchaseGranted: Purchase successful, receipt cleared
-- NotProcessedYet: Try again later (player rejoins, etc.)
local player = Players:GetPlayerByUserId(receiptInfo.PlayerId)
if not player then
return Enum.ProductPurchaseDecision.NotProcessedYet
end
-- Grant product...
return Enum.ProductPurchaseDecision.PurchaseGranted
end
-- Prompt purchase
MarketplaceService:PromptProductPurchase(player, productId)
MarketplaceService:PromptGamePassPurchase(player, gamepassId)
```
RemoteEvent/Function Patterns
Type Safety
```lua
-- Client sends data, server receives
-- NEVER trust client data types
RemoteEvent.OnServerEvent:Connect(function(player, data)
-- Validate types
if type(data) ~= "table" then return end
if type(data.action) ~= "string" then return end
if type(data.value) ~= "number" then return end
-- Validate ranges
if data.value < 0 or data.value > 100 then return end
if data.value ~= data.value then return end -- NaN check
-- Now safe to use
end)
```
RemoteFunction Timeout
```lua
-- RemoteFunctions can hang if server/client doesn't respond
-- Wrap in pcall and use manual timeout
local function safeInvoke(remote, ...)
local args = {...}
local result
local finished = false
task.spawn(function()
local success, response = pcall(function()
return remote:InvokeServer(unpack(args))
end)
if success then
result = response
end
finished = true
end)
-- Timeout after 10 seconds
local startTime = os.clock()
while not finished and os.clock() - startTime < 10 do
task.wait()
end
return result
end
```
Physics Patterns
Network Ownership
```lua
-- Parts have network owners that control physics simulation
local part = workspace.MyPart
-- Set owner to specific player
part:SetNetworkOwner(player)
-- Set owner to server (nil)
part:SetNetworkOwner(nil)
-- Get current owner
local owner = part:GetNetworkOwner()
-- Check if can set
if part:CanSetNetworkOwnership() then
part:SetNetworkOwner(player)
end
-- Anchored parts have no network owner
-- Welded parts inherit owner from root part
```
AssemblyLinearVelocity vs Velocity
```lua
-- Velocity is DEPRECATED
-- Use AssemblyLinearVelocity and AssemblyAngularVelocity
local part = workspace.MyPart
-- Get velocity
local linearVel = part.AssemblyLinearVelocity
local angularVel = part.AssemblyAngularVelocity
-- Set velocity (only works if you're network owner)
part.AssemblyLinearVelocity = Vector3.new(0, 50, 0)
-- For applying forces, use constraints instead
local attachment = Instance.new("Attachment", part)
local linearVelocity = Instance.new("LinearVelocity")
linearVelocity.Attachment0 = attachment
linearVelocity.VectorVelocity = Vector3.new(0, 50, 0)
linearVelocity.MaxForce = math.huge
linearVelocity.Parent = part
```
Animation Patterns
Loading Animations
```lua
local humanoid = character:WaitForChild("Humanoid")
local animator = humanoid:WaitForChild("Animator")
-- Load animation
local animation = Instance.new("Animation")
animation.AnimationId = "rbxassetid://123456789"
local track = animator:LoadAnimation(animation)
-- Play with settings
track:Play(
0.1, -- Fade in time
1, -- Weight
1 -- Speed
)
-- Animation priorities
track.Priority = Enum.AnimationPriority.Action -- Highest
-- Core < Idle < Movement < Action
-- Animation events
track:GetMarkerReachedSignal("FootstepMarker"):Connect(function()
playFootstepSound()
end)
-- Stop
track:Stop(0.1) -- Fade out time
```
Lighting Patterns
Time of Day
```lua
local Lighting = game:GetService("Lighting")
-- Set time (0-24 hours)
Lighting.ClockTime = 14.5 -- 2:30 PM
-- Animate day/night cycle
local function updateTimeOfDay()
Lighting.ClockTime = (Lighting.ClockTime + 0.001) % 24
end
-- Geographic time (affects sun angle)
Lighting.GeographicLatitude = 41.9 -- New York latitude
```
Post-Processing
```lua
-- Add to Lighting or Camera
local blur = Instance.new("BlurEffect")
blur.Size = 10
blur.Parent = Lighting
local colorCorrection = Instance.new("ColorCorrectionEffect")
colorCorrection.Brightness = 0.1
colorCorrection.Contrast = 0.2
colorCorrection.Saturation = -0.1
colorCorrection.Parent = Lighting
local bloom = Instance.new("BloomEffect")
bloom.Intensity = 0.5
bloom.Size = 24
bloom.Threshold = 0.95
bloom.Parent = Lighting
```
Common Attribute Patterns
Using Attributes
```lua
-- Set attribute
part:SetAttribute("Health", 100)
part:SetAttribute("Team", "Red")
part:SetAttribute("IsActive", true)
-- Get attribute
local health = part:GetAttribute("Health")
-- Get all attributes
local attributes = part:GetAttributes()
for name, value in pairs(attributes) do
print(name, value)
end
-- Listen for changes
part:GetAttributeChangedSignal("Health"):Connect(function()
local newHealth = part:GetAttribute("Health")
end)
-- Attributes replicate automatically (server -> client)
-- Attributes persist with the instance
```
Debugging Patterns
Print Debugging
```lua
-- Use warn for visibility
warn("Important message")
-- Structured logging
local function log(level, message, data)
local timestamp = os.date("%H:%M:%S")
local output = string.format("[%s] [%s] %s", timestamp, level, message)
if data then
output = output .. " " .. HttpService:JSONEncode(data)
end
print(output)
end
log("INFO", "Player joined", {userId = player.UserId})
```
Error Handling
```lua
-- Always wrap risky operations
local success, result = pcall(function()
return riskyOperation()
end)
if success then
-- Use result
else
warn("Operation failed:", result)
end
-- With xpcall for stack traces
local success, result = xpcall(function()
return riskyOperation()
end, function(err)
warn("Error:", err)
warn(debug.traceback())
end)
```
More from this repository10
Manages audio systems with advanced sound pooling, priority management, and performance optimization for immersive game sound experiences.
optimization skill from taozhuo/game-dev-skills
Implements robust game systems like inventory, shops, trading, and progression mechanics for Roblox RPGs with secure, stackable item management.
animation-system skill from taozhuo/game-dev-skills
Generates dynamic visual effects like particle systems, camera shakes, and impact animations for enhancing game experiences in Roblox.
Generates conceptual game art images using Gemini AI, providing precise style-specific prompts for character, environment, and UI design across various game aesthetics.
Enables comprehensive combat mechanics with advanced hitbox detection, damage calculation, and hit validation for creating dynamic player-vs-player and player-vs-NPC combat interactions.
security-anticheat skill from taozhuo/game-dev-skills
Manages reliable Roblox data persistence by implementing robust data loading, saving, caching, and error handling for player progress and game state.
Generates procedurally randomized content like terrain, dungeons, and cities using advanced noise algorithms and algorithmic techniques.