EDIT (12/19/2021 @ 6:05 AM EST): Forgot to parent the instances in the code sample.
A couple of things I want to point out about the code right now:
-
GetService is the canonical way to get services, please be consistent in using it when accessing services. DataStoreService is fetched using it but Players is not.
-
This code does not handle a race condition where the player joins before the script can connect to PlayerAdded. If a player joins before the connections are established they will not have data.
-
While it was correctly pointed out that using the parent argument of Instance.new is deprecated, the updated code sample did not reflect the advice given and the advice given is not in full. It’s important to understand why it’s deprecated; you should only parent after all the properties (and children, if dynamically created) have been set.
-
pcall is a callback. You don’t need an upvalue to store the value of a call, you can either return the result from within the pcall or wrap the method call directly.
-
For maintainability’s sake, please use more than one letter for a variable. “success, result” is straightforward for working with a one-returned-value callback - GetAsync returns two now though, so it should be “success, data, keyInfo” or similar.
Putting that all together, you can get something like this:
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
-- DataStores should also have proper names so it's easier to edit later!
local PlayerData = DataStoreService:GetDataStore("PlayerData")
local function playerAdded(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
local tickets = Instance.new("IntValue")
tickets.Name = "Tickets"
local success, data, keyInfo = pcall(function ()
return PlayerData:GetAsync(tostring(player.UserId))
end)
if success then
if data then
tickets.Value = data.Tickets
else
tickets.Value = 1000
end
else
warn("Could not fetch player data: " .. data)
-- Add other error handling as needed here
end
tickets.Parent = leaderstats
leaderstats.Parent = player
end
local function playerRemoving(player)
-- Don't yield for this or assume WFC returns a non-nil Instance
local tickets = player:FindFirstChild("Tickets")
if not tickets then return end
local data = {Tickets = tickets.Value}
local success, err = pcall(function ()
return PlayerData:SetAsync(tostring(player.UserId), data)
end)
if not success then warn(err) end
end
Players.PlayerAdded:Connect(playerAdded)
Players.PlayerRemoving:Connect(playerRemoving)
for _, player in ipairs(Players:GetPlayers()) do
playerAdded(player)
end
Now the other thing I want to address is the comment at the top where this resource feels the explicit need to clarify that it does not incorporate any DataStore resources.
Data saving is really cool but a large swath of developers creating these resources are dangerously obsessed with creating “easy-to-use” DataStore resources and, like I mentioned earlier, feel the explicit need to clarify that they are different from existing resources. This kind of thinking is pretty self-destructive and not helpful for developers who need deep tooling.
Let me explain: other DataStore resources like DataStore2 and ProfileService are an abstraction on using DataStores, meaning they take care of all the heavy lifting and complex programming that you ideally would want to safely handle a feature as critical as player data while your only concern is learning the developer-facing API to interface with the abstraction.
These resources provide many powerful features such as session locking, session caching, automatic saving, prototyping for easy testing of data-related features without using real data, backups and other useful tooling. Explaining what each of these means individually would defeat the purpose of the resource developer performing the research to understand what these are in order to make a better module but it’s also not the point of my response so I’ll avoid doing that.
What I’m trying to say here is, resources should not pride themselves on trying to be “simple” or not one of the existing resources because in doing that you’re effectively dismissing the level of tooling these resources provide with good reason and that’s counterproductive. Developers can miss out on useful, important or even necessary features that would expedite their development or resolve a number of issues they may have while working with their data layers by settling for resources that just “want to be different” without providing the same level of or better tooling than existing resources.
My advice to you, as well as to other developers who want to create DataStore resources, is first to think “should I?” rather than “could I?”. This comes in two parts: whether or not it would be more effective to create a usage tutorial instead for an existing resource or what you should really keep in mind when you want to create a DataStore resource.
If the problem with existing resources is that the API for a resource feels complicated, then instead of creating a new resource devoid of all the benefits of existing ones try asking for help on relevant venues to comprehend the API. You can take some time to learn it thoroughly and, if warranted, put out a resource that explains the benefits of a module and how to use it in such a way that a novice developer could understand, which may not be too difficult from a fellow novice developer.
If you’re just interested in releasing your own DataStore resource and you’re not particular about the API of existing resources, then do try to do a deep dive into what’s currently available and what kinds of features those resources offer. Ask yourself if you can provide sufficient tooling for working with DataStores much like those resources do or if your resource is too rigid and lacks feature depth. Could you not just pitch a suggestion on the resource for the developer to look at instead? Is what you have in mind for a DataStore “system” able to be integrated into any developers’ code (think: why do most DataStore resources come as modules and not scripts)? Does your resource have enough features to cover important aspects of data handling?
Existing resources don’t come the way they are for no reason. There’s a lot that should go into making the data experience - for both developers on the technical side and players on the gameplay side - the best it can be.