保存玩家数据

Time:15 分钟

Roblox 支持将数据保存到其服务器上。此功能主要用于存储上次会话的玩家数据,可完整保存其统计数据、道具和其他数据。在本教程中,我们将创建一个系统,该系统自动存储玩家的资金和体验数据,以便在他们再次加入游戏时可取回这些数据。

如果你想在开始本教程前更深入地了解 Roblox 数据存储系统,请参见Articles/Data store|数据存储文章。

API 访问权限必须处于有效状态,以便能访问和测试 Roblox Studio 中的数据存储。请参见Articles/Data store|数据存储文章了解有关启用 API 访问权限的说明。

创建数据模块

在深入了解数据存储前,我们将设置一个系统,用于在游戏过程中记录玩家的资金和体验数据。首先,我们将创建 ModuleScript,这是一种可在其他脚本中引用的特殊脚本。此 ModuleScript 的理想位置是放在 ServerStorage 中。

    
    -- Set up table to return to any script that requires this module script
    local PlayerStatManager = {}
    
    -- Table to hold player information for the current session
    local sessionData = {}
    
    local AUTOSAVE_INTERVAL = 60
    
    -- Function that other scripts can call to change a player's stats
    function PlayerStatManager:ChangeStat(player, statName, value)
    	local playerUserId = "Player_" .. player.UserId
    	assert(typeof(sessionData[playerUserId][statName]) == typeof(value), "ChangeStat error: types do not match")
    	if typeof(sessionData[playerUserId][statName]) == "number" then
    		sessionData[playerUserId][statName] = sessionData[playerUserId][statName] + value
    	else
    		sessionData[playerUserId][statName] = value
    	end
    end
    
    -- Function to add player to the "sessionData" table
    local function setupPlayerData(player)
    	local playerUserId = "Player_" .. player.UserId
    	sessionData[playerUserId] = {Money=0, Experience=0}
    end
    
    -- Connect "setupPlayerData()" function to "PlayerAdded" event
    game.Players.PlayerAdded:Connect(setupPlayerData)
    
    return PlayerStatManager

请注意,PlayerStatManager:ChangeStat() 函数可处理数值非数值变更。这意味着你可以安全地使用数值(正值或负值)或字符串值作为 value 参数来调用此函数。

保存玩家数据

现在,我们开始使用数据存储来存储实际信息。

初始化数据存储

首先,我们将在同一 ModuleScript 中为数据存储添加一个新变量,并调用 DataStoreService/GetDataStore|GetDataStore() 以打开新的 PlayerData 数据存储。

    
    -- Set up table to return to any script that requires this module script
    local PlayerStatManager = {}
    
    local DataStoreService = game:GetService("DataStoreService")
    local playerData = DataStoreService:GetDataStore("PlayerData")
    
    -- Table to hold player information for the current session
    local sessionData = {}
    
    local AUTOSAVE_INTERVAL = 60

你可以方便地命名数据存储。数据存储将在游戏中的所有场景之间共享,因此,如果你的游戏中有多个场景,则这些场景都可以通过名称访问同一个数据存储。

读取/写入初始数据

就下来,让我们更改 setupPlayerData() 函数的工作方式。目前,此函数只在玩家加入游戏时为玩家创建新数据,但此数据不会保存在任何位置!现在,通过访问 playerData 数据存储,我们可以调用 GlobalDataStore/GetAsync|GetAsync() 来检查它是否保存玩家的任何信息。如果此调用返回有效数据,我们会将此数据保存到 sessionData 表,否则会将玩家数据保存到 sessionData 表中。

    
    -- Function to add player to the "sessionData" table
    local function setupPlayerData(player)
    	local playerUserId = "Player_" .. player.UserId
    	local data = playerData:GetAsync(playerUserId)
    	if data then
    		-- Data exists for this player
    		sessionData[playerUserId] = data
    	else
    		-- Data store is working, but no current data for this player
    		sessionData[playerUserId] = {Money=0, Experience=0}
    	end
    end
    
    -- Connect "setupPlayerData()" function to "PlayerAdded" event
    game.Players.PlayerAdded:Connect(setupPlayerData)

退出时保存数据

建议在玩家退出游戏时保存玩家数据。可以通过绑定到 Players/PlayerRemoving|PlayerRemoving 事件的新 savePlayerData() 函数来实现此功能。

    
    -- Function to save player's data
    local function savePlayerData(playerUserId)
    	if sessionData[playerUserId] then
    		playerData:SetAsync(playerUserId, sessionData[playerUserId])
    	end
    end
    
    -- Function to save player data on exit
    local function saveOnExit(player)
    	local playerUserId = "Player_" .. player.UserId
    	savePlayerData(playerUserId)
    end
    
    -- Connect "setupPlayerData()" function to "PlayerAdded" event
    game.Players.PlayerAdded:Connect(setupPlayerData)
    
    -- Connect "saveOnExit()" function to "PlayerRemoving" event
    game.Players.PlayerRemoving:Connect(saveOnExit)

自动保存

最后,有必要让系统处理游戏崩溃等意外事件。为此,可以使用将等待AUTOSAVE_INTERVAL 秒(60)的函数,此函数循环遍历 sessionData 表中的所有玩家,并使用 savePlayerData() 函数保存当前信息。请注意,此函数最初会通过使用 spawn() 函数在协同程序中运行。

    
    -- Function to periodically save player data
    local function autoSave()
    	while wait(AUTOSAVE_INTERVAL) do
    		for playerUserId, data in pairs(sessionData) do
    			savePlayerData(playerUserId)
    		end
    	end
    end
    
    -- Start running "autoSave()" function in the background
    spawn(autoSave)
    
    -- Connect "setupPlayerData()" function to "PlayerAdded" event
    game.Players.PlayerAdded:Connect(setupPlayerData)

实现以上所有函数后,我们将得到一个简单的统计数据保存系统,可用于自动保存玩家数据。

处理数据存储失败

对数据存储的请求(如所有网络调用)可能偶尔会因连接不良或其他问题而失败。正如你在Articles/Data store|数据存储文章中所了解的,应将这些调用打包在 pcall() 中以捕获和处理错误。下面我们将此做法应用于当前脚本:

    
    -- Set up table to return to any script that requires this module script
    local PlayerStatManager = {}
    
    local DataStoreService = game:GetService("DataStoreService")
    local playerData = DataStoreService:GetDataStore("PlayerData")
    
    -- Table to hold player information for the current session
    local sessionData = {}
    
    local AUTOSAVE_INTERVAL = 60
    
    -- Function that other scripts can call to change a player's stats
    function PlayerStatManager:ChangeStat(player, statName, value)
    	local playerUserId = "Player_" .. player.UserId
    	assert(typeof(sessionData[playerUserId][statName]) == typeof(value), "ChangeStat error: types do not match")
    	if typeof(sessionData[playerUserId][statName]) == "number" then
    		sessionData[playerUserId][statName] = sessionData[playerUserId][statName] + value
    	else
    		sessionData[playerUserId][statName] = value
    	end
    end
    
    -- Function to add player to the "sessionData" table
    local function setupPlayerData(player)
    	local playerUserId = "Player_" .. player.UserId
    	local success, data = pcall(function()
    		return playerData:GetAsync(playerUserId)
    	end)
    	if success then
    		if data then
    			-- Data exists for this player
    			sessionData[playerUserId] = data
    		else
    			-- Data store is working, but no current data for this player
    			sessionData[playerUserId] = {Money=0, Experience=0}
    		end
    	else
    		warn("Cannot access data store for player!")
    	end
    end
    
    -- Function to save player's data
    local function savePlayerData(playerUserId)
    	if sessionData[playerUserId] then
    		local success, err = pcall(function()
    			playerData:SetAsync(playerUserId, sessionData[playerUserId])
    		end)
    		if not success then
    			warn("Cannot save data for player!")
    		end
    	end
    end
    
    -- Function to save player data on exit
    local function saveOnExit(player)
    	local playerUserId = "Player_" .. player.UserId
    	savePlayerData(playerUserId)
    end
    
    -- Function to periodically save player data
    local function autoSave()
    	while wait(AUTOSAVE_INTERVAL) do
    		for playerUserId, data in pairs(sessionData) do
    			savePlayerData(playerUserId)
    		end
    	end
    end
    
    -- Start running "autoSave()" function in the background
    spawn(autoSave)
    
    -- Connect "setupPlayerData()" function to "PlayerAdded" event
    game.Players.PlayerAdded:Connect(setupPlayerData)
    
    -- Connect "saveOnExit()" function to "PlayerRemoving" event
    game.Players.PlayerRemoving:Connect(saveOnExit)
    
    return PlayerStatManager

数据存储重试

作为最后一项可靠性措施,有必要在第一次尝试失败后重新尝试保存数据。可以使用 savePlayerData() 函数中简单的 repeat 循环将此功能添加到现有脚本中:

    
    -- Function to save player's data
    local function savePlayerData(playerUserId)
    	if sessionData[playerUserId] then
    		local tries = 0	
    		local success
    		repeat
    			tries = tries + 1
    			success = pcall(function()
    				playerData:SetAsync(playerUserId, sessionData[playerUserId])
    			end)
    			if not success then wait(1) end
    		until tries == 3 or success
    		if not success then
    			warn("Cannot save data for player!")
    		end
    	end
    end

***Roblox官方链接:保存玩家数据