中级教程

使用标准数据存储保存玩家数据

*此内容使用人工智能(Beta)翻译,可能包含错误。若要查看英文页面,请点按 此处

数据存储是一个服务,用于在不同的玩家会话中保存和加载持久的玩家数据。它们存储重要的信息,比如玩家的进度或库存,并允许你在玩家下次加入游戏时检索这些信息。如果没有数据存储,玩家每次离开游戏时将失去所有进度。

数据存储有两种类型:标准和有序。本教程使用标准数据存储,它们存储如数字、字符串和不需要排序或排名的表格等数据。

本教程以 Gold Rush 数据存储教程 - 开始 .rbxl 文件作为起点,以 Gold Rush 数据存储教程 - 完成 .rbxl 文件作为参考,提供了保存玩家进度和创建更一致的游戏所需的基础,包括以下内容的指导:

  • 保存和加载玩家收集的黄金数量。
  • 更新用户界面以显示玩家的黄金库存。
  • 每 30 秒自动保存玩家的黄金库存。
  • 玩家离开游戏时保存他们的位置。
  • 玩家再次加入游戏时恢复他们的位置。

通过本教程的学习,你应该能够在 ServerScriptService 下创建两个脚本,以处理数据存储:

  1. 一个更新后的 GoldManager 脚本,跟踪、加载并自动保存玩家的黄金。
  2. 一个更新后的 PositionManager 脚本,保存玩家在离开游戏时的位置,并在他们返回游戏时恢复该位置。

启用 Studio 对 API 服务的访问

数据存储不存储在你的设备上,因此你的游戏依赖于与 Roblox 后端的服务器间通信来使用它们。默认情况下,Studio 限制此通信以防止滥用或意外使用。在你明确启用 API 服务的访问之前,Studio 中对 API 服务的访问是禁用的,以确保只有受信的游戏可以读取和写入 Roblox 后端服务器。

要启用 Studio 对 API 服务的访问,以便你可以使用数据存储:

  1. 在 Studio 中打开 Gold Rush 数据存储教程 - 开始 .rbxl 文件并创建本地副本。
  2. 返回 Studio,前往 文件体验设置安全
  3. 打开 启用 Studio 对 API 服务的访问
  4. 保存你的更改。

创建标准数据存储

创建数据存储时,应始终在服务器端的 Script 中调用 DataStoreService。这样做很重要,因为:

  • Roblox 阻止客户端访问所有数据存储,以确保只有服务器有权限访问和修改持久的玩家数据。
  • 服务器端脚本在集中环境中运行,确保读取和写入到数据存储的数据是准确有效的。
  • 由于客户端脚本在玩家设备上运行,任何玩家都有可能利用游戏并窃取或覆盖其他玩家的数据,如果客户端能够访问你的数据存储。

你还应该为你的数据存储赋予一个唯一的名称,以保持你的数据组织良好,并防止在不同数据存储中重叠或冲突。在本教程中,保存和存储玩家黄金库存的数据存储被称为PlayerGold

要创建 PlayerGold 数据存储:

  1. 打开 ServerScriptService 下的现有 GoldManager 脚本。

  2. 在脚本顶部的其他服务下,初始化 DataStoreService 并调用 GetDataStore 方法,传入字符串 "PlayerGold"


    local DataStoreService = game:GetService("DataStoreService")
    local goldStore = DataStoreService:GetDataStore("PlayerGold")

保存和加载玩家数据

数据存储由标识数据的键和存储数据的值组成。

描述键是一个唯一的标识符,你可以用它来访问特定的数据片段。

键帮助你组织数据,以便你可以轻松管理和调试。
值是你想要保存或加载的实际数据,比如玩家的库存或他们的设置。

值允许你在会话之间存储进度,并在玩家下次加入游戏时检索玩家数据。
允许的格式只能是字符串。可以是数字、字符串、布尔值或表格。
示例"Player_A""TopScore""GameSetting"100"Level_5"true{gold = 100, level = 5}

在本教程中,键是玩家的 userId,值是他们收集的黄金数量。要使用此键和值保存和加载玩家数据:

  1. GoldManager 脚本中,添加一个名为 saveGold 的帮助函数,以将玩家的黄金保存到 PlayerGold 数据存储中。


    local function saveGold(userId, value)
    local success, err = pcall(function()
    -- `userId` 是玩家的唯一 ID
    -- `value` 是要为该玩家保存的黄金数量
    goldStore:SetAsync(userId, value)
    end)
    end
  2. 可选
    添加一个警告到 saveGold,打印玩家的 ID 和错误信息。虽然不是强制性的,但这个步骤有助于在函数失败时更容易调试。


    local function saveGold(userId, value)
    local success, err = pcall(function()
    -- `userId` 是玩家的唯一 ID
    -- `value` 是要为该玩家保存的黄金数量
    goldStore:SetAsync(userId, value)
    end)
    if not success then
    warn("[SAVE ERROR] 无法为", userId, "保存黄金:", err)
    end
    end
  3. 修改 onPlayerAdded 事件,以在玩家首次加入游戏时加载他们的黄金。更新后的 onPlayerAdded 函数:

    • 尝试加载 PlayerGold 数据存储中的内容。如果玩家没有黄金或以前没有玩过该游戏,则返回值为 0。
    • 提供调试日志,以帮助你更轻松地调试代码。这个调试日志将包含加入玩家的保存黄金,或在 onPlayerAdded 无法访问数据存储时的错误信息。
    • 更新用户界面,以在他们的屏幕上显示玩家的保存黄金。

    local function onPlayerAdded(player)
    local userId = player.UserId
    local success, storedGold = pcall(function()
    return goldStore:GetAsync(userId)
    end)
    if success then
    local currentGold = storedGold or 0
    playerGold[userId] = currentGold
    uiEvent:FireClient(player, {
    gold = currentGold,
    doTween = false,
    showAlert = not success
    })
    else
    uiEvent:FireClient(player, { showAlert = true })
    warn("无法加载", player.Name, "的黄金")
    end
    end
  4. 修改 onPlayerRemoving 事件,调用你之前创建的 saveGold 函数,并在玩家离开游戏时保存他们的黄金。


    local function onPlayerRemoving(player)
    local userId = player.UserId
    if playerGold[userId] then
    saveGold(userId, playerGold[userId])
    end
    end

自动保存玩家数据

自动保存玩家的数据是良好的实践,因为它保护玩家不丢失他们的数据。如果你只在玩家使用 onPlayerRemoving 离开游戏时保存他们的数据,你可能会在他们意外断开与服务器的连接时丢失他们的最新进度。

要自动保存玩家数据:

  1. 创建一个新的常量,以确定希望游戏多久自动保存一次玩家的数据。常量是游戏运行时其值不会更改的变量。你可以在整个脚本中使用此常量来引用自动保存间隔,并轻松调整自动保存的频率。


    -- 这个数字以秒为单位
    local AUTOSAVE_INTERVAL = 30
  2. 向现有的 onPlayerAdded 函数添加一个自动保存协程,以创建一个在玩家在游戏内时运行的后台循环。协程是一个异步运行的函数;它可以在某些点暂停,恢复到它离开的地方,并与脚本的其他部分并行运行而不会阻塞它们。

    在这个脚本中,自动保存协程后台循环等待 AUTOSAVE_INTERVAL 常量中的秒数,然后调用你之前创建的 saveGold 函数。


    coroutine.wrap(function()
    while player.Parent do
    task.wait(AUTOSAVE_INTERVAL)
    if playerGold[userId] then
    print("[AUTOSAVE] 正在保存", playerGold[userId], "黄金,玩家:", player.Name)
    saveGold(userId, playerGold[userId])
    end
    end
    end)()

保存和加载玩家位置

要保存玩家的位置,应该处理他们的 Character 而不是 Player 对象本身。玩家的 Character 代表他们在 3D 世界中的物理模型,并包含他们当前的位置和方向的坐标,而 Player 对象仅表示用户账户。

由于数据存储只能保存基本数据类型,如数字、字符串和表格,因此你无法直接存储复杂对象,如 Vector3CFrame 值。要保存玩家的位置,你需要将其分成三个单独的数字(X、Y 和 Z 坐标)并单独保存。稍后加载位置时,你可以使用保存的坐标重建 Vector3

要保存和加载角色的位置:

  1. 打开 ServerScriptService 下的现有 PositionManager 脚本。

  2. 在脚本顶部,初始化 DataStoreService 并调用 GetDataStore 方法,传入字符串 "PlayerPosition"


    local DataStoreService = game:GetService("DataStoreService")
    local positionStore = DataStoreService:GetDataStore("PlayerPosition")
  3. 添加一个名为 savePosition 的帮助函数,接受玩家的 userId 和一个 Vector3 位置。由于你无法直接保存 Vector3 值,请将位置转换为 X、Y、Z 数字的表。


    local function savePosition(player, position)
    local userId = player.UserId
    local success, err = pcall(function()
    positionStore:SetAsync(userId, {position.X, position.Y, position.Z})
    end)
    end
  4. 可选
    savePosition 添加一个警告,打印错误信息和未能保存位置的玩家姓名。虽然不是强制性的,但这个步骤有助于在函数失败时更方便地调试。


    local function savePosition(player, position)
    local userId = player.UserId
    local success, err = pcall(function()
    positionStore:SetAsync(userId, {position.X, position.Y, position.Z})
    end)
    if not success then
    warn("无法为", player.Name, "保存位置:", err)
    end
    end
  5. 添加另一个名为 loadPosition 的帮助函数,以加载 PlayerPosition 数据存储中保存的坐标。使用 PivotTo 将角色在世界中的位置恢复。由于玩家的保存位置是作为表返回的,因此你必须重建一个 Vector3,然后将其转换为 CFrame 来移动角色。


    local function loadPosition(userId, character)
    local userId = player.UserId
    local success, savedCoords = pcall(function()
    return positionStore:GetAsync(userId)
    end)
    if success and savedCoords then
    local pos = Vector3.new(savedCoords[1], savedCoords[2], savedCoords[3])
    print("恢复了", player.Name, "的位置")
    character:PivotTo(CFrame.new(pos))
    elseif not success then
    warn("无法加载", player.Name, "的位置")
    end
    end
  6. 修改 onPlayerAdded 事件,以设置每个加入游戏的玩家的位置逻辑。在更新后的 onPlayerAdded 函数中:

    • CharacterAdded 调用 loadPosition,在玩家角色生成时恢复他们的最后已知位置。
    • CharacterRemoving 调用 savePosition,在玩家角色移除时保存当前角色的位置。
    • GetPivot 获取角色在被移除时在世界中的确切位置。

    local function onPlayerAdded(player)
    local userId = player.UserId
    player.CharacterAdded:Connect(function(character)
    loadPosition(player, character)
    end)
    player.CharacterRemoving:Connect(function(character)
    local pos = character:GetPivot().Position
    savePosition(player, pos)
    end)
    end
    Player.PlayerAdded:Connect(onPlayerAdded)

完整代码

请参阅以下代码片段以获取完整的 GoldManagerPositionManager 脚本。你可以将它们粘贴并直接在 Gold Rush 保存数据教程 - 开始 .rbxl 文件中运行。

GoldManager


-- 获取服务
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local DataStoreService = game:GetService("DataStoreService")
-- 远程事件用于更新用户界面
local uiEvent = ReplicatedStorage:WaitForChild("UIEvent")
-- 数据存储用于保存玩家的黄金块
local goldStore = DataStoreService:GetDataStore("PlayerGold")
-- 表格用于存储单次会话中的玩家黄金块
local playerGold = {}
-- 常量
local GOLD_VALUE = 10 -- 每个黄金块的价值
local GOLD_REGEN_DELAY = 2 -- 块重新生成的延迟(以秒为单位)
local AUTOSAVE_INTERVAL = 30 -- 玩家块保存的频率(以秒为单位)
-- 处理玩家收集黄金块的函数
local function processGoldTouch(chunk, player)
-- 当玩家收集块时隐藏块
chunk.Parent = nil
-- 将块加入到玩家本次会话的黄金分数
local userId = player.UserId
playerGold[userId] = (playerGold[userId] or 0) + GOLD_VALUE
print(player.Name, "目前有", playerGold[userId], "黄金")
-- 更新用户界面以反映本次会话的新黄金分数
uiEvent:FireClient(player, {
gold = playerGold[userId],
doTween = true,
showAlert = false
})
-- 等待一会儿,然后重新生成刚刚被收集的黄金块
task.delay(GOLD_REGEN_DELAY, function()
chunk.Parent = workspace.GoldChunks
end)
end
-- 循环遍历所有标记为“Gold”的部件,并连接触摸检测
for _, chunk in ipairs(CollectionService:GetTagged("Gold")) do
chunk.Touched:Connect(function(otherPart)
local player = Players:GetPlayerFromCharacter(otherPart.Parent)
if player then
processGoldTouch(chunk, player)
end
end)
end
-- 保存玩家黄金到数据存储中的函数
local function saveGold(player, value)
local userId = player.UserId
local success, err = pcall(function()
goldStore:SetAsync(userId, value)
end)
if not success then
warn("无法为", player.Name, "保存黄金:", err)
end
end
-- 处理玩家加入游戏的函数
local function onPlayerAdded(player)
local userId = player.UserId
print(player.Name, "已加入游戏")
-- 尝试从数据存储加载玩家的黄金
local success, storedGold = pcall(function()
return goldStore:GetAsync(userId)
end)
if success then
-- 使用数据存储中的值设置当前会话的黄金分数
local currentGold = storedGold or 0
playerGold[userId] = currentGold
-- 更新用户界面以显示玩家的数据存储值
uiEvent:FireClient(player, {
gold = currentGold,
doTween = false,
showAlert = false
})
print("加载", currentGold, "黄金,玩家:", player.Name)
else
-- 通知玩家他们的黄金无法加载
uiEvent:FireClient(player, { showAlert = true })
warn("无法加载", player.Name, "的黄金")
end
-- 启动自动保存协程以每 30 秒保存玩家的黄金
coroutine.wrap(function()
while player.Parent do
task.wait(AUTOSAVE_INTERVAL)
if playerGold[userId] then
saveGold(player, playerGold[userId])
print("自动保存", playerGold[userId], "黄金,玩家:", player.Name)
end
end
end)()
end
-- 处理玩家离开游戏的函数
local function onPlayerRemoving(player)
local userId = player.UserId
print(player.Name, "已离开游戏")
-- 检查玩家的黄金是否被跟踪
if playerGold[userId] then
-- 在玩家离开之前将玩家的黄金保存到数据存储中
saveGold(player, playerGold[userId])
end
end
-- 连接玩家加入和离开事件
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)

PositionManager


-- 获取服务
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
-- 数据存储用于保存玩家的位置
local positionStore = DataStoreService:GetDataStore("PlayerPosition")
-- 将玩家的位置保存为 {X, Y, Z} 表的函数
local function savePosition(player, position)
local userId = player.UserId
local success, err = pcall(function()
positionStore:SetAsync(userId, {position.X, position.Y, position.Z})
end)
if not success then
warn("无法为", player.Name, "保存位置:", err)
end
end
-- 加载玩家最后保存的位置并移动其角色的函数
local function loadPosition(player, character)
local userId = player.UserId
local success, savedCoords = pcall(function()
return positionStore:GetAsync(userId)
end)
if success and savedCoords then
local pos = Vector3.new(savedCoords[1], savedCoords[2], savedCoords[3])
print("恢复了", player.Name, "的位置")
character:PivotTo(CFrame.new(pos))
elseif not success then
warn("无法加载", player.Name, "的位置")
end
end
-- 处理玩家加入游戏的函数
local function onPlayerAdded(player)
local userId = player.UserId
-- 当他们的角色生成时尝试加载玩家的位置
player.CharacterAdded:Connect(function(character)
loadPosition(player, character)
end)
-- 当他们的角色被移除时保存玩家的位置
player.CharacterRemoving:Connect(function(character)
local pos = character:GetPivot().Position
savePosition(player, pos)
end)
end
Players.PlayerAdded:Connect(onPlayerAdded)
©2026 Roblox Corporation、Roblox、Roblox 标志及 Powering Imagination 是我们在美国及其他国家或地区的注册与未注册商标。