mcpField
R
roblox-datastores

roblox-datastores

github.com/gamedev-skillsデータ・データベース5.4k6.3k 回インストール今日更新
リポジトリ

roblox-datastores について

Robloxのサーバー開発者向けに、DataStoreServiceでプレイヤーの進行状況やランキングを保存する手引きです。エラーを考慮した読み書き、再試行、退出時や終了時の保存を扱い、データ損失や上書きの防止に役立ちます。

主な機能

  1. 01参加時の読み込みと退出時の保存
  2. 02UpdateAsyncによる安全なデータ更新
  3. 03pcallを使った失敗処理と再試行
  4. 04サーバー終了時の参加者データ保存
  5. 05OrderedDataStoreでのランキング作成

活用シーン

  1. 01コイン、所持品、レベルの永続化
  2. 02複数サーバー間の上書き防止
  3. 03一時的な失敗やスロットリングへの対応
  4. 04ランキングデータの取得と表示

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/gamedev-skills/awesome-gamedev-agent-skills --skill roblox-datastores

インストール後、Claude Code で/roblox-datastoresを入力して読み込まれたか確認します。

同梱ファイルreferences/sessions-and-limits.md

詳細

Roblox DataStores

DataStoreService を使って Roblox でセッションをまたいでデータを永続化します。参加時の読み込み、退出時とシャットダウン時の保存、安全な更新、再試行、リーダーボード用の順序付きストアに対応します。サーバー側専用です。

使用する場面

  • プレイヤーの進行状況(コイン、インベントリ、レベル)の保存や読み込み、永続的なリーダーボードの作成、データ損失、上書き、スロットリングの修正に使用します。
  • サーバーコードで DataStoreService、GetDataStore、GetAsync、SetAsync、UpdateAsync、または GetOrderedDataStore を呼び出す場合に使用します。

使用しない場面: 一般的なスクリプト、サービス、リモート、クライアントとサーバーの分担 → roblox-luau。高頻度の一時的な状態(マッチメイキング、ラウンド単位)→ メモリストア(別のサービス)。エンジンに依存しない永続化の理論 → save-systems。

基本的なワークフロー

  1. Studio のアクセスを一度有効にします。 File → Game Settings → Security → Enable Studio Access to API Services(テスト用の場所を使ってください。Studio はライブデータにアクセスします)。DataStore はサーバーの Script からのみ使用でき、LocalScript からは使用できません。
  2. ストアを取得してから、キーを使って読み書きします。 DataStoreService:GetDataStore("Name") を使います。プレイヤーごとのキーには通常 "Player_" .. player.UserId を使います。
  3. すべての呼び出しを pcall で囲みます。 GetAsync/SetAsync/UpdateAsync は失敗する可能性があるネットワーク呼び出しです。保護されていない呼び出しが失敗するとスレッドがエラーになり、データ損失の危険があります。
  4. PlayerAdded で読み込み、PlayerRemoving で保存し、さらに BindToClose も使います。 プレイヤーの退出時とサーバーのシャットダウン時の両方で、最後に保存する必要があります。
  5. 読み取りと変更を伴う書き込みには、SetAsync(無条件の上書き)よりも UpdateAsync(複数サーバー間で安全)を優先します。 読み込みに失敗した場合は、デフォルト値で上書きしないでください。保存を中止し、既存のデータを消さないようにします。
  6. ランキングデータ(リーダーボード)には、GetSortedAsync を使う OrderedDataStore を使用します。 参加し、データを変更してから再参加し、データが保持されていることを確認してテストします。

パターン

1. 参加時に読み込む(pcall で保護)
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. UpdateAsync で保存する(複数サーバー間で安全)
-- 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. 退出時とシャットダウン時に保存する
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. バックオフ付きで再試行する(一時的な障害)
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. カウンターを増加させる
-- 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. OrderedDataStore を使ったリーダーボード
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

注意点

  • 未処理の失敗によって進行状況が消える → Async の呼び出しは必ず pcall で保護してください。読み込みに失敗した場合はセッションに印を付け、デフォルト値が実データを上書きしないよう、保存を拒否します。
  • サーバー間で SetAsync が競合する → 2 つのサーバーが同じキーを書き込むと、互いのデータを上書きする可能性があります。各書き込みが最新の値を参照できるよう、読み取りと変更を伴う書き込みには UpdateAsync を使用します。
  • UpdateAsync のコールバック内で待機する → コールバックから task.wait や他の Async 関数を呼び出すことはできません。新しい値を事前に計算して返してください。
  • BindToClose で保存しない → シャットダウン時にサーバーにいるプレイヤーは、未保存の進行状況を失います。game:BindToClose を追加し、その制限時間内に保存が完了するまで待ってください。
  • スロットリング/「リクエストが多すぎます」 → キーごとおよび分ごとの制限を守り、値が変わるたびに保存しないでください。まとめて、タイマーまたは退出時に保存します。GetAsync の結果は短時間キャッシュされるため、直後の再読み込みでは古い値が返ることがあります。
  • シリアライズできない値を保存する → 永続化できるのは JSON にシリアライズ可能なデータのみです。数値、文字列、ブール値、およびキーが文字列または数値のテーブルが対象です。Instance、Vector3、CFrame、関数は保存できないため、先に通常のテーブルにシリアライズしてください。
  • API アクセスなしでテストする → Enable Studio Access to API Services が有効になるまで、Studio では DataStore を使用できません(また、LocalScript からも使用できません)。
  • 順序付きストアで DataStoreKeyInfo が nil になる → OrderedDataStore はバージョン管理やメタデータに対応していません。これらが必要な場合は通常の DataStore を使用してください。

参考資料

  • セッションロック(サーバー間でデータが重複するのを防ぐ)、DataStoreSetOptions を使ったバージョン管理/メタデータ、順序付きストアのページネーション(AdvanceToNextPageAsync)、キーのエラーコードとリクエスト制限、および忘れられる権利への準拠については、references/sessions-and-limits.md をお読みください。

関連スキル

  • roblox-luau — サービス、インスタンス、イベント、サーバー/クライアントモデル。
  • save-systems — エンジンに依存しないシリアライズ、スロット、移行。

コメント