roblox-datastores について
サーバー上でプレイヤーデータを保存、読み込み、管理するRoblox開発者向けの`DataStoreService`リファレンススキルです。メソッドの説明や例に加え、信頼性、バージョン管理、データ損失への注意点を扱います。
主な機能
- 01DataStoreメソッドのリファレンス
- 02データの読み込みと保存の手引き
- 03再試行とエラー処理のパターン
- 04退出時と終了時の保存
- 05リーダーボード向けの順序付きストア
- 06バージョン管理と移行の手引き
活用シーン
- 01プレイヤーデータの読み込みと保存の実装
- 02ストレージ障害への対処と再試行
- 03サーバー終了時のセッションデータ保存
- 04スキーマ変更後のデータ移行
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ sentinelcore/ roblox-skills --skill roblox-datastoresインストール後、Claude Code で/roblox-datastoresを入力して読み込まれたか確認します。
詳細
roblox-datastores
Roblox の DataStoreService に関するリファレンス — サーバー上でのプレイヤーデータの保存、読み込み、管理について。
クイックリファレンス
| メソッド | シグネチャ | 備考 |
|---|---|---|
GetDataStore | DSS:GetDataStore(name, scope?) | GlobalDataStore を返します |
GetOrderedDataStore | DSS:GetOrderedDataStore(name, scope?) | リーダーボード用 |
GetAsync | store:GetAsync(key) | 値または nil を返します |
SetAsync | store:SetAsync(key, value) | 戻り値は不要です |
UpdateAsync | store:UpdateAsync(key, fn) | アトミックな読み取り・変更・書き込み |
RemoveAsync | store:RemoveAsync(key) | キーを削除し、古い値を返します |
GetSortedAsync | orderedStore:GetSortedAsync(asc, pageSize) | DataStorePages を返します |
基本的なセットアップ
-- Server Script (ServerScriptService)
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local playerStore = DataStoreService:GetDataStore("PlayerData_v1")
local DEFAULT_DATA = {
coins = 0,
level = 1,
xp = 0,
}
データの読み込み (GetAsync + pcall)
データストアの呼び出しは常に pcall でラップしてください。ネットワークの問題やレート制限により失敗する可能性があります。
local function loadData(player)
local key = "player_" .. player.UserId
local success, data = pcall(function()
return playerStore:GetAsync(key)
end)
if success then
local result = {}
for k, v in pairs(DEFAULT_DATA) do result[k] = v end
if data then
for k, v in pairs(data) do result[k] = v end
end
return result
else
warn("Failed to load data for", player.Name, ":", data)
return nil -- signal failure; do not give default data silently
end
end
データの保存 (SetAsync と UpdateAsync)
単純な上書きには SetAsync を使用します。値が現在保存されている値に基づく必要がある場合 (たとえば、複数のサーバーにまたがってカウンターを安全にインクリメントする場合) は UpdateAsync を使用します。
-- Simple save
local function saveData(player, data)
local key = "player_" .. player.UserId
local success, err = pcall(function()
playerStore:SetAsync(key, data)
end)
if not success then
warn("Failed to save data for", player.Name, ":", err)
end
end
-- Atomic increment with UpdateAsync
local function addCoinsAtomic(userId, amount)
local key = "player_" .. userId
pcall(function()
playerStore:UpdateAsync(key, function(current)
current = current or { coins = 0 }
current.coins = current.coins + amount
return current
end)
end)
end
リトライロジック
local MAX_RETRIES = 3
local RETRY_DELAY = 2
local function safeGet(store, key)
for attempt = 1, MAX_RETRIES do
local success, result = pcall(function()
return store:GetAsync(key)
end)
if success then return true, result end
warn(string.format("GetAsync attempt %d/%d failed: %s", attempt, MAX_RETRIES, result))
if attempt < MAX_RETRIES then task.wait(RETRY_DELAY) end
end
return false, nil
end
自動保存: PlayerRemoving + BindToClose
BindToClose を使わずにサーバーをシャットダウンすると、保存されていないデータは黙って破棄されます。
local sessionData = {} -- [userId] = data table
Players.PlayerAdded:Connect(function(player)
local data = loadData(player)
if data then
sessionData[player.UserId] = data
else
player:Kick("Could not load your data. Please rejoin.")
end
end)
Players.PlayerRemoving:Connect(function(player)
local data = sessionData[player.UserId]
if data then
saveData(player, data)
sessionData[player.UserId] = nil
end
end)
-- Flush all sessions on server shutdown
game:BindToClose(function()
for userId, data in pairs(sessionData) do
local key = "player_" .. userId
pcall(function()
playerStore:SetAsync(key, data)
end)
end
end)
Ordered DataStore (リーダーボード)
値は正の整数である必要があります。
local coinsLeaderboard = DataStoreService:GetOrderedDataStore("Coins_v1")
local function setLeaderboardScore(userId, coins)
pcall(function()
coinsLeaderboard:SetAsync("player_" .. userId, math.floor(coins))
end)
end
local function getTopPlayers(count)
local success, pages = pcall(function()
return coinsLeaderboard:GetSortedAsync(false, count) -- false = descending
end)
if not success then return {} end
local results = {}
for rank, entry in ipairs(pages:GetCurrentPage()) do
table.insert(results, { rank = rank, userId = entry.key, score = entry.value })
end
return results
end
データのバージョニング / マイグレーション
_version フィールドを含め、読み込みパスでマイグレーションを行います。
local CURRENT_VERSION = 2
local function migrateData(data)
local version = data._version or 1
if version < 2 then
data.coins = data.gold or 0 -- renamed field
data.gold = nil
data._version = 2
end
return data
end
スキーマの破壊的変更には、バージョン付きのデータストア名 (PlayerData_v2) を使用してください。
よくある間違い
| 間違い | 結果 | 修正方法 |
|---|---|---|
データストア呼び出しを pcall で囲まない | 未処理のエラーでスクリプトがクラッシュする | 常に pcall でラップする |
Changed イベントごとに保存する | レート制限に達する (60 + numPlayers×10 書き込み/分) | スロットリングする。削除時 + 定期的な間隔で保存する |
BindToClose ハンドラがない | サーバーシャットダウン時にデータが失われる | BindToClose ですべてのセッションを必ずフラッシュする |
| 読み込み失敗時にデフォルトデータを返す | プレイヤーが気づかないうちに進行状況を失う | 失敗時は nil を返す。キックするかリトライする |
アトミックなカウンターに SetAsync を使う | 複数サーバー間で競合状態が発生する | 読み取り・変更・書き込みには UpdateAsync を使う |
| Instance や関数を保存する | データが黙って破棄される | 文字列、数値、真偽値、プレーンなテーブルのみを保存する |
| スキーマ変更後にデータストア名を再利用する | 古い形状が新しいコードと競合する | 破壊的変更時は名前に _v2、_v3 を付ける |
コメント
Markdown 対応