gamedev-skills/awesome-gamedev-agent-skills

roblox-datastores

Persist player data in Roblox with DataStoreService: GetDataStore, GetAsync/ SetAsync/UpdateAsync/IncrementAsync wrapped in pcall, load-on-join and save-on-leave plus BindToClose, retries, and OrderedDataStore leaderboards.

Ver código fuente
Documento original del Skill

Contenido del repositorio de origen con títulos, ejemplos, código, tablas, enlaces e imágenes preservados.

Roblox DataStores

Persist data across sessions in Roblox with DataStoreService: loading on join, saving on leave and shutdown, safe updates, retries, and ordered stores for leaderboards. Server-side only.

When to use

  • Use to save/load player progress (coins, inventory, levels), build persistent

leaderboards, or fix data loss, overwrites, and throttling.

  • Use when server code calls DataStoreService, GetDataStore, GetAsync,

SetAsync, UpdateAsync, or GetOrderedDataStore.

When not to use:* general scripting, services, remotes, the client/server split → roblox-luau. High-frequency temporary state (matchmaking, per-round) → memory stores (a different service). Engine-agnostic persistence theory → save-systems.

Core workflow

  1. Enable Studio access once. File → Game Settings → Security → *Enable Studio

Access to API Services* (use a test place; Studio hits live data). DataStores work only from server Scripts, never LocalScripts.

  1. Get a store, then read/write by key. DataStoreService:GetDataStore("Name");

key per player is usually "Player_" .. player.UserId.

  1. Wrap every call in `pcall`. GetAsync/SetAsync/UpdateAsync are network

calls that can fail; an unguarded failure errors the thread and risks data loss.

  1. Load on `PlayerAdded`, save on `PlayerRemoving`, and also `BindToClose`. A

leaving player and a shutting-down server both need a final save.

  1. Prefer `UpdateAsync` for read-modify-write (multi-server safe) over SetAsync

(blind overwrite). On a failed load, do not overwrite with defaults — abort the save so you don't wipe good data.

  1. Use `OrderedDataStore` for ranked data (leaderboards) via GetSortedAsync.

Test by joining, changing data, rejoining, and confirming it persisted.

Patterns

1. Load on join (pcall-guarded)

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

local DEFAULT = { Coins = 0, Level = 1 }

Players.PlayerAdded:Connect(function(player)
    local key = "Player_" .. player.UserId
    local ok, data = pcall(function()
        return store:GetAsync(key)
    end)

    if not ok then
        -- Load FAILED (network). Do not treat as a new player; flag so we never save
        -- over their real data with defaults.
        warn("Load failed for", player.Name, data)
        player:SetAttribute("DataLoaded", false)
        return
    end

    player:SetAttribute("DataLoaded", true)
    local profile = data or DEFAULT          -- nil == genuinely new player
    applyToLeaderstats(player, profile)
end)

2. Save with UpdateAsync (multi-server safe)

lua
-- UpdateAsync reads the latest value, then writes what the callback returns.
-- The callback MUST NOT yield (no task.wait, no further Async calls inside it).
local function savePlayer(player)
    if player:GetAttribute("DataLoaded") == false then return end  -- never overwrite on a bad load
    local key = "Player_" .. player.UserId
    local newData = gatherDataFor(player)    -- a plain table of serializable values

    local ok, err = pcall(function()
        store:UpdateAsync(key, function(old)
            -- merge/decide here; return nil to cancel the write
            return newData
        end)
    end)
    if not ok then warn("Save failed for", player.Name, err) end
end

3. Save on leave AND on shutdown

lua
Players.PlayerRemoving:Connect(savePlayer)

-- BindToClose runs when the server shuts down; save everyone still in.
-- It has a limited time budget, so save in parallel and yield until done.
game:BindToClose(function()
    local players = Players:GetPlayers()
    local remaining = #players
    if remaining == 0 then return end
    for _, player in players do
        task.spawn(function()
            savePlayer(player)
            remaining -= 1
        end)
    end
    while remaining > 0 do task.wait() end
end)

4. Retry with backoff (transient failures)

lua
local function withRetry(fn, attempts)
    attempts = attempts or 3
    for i = 1, attempts do
        local ok, result = pcall(fn)
        if ok then return true, result end
        if i < attempts then task.wait(2 ^ i) end   -- 2s, 4s, ... backoff
    end
    return false
end

local ok, data = withRetry(function() return store:GetAsync(key) end)

5. Increment a counter

lua
-- IncrementAsync is a convenience for integer read-modify-write (still wrap it).
local ok, newTotal = pcall(function()
    return store:IncrementAsync("Visits_" .. player.UserId, 1)
end)

6. Leaderboard with OrderedDataStore

lua
local boards = DataStoreService:GetOrderedDataStore("Coins")

-- Write a player's score (call when it changes, not every frame).
pcall(function() boards:SetAsync("Player_" .. player.UserId, coins) end)

-- Read the top 10, descending.
local ok, pages = pcall(function()
    return boards:GetSortedAsync(false, 10)   -- ascending=false → highest first
end)
if ok then
    for rank, entry in ipairs(pages:GetCurrentPage()) do
        print(rank, entry.key, entry.value)   -- entry.value is the number
    end
end

Pitfalls

  • Unhandled failure wipes progress → always pcall Async calls; on a failed

load, mark the session and refuse to save so defaults never overwrite real data.

  • `SetAsync` race between servers → two servers writing the same key can clobber

each other. Use UpdateAsync for read-modify-write so each write sees the latest.

  • Yielding inside the `UpdateAsync` callback → the callback can't call

task.wait or other Async functions; compute the new value beforehand and return it.

  • No `BindToClose` save → players in the server at shutdown lose unsaved progress;

add game:BindToClose and wait for saves to finish within its budget.

  • Throttling / "too many requests" → respect per-key and per-minute limits; don't

save on every value change. Batch and save on a timer / on leave. GetAsync is cached briefly, so immediate re-reads may be stale.

  • Storing non-serializable values → only JSON-serializable data persists: numbers,

strings, booleans, and tables with string/number keys. Instances, Vector3, CFrame, and functions do not — serialize them to plain tables first.

  • Testing without API access → DataStores silently can't be used in Studio until

Enable Studio Access to API Services is on (and they don't work from a LocalScript).

  • `DataStoreKeyInfo` is nil for ordered storesOrderedDataStore doesn't

support versioning/metadata; use a regular DataStore when you need those.

References

  • For session locking (preventing duplicate data across servers), versioning/

metadata with DataStoreSetOptions, ordered-store pagination (AdvanceToNextPageAsync), the key error codes and request limits, and Right-to-be-Forgotten compliance, read references/sessions-and-limits.md.

Related skills

  • roblox-luau — services, instances, events, and the server/client model.
  • save-systems — engine-agnostic serialization, slots, and migration.
del mismo repositorio

Más Skills

Todos los Skills
gamedev-skills
Comunidad

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 APIs. Use when the user mentions game feel, juice, "make it feel good/punchy", screen shake, hit stop, screen freeze, easing, squash and stretch, impact frames, or feedback/polish on hits, jumps, pickups, and deaths.

instalaciones
5
GitHub Stars
1,1 mil
Actualizado
10 sept
gamedev-skills
Comunidad

game-ui-ux

Design and build game UI/UX — HUDs, menus, and overlays — that survive every screen: anchor- based responsive layout, resolution/aspect scaling and safe areas, keyboard/gamepad focus navigation, a screen/menu state stack, and event-driven (not polled) HUD updates. Engine- neutral patterns that pair with the detected engine's UI skill. Use when the user mentions HUD, health bar, main menu, pause menu, settings screen, UI layout, anchors, UI scaling, aspect ratio, safe area, controller/keyboard menu navigation, or wiring UI to game state.

instalaciones
5
GitHub Stars
1,1 mil
Actualizado
10 sept
gamedev-skills
Comunidad

godot-3d-essentials

Set up a Godot 4.7 3D scene: Node3D transforms, Camera3D, lighting (DirectionalLight3D/OmniLight3D), WorldEnvironment for sky/ambient/tonemap/post, MeshInstance3D materials, and GridMap for tile-based 3D levels. Use when building a 3D scene in a Godot project, placing cameras/lights, configuring environment and post-processing, or working with Node3D/.tscn 3D content and GridMap.

instalaciones
2
GitHub Stars
1,1 mil
Actualizado
10 sept
gamedev-skills
Comunidad

godot-animation

Animate in Godot 4.7 three ways: AnimationPlayer for keyframed clips (incl. call and signal tracks), AnimationTree with state machines and blend spaces for character animation, and Tween for short procedural/UI tweens via createtween(). Use when working with AnimationPlayer/AnimationTree nodes in a .tscn, blending character states, sprite-sheet animation, or code-driven Tweens.

instalaciones
2
GitHub Stars
1,1 mil
Actualizado
10 sept