name: roblox-api-patterns description: Provides Roblox-specific API patterns, gotchas, best practices, and platform knowledge. Use when you need to understand Roblox-specific behaviors, services, or common pitfalls. allowed-tools: Read, Write, Edit, Glob, Grep
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
-- 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
-- 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
-- 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()
-- 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
-- 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
-- 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
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
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
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
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
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
-- 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
-- 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
-- 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
-- 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
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
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
-- 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
-- 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
-- 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
-- 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)
Skills similaires
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Claude CodeCursoradvanced
890
234
2,619
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
claudeCursorWindsurfbeginner
259
72
589
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.
claudeCursorWindsurfintermediate
156
44
451