In my game, the player(s) will be able to collect a few select resource types (wood, stone, ect.). They will collect possibly hundreds of these resources, so the default inventory won’t work. My problem is not with making the inventory system, I just don’t know where and how to store the inventory data of every player in an easy to access way. I’ve heard people create a table in server storage for each resource, but I’m not sure if that’s the best way (and im also not sure how to do that). Any help with this would be appreciated.
You could store the data using Data Stores | Documentation - Roblox Creator Hub, or using developer-made resources, right now I’m using Save your player data with ProfileService! (DataStore Module) but I would stick with using normal data stores first before moving onto using other things.
You would store the data as a table, adding onto the resource number each time (wood, stone etc).
Here’s an example of a basic template as to how this would work. Depending on your use-case, this would have to be changed/organised better definitely. I would also recommend to put some of these DataStore methods in your own module maybe in the future?
-- Load our services.
local PS = game:GetService("Players")
local DSS = game:GetService("DataStoreService")
-- Get our data store.
local resourceStore = DSS:GetDataStore("PlayerResourceStore")
local resourceStoreKeyPrefix = "Player_"
-- Blank table example of our resources. This is what the player's data would initially be.
local baseResourceTemplateTable = {
["Wood"] = 0,
["Stome"] = 0
}
--[[
Okay so, think of a data store as a table in a way, right now it starts off as empty.
resourceStore = {}
And now we're going to use :SetAsync to set player data, giving a key identifier to signify that the thing we're savinf
IS the player's data specifically.
resourceStore:SetAsync(resourceStoreKeyPrefix .. tostring(player.UserId), {
["Wood"] = 4,
["Stone"] = 0
})
It's important to use UserId as part of the key, as that's the thing that'll remain unique for that user indefinitely,
names can change, so it's not a reliable KEY identifier.
NOW after a few seconds, resourceStore would look like this in theory, where 01234567 is our 'UserId'.
resourceStore = {
["Player_01234567"] = {
["Wood"] = 4,
["Stone"] = 0
}
}
And when we use :GetAsync(resourceStoreKeyPrefix .. tostring(player.UserId), we're just returning the following:
playerResourceData = {
["Wood"] = 4,
["Stone"] = 0
}
And to access our resources individually, we just have to index it:
print(playerResourceData["Wood"]) -- Output: 4
It really is that simple. :)
]]
--[[ This is the collection of data for every player, we'll be adjusting this during runtime, and saving the player's data
the moment they leave the game. It saves us querying the DataStore repeatedly. ]]
local serverData = {}
-- We can put something in place to save a players data periodically though, every 5 seconds? This might have to be changed
-- depending on the number of players, as there's a rate limit as to how often you can save.
task.spawn(function()
while task.wait(5) do
for _, player in ipairs(PS:GetPlayers()) do
local dataKey = resourceStoreKeyPrefix .. tostring(player.UserId)
-- Check to see if the player has data populated in serverData before we make updates to resourceStore.
-- If data doesn't exist, iterate to the next player.
if not serverData[dataKey] then continue end
-- pcall is essentially a way to silence errors, but handle them in your own way.
local success, updatedData = pcall(function()
return resourceStore:UpdateAsync(dataKey, serverData[dataKey])
end)
if not success then
-- Some retry stuff here, for now we won't.
else
-- updatedData in this case is the data returned (and has definitely saved if it's not null).
-- So here, you could do whatever you need to with it, or just continue to iterate the next player, which is what I'll do.
continue
end
end
end
end)
PS.PlayerAdded:Connect(function(player)
-- Store our key identifier in a variable.
local dataKey = resourceStoreKeyPrefix .. tostring(player.UserId)
-- A pcall function to silence any errors, again, handling our own outcome in the event of an error.
local success, playerData = pcall(function()
return resourceStore:GetAsync(dataKey)
)
if success then
print("Data retrieval succeeded!")
print(playerData)
-- If the retrieval of data succeeds, set it in our serverData under our dataKey (where dataKey is Player_01234567).
-- So now serverData looks like:
--[[
serverData = {
["Player_01234567"] = {
["Wood"] = 0,
["Stone"] = 0
}
}
]]
-- If playerData is nil, set our data to a blank table.
serverData[dataKey] = playerData
if not playerData then
serverData[dataKey] = baseResourceTemplateTable
end
-- We could do a :SetAsync() here of the new data, but not worth it as our loop above will do that for us.
else
-- Do some retry stuff here since it failed.
print("Failed to retriee data...")
end
end)
PS.PlayerRemoving:Connect(function(player)
-- When the player is being removed, we want to use this opportunity to save the data and remove them from the serverData table.
local dataKey = resourceStoreKeyPrefix .. tostring(player.UserId)
local success, setData = pcall(function()
return resourceStore:SetAsync("User_1234", serverData[dataKey])
end)
if success then
print("Data saved!")
else
-- Data didn't save, so we can do some retry stuff again here.
print("Data did not save...")
end
end)
-- So now all we need to do, is modify serverData as our source of truth, and trust that the data store will update the players' data. :)
-- Lets do a while loop, which increments every players' wood resource by 1 every second.
task.spawn(function()
while task.wait(1) do
local dataKey = resourceStoreKeyPrefix .. tostring(player.UserId)
for _, player in ipairs(PS:GetPlayers()) do
serverData[dataKey]["Wood"] += 1
end
end
end)
I hope this helps, any questions just let me know.
PS: Forgot to say, I added comments to explain nearly every step (hoping I haven’t missed any steps!).
The only difference is, we don’t access data like resourceStore[“Player_01234567”][“Wood”], as resourceStore (and any other DataStore) AREN’T tables, but are easier to understand/imagine AS tables.