Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
0

How do i make it so the leaderboard saves when you leave? Other tutorials have not worked for me.

Asked by 1 year ago

This is my leaderboard code:

game.Players.PlayerAdded:Connect(function(player) local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player

local clicks = Instance.new("IntValue")
clicks.Name = "Clicks"
clicks.Value = 0
clicks.Parent = leaderstats

local rebirths = Instance.new("IntValue")
rebirths.Name = "Rebirths"
rebirths.Value = 0
rebirths.Parent = leaderstats

end)

I have tried many other tutorials on youtube and I cannot figure it out.

1 answer

Log in to vote
0
Answered by 1 year ago
Edited 1 year ago

You should use DataStoreService in which you can create/get a data store by using DataStoreService:GetDataStore() with the name of the data store. You can get data by using DataStore:GetAsync() and save data by using either DataStore:SetAsync(), DataStore:UpdateAsync(), or DataStore:IncrementAsync() (only in integers).

Before using Data Stores, make sure API Services are enabled in your game settings or follow this instead.

To detect if a player left, just use Player.PlayerRemoving event.

So your script should look like this:

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("GameDataStore") -- you can name it whatever you want, although renaming could lead to reset of data

local function loadData(player: Player) -- if a player joined
    -- Preparing the values
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = player

    local clicks = Instance.new("IntValue")
    clicks.Name = "Clicks"
    clicks.Value = 0
    clicks.Parent = leaderstats

    local rebirths = Instance.new("IntValue")
    rebirths.Name = "Rebirths"
    rebirths.Value = 0
    rebirths.Parent = leaderstats

    -- Loading the data
    local key = tostring(player.UserId)
    local hasData, data = pcall(DataStore.GetAsync, DataStore, key) -- gets the player's data from the data store

    if hasData then -- if the player has data
        clicks.Value = data.Clicks
        rebirths.Value = data.Value
    else
        warn("Player " .. key .. "'s data failed to load or has no data.")
    end
end

local function saveData(player: Player) -- if a player left
    local leaderstats = player:FindFirstChild("leaderstats")

    if leaderstats and leaderstats:IsA("Folder") then
        local clicks = leaderstats:FindFirstChild("Clicks")
        local rebirths = leaderstats:FindFirstChild("Rebirths")

        if (clicks and clicks:IsA("IntValue")) and (rebirths and rebirths:IsA("IntValue")) then
            local key = tostring(player.UserId)
            local dataToSave = {
                ["Clicks"] = clicks.Value,
                ["Rebirths"] = rebirths.Value,
            }

            local success, err = pcall(DataStore.SetAsync, DataStore, key, dataToSave)

            if success then
                print("Data successfully saved!")
            else
                warn(err)
                print("Saving data failed!")
            end
        end
    end
end

Players.PlayerAdded:Connect(loadData) -- if a player joined
Players.PlayerRemoving:Connect(saveData) -- if a player left 

Our basic data store is done, but it's still not safe. There's a small chance of returning errors, so if you want your data to be safer, you can continue reading.

If we just use DataStore:SetAsync() once, there's a chance it could error. If it errors, we can retry the function by repeating pcall-ing a function until success is true. So it should look like this:

local success
repeat
    success, err = pcall(DataStore.SetAsync, DataStore, key, dataToSave)
until success

We won't do the same to DataStore:GetAsync() because it would yield forever if a player is new to the game because it has no data, making success false.

Next is using UpdateAsync instead of SetAsync. UpdateAsync is faster than SetAsync, however it is not recommended if you're using OrderedDataStores which are mostly used for leaderboards.

The script should look like this:

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("GameData") -- you can name it whatever you want, although renaming could lead to reset of data

local function loadData(player: Player) -- if a player joined
    -- Preparing the values
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = player

    local clicks = Instance.new("IntValue")
    clicks.Name = "Clicks"
    clicks.Value = 0
    clicks.Parent = leaderstats

    local rebirths = Instance.new("IntValue")
    rebirths.Name = "Rebirths"
    rebirths.Value = 0
    rebirths.Parent = leaderstats

    -- Loading the data
    local key = tostring(player.UserId)
    local hasData, data = pcall(DataStore.GetAsync, DataStore, key) -- gets the player's data from the data store

    if hasData then -- if the player has data
        clicks.Value = data.Clicks
        rebirths.Value = data.Value
    else
        warn("Player " .. key .. "'s data failed to load or has no data.")
    end
end

local function saveData(player: Player) -- if a player left
    local leaderstats = player:FindFirstChild("leaderstats")

    if leaderstats and leaderstats:IsA("Folder") then
        local clicks = leaderstats:FindFirstChild("Clicks")
        local rebirths = leaderstats:FindFirstChild("Rebirths")

        if (clicks and clicks:IsA("IntValue")) and (rebirths and rebirths:IsA("IntValue")) then
            local key = tostring(player.UserId)
            local dataToSave = {
                ["Clicks"] = clicks.Value,
                ["Rebirths"] = rebirths.Value,
            }

            local success
            repeat
                success = pcall(DataStore.UpdateAsync, DataStore, key, function() -- saves the data
                    return dataToSave
                end)
            until success
        end
    end
end

Players.PlayerAdded:Connect(loadData) -- if a player joined
Players.PlayerRemoving:Connect(saveData) -- if a player left

BUT IT'S STILL NOT SAFE!!

I can't explain the rest because there's a 10k words limit :( But don't worry there's a link at the very bottom which explains everything!

So the final script should look like this:

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("GameData") -- you can name it whatever you want, although renaming could lead to reset of data

local function waitForRequestBudget(requestType: Enum.DataStoreRequestType)
    local currentBudget = DataStoreService:GetRequestBudgetForRequestType(requestType)
    while currentBudget < 1 do
        currentBudget = DataStoreService:GetRequestBudgetForRequestType(requestType)
        task.wait(5)
    end
end

local function loadData(player: Player) -- if a player joined
    -- Preparing the values
    local leaderstats = Instance.new("Folder")
    leaderstats.Name = "leaderstats"
    leaderstats.Parent = player

    local clicks = Instance.new("IntValue")
    clicks.Name = "Clicks"
    clicks.Value = 0
    clicks.Parent = leaderstats

    local rebirths = Instance.new("IntValue")
    rebirths.Name = "Rebirths"
    rebirths.Value = 0
    rebirths.Parent = leaderstats

    -- Loading the data
    local key = tostring(player.UserId)

    waitForRequestBudget(Enum.DataStoreRequestType.GetAsync)
    local hasData, data = pcall(DataStore.GetAsync, DataStore, key) -- gets the player's data from the data store

    if hasData then -- if the player has data
        clicks.Value = data.Clicks
        rebirths.Value = data.Value
    else
        warn("Player " .. key .. "'s data failed to load or has no data.")
    end
end

local function saveData(player: Player, dontWait: boolean?) -- if a player left
    local leaderstats = player:FindFirstChild("leaderstats")

    if leaderstats and leaderstats:IsA("Folder") then
        local clicks = leaderstats:FindFirstChild("Clicks")
        local rebirths = leaderstats:FindFirstChild("Rebirths")

        if (clicks and clicks:IsA("IntValue")) and (rebirths and rebirths:IsA("IntValue")) then
            local key = tostring(player.UserId)
            local dataToSave = {
                ["Clicks"] = clicks.Value,
                ["Rebirths"] = rebirths.Value,
            }

            local success
            repeat
                if not dontWait then
                    waitForRequestBudget(Enum.DataStoreRequestType.UpdateAsync)
                end
                success = pcall(DataStore.UpdateAsync, DataStore, key, function() -- saves the data
                    return dataToSave
                end)
            until success
        end
    end
end

for _, player in ipairs(Players:GetPlayers()) do
    coroutine.wrap(loadData)(player) -- incase PlayerAdded event didn't work, we simply load everyone's data
end

Players.PlayerAdded:Connect(loadData) -- if a player joined
Players.PlayerRemoving:Connect(saveData) -- if a player left
game:BindToClose(function() -- if the game is being shutdowned
    if RunService:IsStudio() then
        task.wait(2)
    else
        local finished = Instance.new("BindableEvent")
        local allPlayers = Players:GetPlayers()
        local leftPlayers = #allPlayers

        for _, player in ipairs(allPlayers) do
            coroutine.wrap(function()
                saveData(player, true)
                leftPlayers -= 1
                if leftPlayers == 0 then
                    finished:Fire()
                end
            end)()
        end

        finished.Event:Wait()
    end
end)

This tutorial came from this DevForum post so credits to him!

Ad

Answer this question