数据存储是一个服务,用于在不同的玩家会话中保存和加载持久的玩家数据。它们存储重要的信息,比如玩家的进度或库存,并允许你在玩家下次加入游戏时检索这些信息。如果没有数据存储,玩家每次离开游戏时将失去所有进度。
数据存储有两种类型:标准和有序。本教程使用标准数据存储,它们存储如数字、字符串和不需要排序或排名的表格等数据。
本教程以 Gold Rush 数据存储教程 - 开始 .rbxl 文件作为起点,以 Gold Rush 数据存储教程 - 完成 .rbxl 文件作为参考,提供了保存玩家进度和创建更一致的游戏所需的基础,包括以下内容的指导:
- 保存和加载玩家收集的黄金数量。
- 更新用户界面以显示玩家的黄金库存。
- 每 30 秒自动保存玩家的黄金库存。
- 玩家离开游戏时保存他们的位置。
- 玩家再次加入游戏时恢复他们的位置。
通过本教程的学习,你应该能够在 ServerScriptService 下创建两个脚本,以处理数据存储:
- 一个更新后的 GoldManager 脚本,跟踪、加载并自动保存玩家的黄金。
- 一个更新后的 PositionManager 脚本,保存玩家在离开游戏时的位置,并在他们返回游戏时恢复该位置。

启用 Studio 对 API 服务的访问
数据存储不存储在你的设备上,因此你的游戏依赖于与 Roblox 后端的服务器间通信来使用它们。默认情况下,Studio 限制此通信以防止滥用或意外使用。在你明确启用 API 服务的访问之前,Studio 中对 API 服务的访问是禁用的,以确保只有受信的游戏可以读取和写入 Roblox 后端服务器。
要启用 Studio 对 API 服务的访问,以便你可以使用数据存储:
- 在 Studio 中打开 Gold Rush 数据存储教程 - 开始 .rbxl 文件并创建本地副本。
- 返回 Studio,前往 文件 ⟩ 体验设置 ⟩ 安全。
- 打开 启用 Studio 对 API 服务的访问。
- 保存你的更改。
创建标准数据存储
创建数据存储时,应始终在服务器端的 Script 中调用 DataStoreService。这样做很重要,因为:
- Roblox 阻止客户端访问所有数据存储,以确保只有服务器有权限访问和修改持久的玩家数据。
- 服务器端脚本在集中环境中运行,确保读取和写入到数据存储的数据是准确有效的。
- 由于客户端脚本在玩家设备上运行,任何玩家都有可能利用游戏并窃取或覆盖其他玩家的数据,如果客户端能够访问你的数据存储。
你还应该为你的数据存储赋予一个唯一的名称,以保持你的数据组织良好,并防止在不同数据存储中重叠或冲突。在本教程中,保存和存储玩家黄金库存的数据存储被称为PlayerGold。
要创建 PlayerGold 数据存储:
打开 ServerScriptService 下的现有 GoldManager 脚本。

在脚本顶部的其他服务下,初始化 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,值是他们收集的黄金数量。要使用此键和值保存和加载玩家数据:
在 GoldManager 脚本中,添加一个名为 saveGold 的帮助函数,以将玩家的黄金保存到 PlayerGold 数据存储中。
local function saveGold(userId, value)local success, err = pcall(function()-- `userId` 是玩家的唯一 ID-- `value` 是要为该玩家保存的黄金数量goldStore:SetAsync(userId, value)end)end- 可选添加一个警告到 saveGold,打印玩家的 ID 和错误信息。虽然不是强制性的,但这个步骤有助于在函数失败时更容易调试。local function saveGold(userId, value)local success, err = pcall(function()-- `userId` 是玩家的唯一 ID-- `value` 是要为该玩家保存的黄金数量goldStore:SetAsync(userId, value)end)if not success thenwarn("[SAVE ERROR] 无法为", userId, "保存黄金:", err)endend
修改 onPlayerAdded 事件,以在玩家首次加入游戏时加载他们的黄金。更新后的 onPlayerAdded 函数:
- 尝试加载 PlayerGold 数据存储中的内容。如果玩家没有黄金或以前没有玩过该游戏,则返回值为 0。
- 提供调试日志,以帮助你更轻松地调试代码。这个调试日志将包含加入玩家的保存黄金,或在 onPlayerAdded 无法访问数据存储时的错误信息。
- 更新用户界面,以在他们的屏幕上显示玩家的保存黄金。
local function onPlayerAdded(player)local userId = player.UserIdlocal success, storedGold = pcall(function()return goldStore:GetAsync(userId)end)if success thenlocal currentGold = storedGold or 0playerGold[userId] = currentGolduiEvent:FireClient(player, {gold = currentGold,doTween = false,showAlert = not success})elseuiEvent:FireClient(player, { showAlert = true })warn("无法加载", player.Name, "的黄金")endend修改 onPlayerRemoving 事件,调用你之前创建的 saveGold 函数,并在玩家离开游戏时保存他们的黄金。
local function onPlayerRemoving(player)local userId = player.UserIdif playerGold[userId] thensaveGold(userId, playerGold[userId])endend
自动保存玩家数据
自动保存玩家的数据是良好的实践,因为它保护玩家不丢失他们的数据。如果你只在玩家使用 onPlayerRemoving 离开游戏时保存他们的数据,你可能会在他们意外断开与服务器的连接时丢失他们的最新进度。
要自动保存玩家数据:
创建一个新的常量,以确定希望游戏多久自动保存一次玩家的数据。常量是游戏运行时其值不会更改的变量。你可以在整个脚本中使用此常量来引用自动保存间隔,并轻松调整自动保存的频率。
-- 这个数字以秒为单位local AUTOSAVE_INTERVAL = 30向现有的 onPlayerAdded 函数添加一个自动保存协程,以创建一个在玩家在游戏内时运行的后台循环。协程是一个异步运行的函数;它可以在某些点暂停,恢复到它离开的地方,并与脚本的其他部分并行运行而不会阻塞它们。
在这个脚本中,自动保存协程后台循环等待 AUTOSAVE_INTERVAL 常量中的秒数,然后调用你之前创建的 saveGold 函数。
coroutine.wrap(function()while player.Parent dotask.wait(AUTOSAVE_INTERVAL)if playerGold[userId] thenprint("[AUTOSAVE] 正在保存", playerGold[userId], "黄金,玩家:", player.Name)saveGold(userId, playerGold[userId])endendend)()
保存和加载玩家位置
要保存玩家的位置,应该处理他们的 Character 而不是 Player 对象本身。玩家的 Character 代表他们在 3D 世界中的物理模型,并包含他们当前的位置和方向的坐标,而 Player 对象仅表示用户账户。
由于数据存储只能保存基本数据类型,如数字、字符串和表格,因此你无法直接存储复杂对象,如 Vector3 或 CFrame 值。要保存玩家的位置,你需要将其分成三个单独的数字(X、Y 和 Z 坐标)并单独保存。稍后加载位置时,你可以使用保存的坐标重建 Vector3。
要保存和加载角色的位置:
打开 ServerScriptService 下的现有 PositionManager 脚本。
在脚本顶部,初始化 DataStoreService 并调用 GetDataStore 方法,传入字符串 "PlayerPosition"。
local DataStoreService = game:GetService("DataStoreService")local positionStore = DataStoreService:GetDataStore("PlayerPosition")添加一个名为 savePosition 的帮助函数,接受玩家的 userId 和一个 Vector3 位置。由于你无法直接保存 Vector3 值,请将位置转换为 X、Y、Z 数字的表。
local function savePosition(player, position)local userId = player.UserIdlocal success, err = pcall(function()positionStore:SetAsync(userId, {position.X, position.Y, position.Z})end)end- 可选为 savePosition 添加一个警告,打印错误信息和未能保存位置的玩家姓名。虽然不是强制性的,但这个步骤有助于在函数失败时更方便地调试。local function savePosition(player, position)local userId = player.UserIdlocal success, err = pcall(function()positionStore:SetAsync(userId, {position.X, position.Y, position.Z})end)if not success thenwarn("无法为", player.Name, "保存位置:", err)endend
添加另一个名为 loadPosition 的帮助函数,以加载 PlayerPosition 数据存储中保存的坐标。使用 PivotTo 将角色在世界中的位置恢复。由于玩家的保存位置是作为表返回的,因此你必须重建一个 Vector3,然后将其转换为 CFrame 来移动角色。
local function loadPosition(userId, character)local userId = player.UserIdlocal success, savedCoords = pcall(function()return positionStore:GetAsync(userId)end)if success and savedCoords thenlocal pos = Vector3.new(savedCoords[1], savedCoords[2], savedCoords[3])print("恢复了", player.Name, "的位置")character:PivotTo(CFrame.new(pos))elseif not success thenwarn("无法加载", player.Name, "的位置")endend修改 onPlayerAdded 事件,以设置每个加入游戏的玩家的位置逻辑。在更新后的 onPlayerAdded 函数中:
- CharacterAdded 调用 loadPosition,在玩家角色生成时恢复他们的最后已知位置。
- CharacterRemoving 调用 savePosition,在玩家角色移除时保存当前角色的位置。
- GetPivot 获取角色在被移除时在世界中的确切位置。
local function onPlayerAdded(player)local userId = player.UserIdplayer.CharacterAdded:Connect(function(character)loadPosition(player, character)end)player.CharacterRemoving:Connect(function(character)local pos = character:GetPivot().PositionsavePosition(player, pos)end)endPlayer.PlayerAdded:Connect(onPlayerAdded)
完整代码
请参阅以下代码片段以获取完整的 GoldManager 和 PositionManager 脚本。你可以将它们粘贴并直接在 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)