mcpField
R
roblox-datastores

roblox-datastores

github.com/sentinelcore开发29612 次安装35 周前更新
代码仓库

关于 roblox-datastores

面向 Roblox 开发者的 `DataStoreService` 参考技能,用于在服务器端保存、加载和管理玩家数据。内容涵盖方法说明、示例,以及可靠性、版本管理和数据丢失风险提示。

主要功能

  1. 01DataStore 方法参考
  2. 02数据加载与保存指导
  3. 03重试和错误处理模式
  4. 04玩家离开及关服时保存
  5. 05用于排行榜的有序存储
  6. 06版本管理与迁移指导

适用场景

  1. 01实现玩家数据加载与保存
  2. 02处理存储故障并重试
  3. 03在服务器关闭时保存会话数据
  4. 04在架构变更后迁移数据

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/sentinelcore/roblox-skills --skill roblox-datastores

安装后在 Claude Code 中输入/roblox-datastores确认已加载。

详细介绍

roblox-datastores

Roblox DataStoreService 参考指南 — 在服务器上保存、加载和管理玩家数据。

快速参考

方法签名说明
GetDataStoreDSS:GetDataStore(name, scope?)返回 GlobalDataStore
GetOrderedDataStoreDSS:GetOrderedDataStore(name, scope?)用于排行榜
GetAsyncstore:GetAsync(key)返回值或 nil
SetAsyncstore:SetAsync(key, value)无需返回值
UpdateAsyncstore:UpdateAsync(key, fn)原子性地读取并修改
RemoveAsyncstore:RemoveAsync(key)删除键并返回旧值
GetSortedAsyncorderedStore: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