You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Roblox Studio中DataStore无法保存玩家数据问题求助

问题排查与修复:DataStore数据无法持久化

我制作的点击类游戏中,玩家leaderstats的Clicks点击数和Time游戏时长数据无法通过DataStore持久化,退出重进后会重置为0。以下是ServerScriptService中的代码:

local DataStoreService = game:GetService("DataStoreService")
local statsDS = DataStoreService:GetDataStore("statsDS")

local SAVE_INTERVAL = 60 -- Save data every 60 seconds (1 minute)

-- Helper function to save player data
local function savePlayerData(player)
    local leaderstats = player:FindFirstChild("leaderstats")

    if leaderstats then
        local Clicks = leaderstats:FindFirstChild("Clicks")
        local Time = leaderstats:FindFirstChild("Time")

        if Clicks and Time then
            local data = {Clicks.Value, Time.Value}

            local playerUserId = "Player" .. player.UserId

            local success, errorMessage = pcall(function()
                statsDS:SetAsync(playerUserId, data)
            end)

            if success then
                print("Saved data for " .. player.Name)
            else
                warn("Error saving data for " .. player.Name .. ": " .. errorMessage)
            end
        end
    end
end

game.Players.PlayerAdded:Connect(function(player)
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = player

    local Clicks = Instance.new("IntValue")
    Clicks.Name = "Clicks"
    Clicks.Parent = leaderstats

    local Time = Instance.new("IntValue")
    Time.Name = "Time"
    Time.Parent = leaderstats

    local playerUserId = "Player" .. player.UserId

    local data = statsDS:GetAsync(playerUserId)

    if data then
        Clicks.Value = data[1]
        Time.Value = data[2]
    end

    player:WaitForChild("leaderstats")
    player:WaitForChild("leaderstats"):WaitForChild("Clicks")

    local lastSaveTime = tick()

    local saveTimer

    saveTimer = game:GetService("RunService").Heartbeat:Connect(function()
        if tick() - lastSaveTime >= SAVE_INTERVAL then
            lastSaveTime = tick()
            savePlayerData(player)
        end

        if not player:IsDescendantOf(game) then
            -- Player left the game
            saveTimer:Disconnect()
            savePlayerData(player)
        end
    end)
end)

game.Players.PlayerRemoving:Connect(function(player)
    savePlayerData(player)
end)
问题分析
  • 未对GetAsync做错误捕获:直接调用GetAsync时,若遇到DataStore服务错误、网络波动等异常,会抛出错误导致数据加载逻辑中断,玩家数据保持默认的0值。
  • Heartbeat循环的玩家离开处理冗余且易出错:Heartbeat中检测玩家是否在线的逻辑,可能在玩家实例开始销毁后执行,此时leaderstats可能已被删除,导致保存失败;且PlayerRemoving事件已触发保存,此逻辑重复。
  • 多余的WaitForChild调用:代码中手动创建了leaderstats和Clicks实例,无需再调用WaitForChild等待,属于无效代码。
  • 缺少服务器关闭时的全局保存:未处理BindToClose事件,服务器意外关闭时会丢失未保存的玩家数据。
修复后的代码
local DataStoreService = game:GetService("DataStoreService")
local statsDS = DataStoreService:GetDataStore("statsDS")

local SAVE_INTERVAL = 60 -- 每60秒自动保存一次

-- 保存玩家数据的辅助函数
local function savePlayerData(player)
    local leaderstats = player:FindFirstChild("leaderstats")
    if not leaderstats then return end

    local Clicks = leaderstats:FindFirstChild("Clicks")
    local Time = leaderstats:FindFirstChild("Time")
    if not Clicks or not Time then return end

    -- 使用键值对存储,避免索引顺序错误
    local data = {
        Clicks = Clicks.Value,
        Time = Time.Value
    }

    local playerUserId = "Player_" .. player.UserId -- 规范键格式,避免冲突

    local success, errorMessage = pcall(function()
        statsDS:SetAsync(playerUserId, data)
    end)

    if success then
        print("已保存玩家 " .. player.Name .. " 的数据")
    else
        warn("保存玩家 " .. player.Name .. " 数据失败: " .. errorMessage)
    end
end

-- 玩家加入时加载数据
game.Players.PlayerAdded:Connect(function(player)
    -- 创建leaderstats及属性
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = player

    local Clicks = Instance.new("IntValue")
    Clicks.Name = "Clicks"
    Clicks.Parent = leaderstats

    local Time = Instance.new("IntValue")
    Time.Name = "Time"
    Time.Parent = leaderstats

    local playerUserId = "Player_" .. player.UserId
    -- 读取数据时用pcall捕获错误
    local success, data = pcall(function()
        return statsDS:GetAsync(playerUserId)
    end)

    if success and data then
        -- 键值对赋值,避免索引混乱
        Clicks.Value = data.Clicks or 0
        Time.Value = data.Time or 0
        print("已加载玩家 " .. player.Name .. " 的数据")
    elseif not success then
        warn("加载玩家 " .. player.Name .. " 数据失败: " .. data)
    end

    -- 用task循环实现定时保存,替代Heartbeat更高效
    local saveLoop = task.spawn(function()
        while task.wait(SAVE_INTERVAL) do
            if player:IsDescendantOf(game) then
                savePlayerData(player)
            else
                break
            end
        end
    end)

    -- 玩家实例销毁时清理定时器并保存数据
    player.AncestryChanged:Connect(function(_, parent)
        if parent == nil then
            task.cancel(saveLoop)
            savePlayerData(player)
        end
    end)
end)

-- 玩家主动离开时保存数据
game.Players.PlayerRemoving:Connect(function(player)
    savePlayerData(player)
end)

-- 服务器关闭前保存所有在线玩家数据
game:BindToClose(function()
    print("服务器即将关闭,正在保存所有玩家数据...")
    for _, player in ipairs(game.Players:GetPlayers()) do
        savePlayerData(player)
        task.wait(0.1) -- 避免DataStore请求过于频繁
    end
    print("所有玩家数据已保存")
end)
关键修改说明
  • 添加GetAsync错误捕获:用pcall包裹读取逻辑,确保服务错误时不会中断数据加载,同时输出错误信息便于调试。
  • 改用键值对存储数据:相比数组,键值对结构更清晰,后续扩展数据时不会因索引顺序错误导致数据混乱。
  • 替换Heartbeat为task循环:使用task.wait(SAVE_INTERVAL)实现定时保存,逻辑更直观高效;通过AncestryChanged监听玩家实例销毁,及时清理定时器并保存。
  • 新增BindToClose处理:服务器关闭时自动保存所有在线玩家数据,避免意外关闭导致数据丢失。
  • 移除冗余代码:删除无效的WaitForChild调用,简化逻辑。

内容的提问来源于stack exchange,提问作者DuckyPolice

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 03:24:59