关于 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)
有序数据存储(排行榜)
值必须为正整数。
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 + 玩家数×10 次写入) | 限流;在玩家离开时及定期保存 |
没有 BindToClose 处理程序 | 服务器关闭时数据丢失 | 始终在 BindToClose 中保存所有会话的数据 |
| 加载失败时提供默认数据 | 玩家会在不知情的情况下丢失进度 | 失败时返回 nil;踢出玩家或重试 |
使用 SetAsync 处理原子计数器 | 多个服务器间发生竞态条件 | 使用 UpdateAsync 执行读取并修改 |
| 存储 Instances 或函数 | 数据会被静默丢弃 | 仅存储字符串、数字、布尔值和普通表 |
| 架构更改后仍复用数据存储名称 | 旧数据结构与新代码冲突 | 发生不兼容的更改时,在名称后追加 _v2、_v3 |
评论
支持 Markdown