資料儲存是一項服務,您可以使用它來儲存和加載持久性玩家數據,跨越不同的玩家會話。它們儲存重要的信息,比如玩家的進度或庫存,並允許您在玩家下次加入遊戲時檢索這些數據。如果沒有資料儲存,玩家每次離開遊戲時都會失去所有進度。
資料儲存有兩種類型:標準和有序。本教學將使用有序資料儲存,它們儲存您可以按數值排名和排序的數據,並根據存儲的數值以升序或降序檢索。
使用 黃金追逐創建排行榜教學 - 開始 .rbxl 文件作為出發點,並將 黃金追逐創建排行榜教學 - 完成 .rbxl 文件作為參考,本教學將提供創建自訂玩家排行榜所需的基礎知識,包括以下指導:
- 在有序資料儲存中儲存黃金得分。
- 從所有伺服器中獲取和排序最高得分。
- 在 UI 中顯示自訂排行榜的最高得分。
- 每幾秒自動刷新自訂排行榜。
在本教學結束時,您應該擁有以下內容:
- 在 ServerScriptService 下:
- 更新的 GoldManager 腳本,它將玩家的得分儲存到排行榜的有序資料儲存中。
- 新的 LeaderboardRemoteHandler 模組腳本,它獲取並返回有序排名的最高得分列表。
- 新的 LeaderboardManager 腳本,它回應來自客戶端的排行榜數據請求,充當 LeaderboardGuiScript 中的客戶端和 LeaderboardManager 中的伺服器之間的橋樑。
- 在 StarterGui 下,新建一個 LeaderboardGuiScript 本地腳本,為玩家呈現排行榜 UI。

啟用 Studio 對 API 服務的存取
資料儲存不會儲存在您的設備上,因此您的遊戲依賴伺服器之間與 Roblox 後端的通信才能使用它們。默認情況下,Studio 限制此通信以防止濫用或意外使用。在您明確啟用之前,Studio 中對 API 服務的存取是禁用的,以確保只有受信任的遊戲才能讀取和寫入 Roblox 的後端伺服器。
要啟用 Studio 對 API 服務的存取,以便您可以使用資料儲存:
- 在 Studio 中打開 黃金追逐創建排行榜教學 - 開始 文件。這個起始位置檔案已經包含了 儲存玩家數據 教學中的代碼和玩家排行榜的 UI 骨架。
- 返回 Studio,轉到 文件 ⟩ 體驗設定 ⟩ 安全性。
- 開啟 啟用 Studio 對 API 服務的存取。
- 儲存您的更改。
創建有序資料儲存
在創建資料儲存時,您應該始終在伺服器端的 Script 中調用 DataStoreService。這樣做很重要,因為:
- Roblox 阻止客戶端訪問所有資料儲存,以便只有伺服器有權訪問和修改持久的玩家數據。
- 伺服器端腳本在集中環境中運行,確保您讀取和寫入的資料儲存數據是準確和有效的。
- 由於客戶端腳本在玩家的設備上運行,任何玩家都可能利用遊戲並竊取或覆蓋其他玩家的數據,如果客戶端可以訪問您的資料儲存。
您還應給您的資料儲存一個唯一的名稱,以保持數據的組織性,並防止它在不同的資料儲存中重疊或衝突。在本教學中,用於排行榜的資料儲存稱為 GlobalLeaderboard。
要創建 GlobalLeaderboard 有序資料儲存:
打開現有的 GoldManager 腳本,在 ServerScriptService 下。

在現有的 PlayerGold 資料儲存下,調用 GetOrderedDataStore,並且使用字串 "GlobalLeaderboard"。這將創建一個有序資料儲存,以便在玩家賺取黃金時儲存他們的得分。
local leaderboardStore = DataStoreService:GetOrderedDataStore("GlobalLeaderboard")
創建資料儲存後,您可以在不同的腳本中引用這個相同的資料儲存。例如,您可以在 GoldManager 腳本和稍後在本教學中創建的 LeaderboardManager 模組腳本中都調用 GetOrderedDataStore。在兩個地方引用相同的資料儲存並不意味著您正在複製數據;這僅僅意味著兩個腳本正在訪問相同的集中儲存,就像兩個隊友使用一個文件夾來共享信息一樣。
將玩家數據發送到排行榜
資料儲存由鍵(識別數據)和值(存儲數據)組成。與標準資料儲存不同,有序資料儲存允許您按 值 進行排序,這使它們非常適合創建排行榜等東西。
每個有序資料儲存中的條目都儲存在唯一的鍵下,您可以按數值進行排名和排序,然後根據存儲的數值以升序或降序檢索。這意味著您可以使用有序資料儲存來顯示,例如,隨著玩家在您的遊戲中收集更多黃金而實時更新的前 3 名列表。
| 鍵 | 值 | |
|---|---|---|
| 描述 | 鍵是您可以用來訪問特定數據片段的唯一字串標識符,比如玩家的用戶 ID。 鍵允許您組織數據,讓您可以輕鬆管理和調試它。 | 值是您想要儲存或加載的實際數值,比如玩家的得分。 值允許您在不同的會話中存儲進度,以及在玩家下次加入遊戲時檢索玩家數據。 |
| 允許的格式 | 只能是字串。如果您使用的是數字,比如用戶 ID,請使用 tostring() 將其轉換為字串。 | 只能是數字。 |
| 範例 | "Player_A"、"TopScore"、"123456"(用戶 ID 以字串形式) | 100、3.14、0、-25 |
現在您已創建了 GlobalLeaderboard 有序資料儲存,請更新 GoldManager 腳本中現有的 saveGold 函數,將玩家的黃金得分發送到您的自定義排行榜,使用 SetAsync:
local function saveGold(player, value)
local userId = player.UserId
local success, err = pcall(function()
goldStore:SetAsync(userId, value)
leaderboardStore:SetAsync(tostring(userId), value)
end)
if not success then
warn("無法儲存", player.Name, "的黃金:", err)
end
end
您可以將有序資料儲存 GlobalLeaderboard 想像成一個共享文件夾:SetAsync 將內容添加到文件夾,而 GetSortedAsync(稍後在 LeaderboardManager 模組腳本中創建)則讀取文件夾的內容並按得分進行排序。
獲取排行榜得分
要獲取排行榜得分,您應該使用 ModuleScript。模組腳本允許您組織並重用跨多個腳本的代碼。您可以在模組腳本中編寫一次邏輯,然後從需要的任何地方調用,而不必在不同地方複製相同的邏輯。
當您在模組中儲存排行榜邏輯時,UI 處理程序和伺服器腳本都可以使用相同的功能來獲取最高玩家的得分。如果您需要,例如,更新如何獲取用戶名或如何排序得分,您只需在一個地方更改代碼。更新模組後,所有使用它的腳本也會自動獲得更新的行為。
您的自訂排行榜使用以下有序資料儲存方法來在不同伺服器之間共享數據:
- SetAsync(來自 GoldManager 腳本)用於將玩家得分發送到 GlobalLeaderboard 有序資料儲存。
- GetSortedAsync(在模組腳本中)用於從相同資料儲存中獲取來自所有伺服器的最高得分。 GetSortedAsync 方法是有序資料儲存專用的,並且可以基於特定排序順序、頁面大小和最小值及最大值檢索多個已排序鍵。
要獲取排行榜得分:
在 ServerScriptService 下,創建一個名為 LeaderboardManager 的新 ModuleScript。
在模組腳本的頂部,調用 GetOrderedDataStore,並使用字串 "GlobalLeaderboard"。引用您在 GoldManager 腳本中創建的相同有序資料儲存將允許排行榜 UI 以後獲取和排序頂部玩家得分。
local DataStoreService = game:GetService("DataStoreService")local Players = game:GetService("Players")local leaderboardStore = DataStoreService:GetOrderedDataStore("GlobalLeaderboard")準備一個模組表來保存所有希望暴露給其他腳本的函數和數據。
local leaderboardManager = {}創建一個名為 GetTopScores 的函數來獲取頂部玩家的得分。該函數:
- 調用 GetSortedAsync 以獲取按降序排列的 limit 數量的最高得分。如果調用失敗,將返回一個空列表以防止其餘代碼崩潰。
- 循環遍歷排行榜以獲取條目的表格,使用 GetCurrentPage 和玩家用戶名使用 GetNameFromUserIdAsync。
- 將每個結果添加到最終列表並構建一個具有玩家用戶名及其得分的排行榜表。
- 返回最終的玩家列表並將排行榜數據發送回調用該 leaderboardManager.GetTopScores 函數的任何腳本。
function leaderboardManager.GetTopScores(limit)local success, pages = pcall(function()return leaderboardStore:GetSortedAsync(false, limit)end)if not success or not pages thenreturn {}endlocal results = {}for _, entry in ipairs(pages:GetCurrentPage()) dolocal userId = tonumber(entry.key)local score = entry.valuelocal username = "未知"local ok, name = pcall(function()return Players:GetNameFromUserIdAsync(userId)end)if ok thenusername = nameendtable.insert(results, {Username = username,Score = score})endreturn resultsend使模組可用,通過返回之前創建的模組表。每當您創建模組腳本時,必須返回模組表,以便其他腳本可以訪問並調用其中的函數。
return leaderboardManager
從伺服器請求排行榜數據
為了讓客戶端 UI 顯示排行榜得分,伺服器必須在客戶端請求數據時作出回應。RemoteFunction 允許客戶端從伺服器請求信息並等待回覆。
要從伺服器請求排行榜數據:
- 在 ReplicatedStorage 下,插入一個名為 LeaderboardRemote 的 RemoteFunction。此遠程函數充當客戶端 UI 腳本與伺服器排行榜邏輯之間的橋樑。
- 在 ServerScriptService 下,創建一個名為 LeaderboardRemoteHandler 的腳本。在此腳本中:
調用 ReplicatedStorage(客戶端和伺服器之間的共享空間)、ServerScriptService(持有排行榜模組)和 leaderboardRemote (客戶端將調用的用於請求排行榜數據的遠程函數)。
local ReplicatedStorage = game:GetService("ReplicatedStorage")local ServerScriptService = game:GetService("ServerScriptService")local leaderboardRemote = ReplicatedStorage:WaitForChild("LeaderboardRemote")加載您之前創建的 LeaderboardManager 模組,其中包括從有序資料儲存中獲取最高得分的函數。
local leaderboardManager = require(ServerScriptService:WaitForChild("LeaderboardManager"))設置當客戶端調用 InvokeServer 時的行為。伺服器的回應是獲取前三個得分並將結果發送回客戶端。
leaderboardRemote.OnServerInvoke = function(player)return leaderboardManager.GetTopScores(3)end
在自訂排行榜 UI 中顯示數據
在設置了伺服器端腳本後,創建一個本地腳本,以每 3 秒獲取前三名玩家並在遊戲內的全局 UI 排行榜中顯示結果。
要在您的自訂排行榜中顯示數據:
在 StarterGui ⟩ LeaderboardGui 下,創建一個名為 LeaderboardGuiScript 的本地腳本。
在腳本的頂部,連接到遠程函數。呼叫 ReplicatedStorage 以獲取對伺服器和客戶端之間共享對象的訪問權限,然後呼叫 leaderboardRemote 以允許客戶端向伺服器請求排行榜數據。
local ReplicatedStorage = game:GetService("ReplicatedStorage")local leaderboardRemote = ReplicatedStorage:WaitForChild("LeaderboardRemote")引用 LeaderboardGui 下的現有排行榜 UI 元素。
local gui = script.Parentlocal leaderboardFrame = gui:WaitForChild("LeaderboardFrame")local entriesFrame = leaderboardFrame:WaitForChild("EntriesFrame")local template = entriesFrame:WaitForChild("LeaderboardEntry")創建一個名為 clearEntries 的函數,以清除舊的排行榜條目。該函數循環遍歷 entriesFrame 的所有子項,其中包含排行榜行,並確保在顯示新數據之前完全刷新排行榜。
local function clearEntries()for _, child in ipairs(entriesFrame:GetChildren()) doif child:IsA("TextLabel") and child.Name:match("^Row%d+$") thenchild:Destroy()endendend創建一個名為 renderLeaderboard 的函數,以在排行榜行中插入和顯示玩家數據。 data 對象是伺服器返回的包含條目的表,其中包含玩家的用戶名和得分。
local function renderLeaderboard(data)clearEntries()for i, entry in ipairs(data) dolocal row = template:Clone()row.Name = "Row" .. irow.Visible = truerow.LayoutOrder = irow.Text = string.format("%d%s 名:%s - %d",i, i == 1 and "st" or i == 2 and "nd" or i == 3 and "rd" or "th",entry.Username, entry.Score)row.Parent = entriesFrameendend創建一個循環以定期獲取排行榜數據。每 3 秒循環運行並使用您在 LeaderboardRemoteHandler 腳本中創建的 InvokeServer 函數調用伺服器。如果調用成功,UI 將以更新的得分刷新。
while true dolocal success, data = pcall(function()return leaderboardRemote:InvokeServer()end)if success and data thenrenderLeaderboard(data)endtask.wait(3)end
最終代碼
請參見以下代碼片段,了解完整的 GoldManager、LeaderboardManager、LeaderboardRemoteHandler 和 LeaderboardGuiScript 腳本。您可以將它們直接粘貼並運行於 黃金追逐創建排行榜教學 - 開始 .rbxl 文件中。
GoldManager
GoldManager 腳本創建一個有序資料儲存以管理排行榜的更新。
-- 獲取服務
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local CollectionService = game:GetService("CollectionService")
local DataStoreService = game:GetService("DataStoreService")
-- 更新 UI 的遠程事件
local uiEvent = ReplicatedStorage:WaitForChild("UIEvent")
-- 儲存玩家黃金區塊的資料儲存
local goldStore = DataStoreService:GetDataStore("PlayerGold")
-- 儲存黃金到排行榜的有序資料儲存
local leaderboardStore = DataStoreService:GetOrderedDataStore("GlobalLeaderboard")
-- 創建隨機數生成器以定位黃金區塊
local random = Random.new()
-- 儲存玩家單一會話的黃金區塊的表
local playerGold = {}
-- 常量
local GOLD_VALUE = 10 -- 每個黃金區塊的價值
local GOLD_REGENERATION_BOUNDS = { -- 用於隨機重新定位黃金區塊的區域邊界
minX = -72,
maxX = 72,
minZ = -72,
maxZ = 72
}
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], "黃金")
-- 更新 UI 得分條以反映此會話中的新黃金得分
uiEvent:FireClient(player, {
gold = playerGold[userId],
doTween = true,
showAlert = false
})
-- 等待一段時間然後在新的隨機位置重新生成剛收集的黃金區塊
task.delay(GOLD_REGEN_DELAY, function()
-- 在新的隨機位置重新生成黃金區塊
local nextPositionX = random:NextInteger(GOLD_REGENERATION_BOUNDS.minX, GOLD_REGENERATION_BOUNDS.maxX)
local nextPositionZ = random:NextInteger(GOLD_REGENERATION_BOUNDS.minZ, GOLD_REGENERATION_BOUNDS.maxZ)
chunk.CFrame = CFrame.new(nextPositionX, 50, nextPositionZ) -- 移動區塊到新位置
chunk:SetAttribute("DropCollided", false) -- 重設任何碰撞標誌
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)
leaderboardStore:SetAsync(tostring(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)
-- 使用資料儲存值設置當前會話黃金得分
local currentGold = if success and storedGold then storedGold else 0
playerGold[userId] = currentGold
-- 使用玩家的資料儲存值更新 UI
uiEvent:FireClient(player, {
gold = currentGold,
doTween = false,
showAlert = not success
})
if success then
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)
LeaderboardManager
LeaderboardManager 模組腳本:
- 在 GlobalLeaderboard 有序資料儲存上調用 GetSortedAsync()。
- 使用 GetNameFromUserIdAsync 將用戶 ID 轉換為用戶名。
- 返回一個 { Username, Score } 表的列表,用於頂部排名的玩家。
- 在客戶端請求排行榜數據時被 LeaderboardRemoteHandler 調用。
-- 獲取服務
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
-- 儲存排行榜得分的有序資料儲存
local leaderboardStore = DataStoreService:GetOrderedDataStore("GlobalLeaderboard")
-- 儲存排行榜得分的表
local leaderboardManager = {}
-- 返回最高得分的函數
function leaderboardManager.GetTopScores(limit)
-- 嘗試從資料儲存獲取經排序的排行榜條目頁面
local success, pages = pcall(function()
-- false = 降序(最高得分優先)
return leaderboardStore:GetSortedAsync(false, limit)
end)
-- 如果調用失敗或返回空值,記錄警告並返回空表
if not success or not pages then
warn("獲取排行榜數據失敗")
return {}
end
-- 創建一個表來儲存頂部玩家數據
local topPlayers = {}
-- 循環遍歷排行榜結果當前頁面上的每個條目
for _, entry in ipairs(pages:GetCurrentPage()) do
local userId = tonumber(entry.key) -- 將條目鍵轉換為數字(用戶 ID 存儲為字串)
local score = entry.value -- 值是玩家的得分(黃金量)
local username = "未知" -- 如果查詢失敗則使用的預設名稱
-- 嘗試從用戶 ID 獲取玩家的用戶名
local ok, name = pcall(function()
return Players:GetNameFromUserIdAsync(userId)
end)
-- 如果查詢成功,儲存真實用戶名
if ok then
username = name
end
-- 將此玩家及其得分添加到列表中
table.insert(topPlayers, {
Username = username,
Score = score
})
end
-- 返回包含用戶名和得分的頂部玩家完整列表
return topPlayers
end
-- 返回 LeaderboardManager 模組,以便其他腳本可以使用它
return leaderboardManager
LeaderboardRemoteHandler
LeaderboardRemoteHandler 腳本獲取並返回前三名得分給呼叫的客戶端。
-- 獲取服務
local ServerScriptService = game:GetService("ServerScriptService") -- 訪問伺服器端模組
local ReplicatedStorage = game:GetService("ReplicatedStorage") -- 共享客戶端-伺服器通信對象
-- 等待客戶端用於請求排行榜數據的 RemoteFunction
local leaderboardRemote = ReplicatedStorage:WaitForChild("LeaderboardRemote")
-- 加載包含 GetTopScores 函數的 LeaderboardManager 模組
local leaderboardManager = require(ServerScriptService:WaitForChild("LeaderboardManager"))
-- 為客戶端調用排行榜 RemoteFunction 設置處理程序
leaderboardRemote.OnServerInvoke = function(player)
-- 調用排行榜模組以獲取前三名得分並將其返回給客戶端
return leaderboardManager.GetTopScores(3)
end
LeaderboardGuiScript
LeaderboardGuiScript 本地腳本:
- 每 3 秒使用 Class.RemoteHandler.InvokeServer|InvokeServer 請求伺服器的排行榜數據。
- 使用隱藏的 LeaderboardEntry 模板顯示每個排行榜行。
- 將每行格式化為 "第1名:用戶名 - 得分"。
- 使用 UIListLayout 管理排行榜行的創建、顯示和佈局。
-- 獲取共享存儲以訪問 RemoteFunction
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- 獲取用於從伺服器請求排行榜數據的 RemoteFunction
local leaderboardRemote = ReplicatedStorage:WaitForChild("LeaderboardRemote")
-- UI 引用
local gui = script.Parent -- 本地腳本位於 ScreenGui 下
local leaderboardFrame = gui:WaitForChild("LeaderboardFrame") -- 排行榜的主容器
local entriesFrame = leaderboardFrame:WaitForChild("EntriesFrame") -- 排行榜條目出現的框架
local template = entriesFrame:WaitForChild("LeaderboardEntry") -- 用於創建行的隱藏模板 TextLabel
-- 清除舊排行榜行的函數(但不清除模板)
local function clearEntries()
for _, child in ipairs(entriesFrame:GetChildren()) do
-- 只銷毀名為 "Row1"、"Row2" 等的標籤(保留模板不變)
if child:IsA("TextLabel") and child.Name:match("^Row%d+$") then
child:Destroy()
end
end
end
-- 顯示排行榜數據的函數
local function renderLeaderboard(data)
-- 首先移除舊行
clearEntries()
for i, entry in ipairs(data) do
local row = template:Clone() -- 克隆隱藏模板
row.Name = "Row" .. i -- 給它一個唯一名稱
row.Visible = true -- 使其可見
row.LayoutOrder = i -- 讓 UIListLayout 正確排序
-- 排行榜格式:"第1名:用戶名 - 123"
row.Text = string.format("%d%s 名:%s - %d",
i,
i == 1 and "st" or i == 2 and "nd" or i == 3 and "rd" or "th",
entry.Username,
entry.Score
)
-- 將數據添加到 UI 中
row.Parent = entriesFrame
end
end
-- 主循環:每 3 秒更新排行榜
while true do
-- 向伺服器請求排行榜數據
local success, data = pcall(function()
return leaderboardRemote:InvokeServer()
end)
if success and data then
-- 在排行榜上顯示結果
renderLeaderboard(data)
else
warn("獲取排行榜數據失敗")
end
-- 等待 3 秒鐘再更新
task.wait(3)
end