Logging Time Played on a Game?

So times has never been my strongest point in scripting, let me just get that out the way.

Me and my developers had this idea of logging days, hours, minutes and seconds played. It would be stored in a DataStore of course and every time the player joins, they could see how many days, hours, etc they have played of the game.

This would open up other possibilities for what I have in mind.

Does anyone know how I could do this? Like I said this isn’t my strongest point lol.

Thanks,

Carl Plandog

5 Likes

You could create a bool value which addeds +1 every second. Then you could divide it by 60 for minutes, another 60 for hours, etc. and save the value whenever the player leaves.

2 Likes

Hey. Do you want to have a system that they can see each digit alone? (In a GUI with sections like ‘total days’, ‘total hours’, ‘total minutes’, etc?)

1 Like

Well I was planning on having it in a “Statistics” Gui which would show it as a GUI so yes similar to that.

1 Like

I think you mean divide it by 60 once for hours, then twice for minutes?

I’ll send you a picture of what I was thinking.

Keep a table and everytime a player joins put there name as key and the value = getasync or 0. Add a while loop every 1 second adding to that value then save it when they leave.

1 Like

Yeah, I’m just making a system right now. :slight_smile:

Finally finished! I’ll get a video and a few screenshots for you.

ServerScriptService should look like this:
image

StarterGui should look like this:
image

LeaderScript (ServerScriptService):

Script (ServerScriptService):

LocalScript (StarterGui > Statistics > Frame):

System working:

Let me know if this is what you wanted. :slight_smile:

[Keep in mind, I had tested this multiple times, so I already have progress. When you join and don’t have any previous data, you start at 0. :slight_smile: ]

6 Likes

The best method(s) to log the player’s total play time is to:

  • Start from 0 seconds
  • When they join, start the timer like this:
game:GetService("Players").PlayerAdded:Connect(function(player)
    local start = tick()

    while player.Parent do
        player.leaderstats.TimePlayed.Value = player.leaderstats.TimePlayed.Value + (tick() - start)
        start = tick()
        wait() -- can extend
    end
end)
  • Connect the value changes from a LocalScript:
local player = game:GetService("Players").LocalPlayer

player.leaderstats:WaitForChild("TimePlayed").Changed:Connect(function()
    -- change the UI based on time change
end)

yourUpdateUIButton.MouseButton1Click:Connect(function()
    -- less rate than previous function
end)
  • Loading and saving can be problematic if used incorrectly.
    • @MaximussDev Your saving and loading does not work well if the data stores decided to fail on the players
    • To fix this, add pcall with some error catching.
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")

local canSave = true
local secondsDS = DataStoreService:GetDataStore("SecondsDataStore")

Players.PlayerAdded:Connect(function(player)
    local stats = Instance.new("Folder") -- do not use second parameter for good reasons from PSA
    stats.Name = "Statistics"
    
    local timePlayed = Instance.new("IntValue", stats) -- exception here, "NumberValue" is alternative
    timePlayed.Name = "TimePlayed"
    
    local success, data = pcall(secondsDS.GetAsync, secondsDS, player.UserId)
    if success then
        timePlayed.Value = data or 0
    else
        canSave = false
    end

    stats.Parent = player
end)

Players.PlayerRemoving:Connect(function(player)
    if not canSave then
        return
    end

    local stats = player.Statistics
    local timePlayed = stats.TimePlayed

    -- no time to break it down with UpdateAsync
    local success, errorMessage = pcall(secondsDS.SetAsync, timePlayed.Value, player.UserId)
end)
  • The saving system above is not flawless, it can accidentally restart player’s data by mistake.
    • This is not the best system AFAIK.
  • I believe you can create the UI through server to the player, but I’m not certain yet.
5 Likes

What was wrong with mine? (Not mad, curious why. :thinking:)

2 Likes

It is clearly the thing about data store failure. It does not protect well against them, thus sometimes causing the player stat to reset to 0 due to failure of GetAsync. Error such as data not saving when SetAsync.

That’s why you have pcall and I think I got the SetAsync wrong.

local success, errorMessage = pcall(function()
    secondsDS:SetAsync(player.UserId, timePlayed.Value)
end)
2 Likes

I’d recommend timestamp based, like so:

local s = {}
Players.PlayerAdded:Connect(function(player)
    s[player] = os.time()
end)

Players.PlayerRemoving:Connect(function(player)
    local timePlayed = os.time() - s[player] -- do something with timePlayed
    s[player] = nil -- cleanup indexes
end)

This prevents the need for extra processing every second.

10 Likes

I keep getting leaderstats is not a valid member of player

Errors like that are very obvious, and you can search for the mistake easily. The most common reasons that this happen are that leaderstats haven’t been loaded to the player or your script stops somewhere.

Some steps to solution is to check with print funtion if your leaderstats script actually works. Use WaitForChild function to allow your leaderstats folder to load first before the rest of the script runs.

You can keep a table in ServerScriptService that has a all the players by their index, and add them to the table. It wont be perfect since the server may lag, but here’s a sample of how your script would look

local DataStoreService = game:GetService('DataStoreService')
local DataStore =  --blah blah blah
local PlayerTimes = {}

spawn(function()
while wait(1) do
for player,timeplayed in pairs (PlayersTimes) do
timeplayed = timeplayed +1
end
end)

game.Players.PlayerAdded:Connect(function(plr)

PlayerTimes[plr.Name] = 0

--OR you can load in their time and have it set instead of adding like im going to do in
--this code
end)

game.Players.PlayerRemoving:Connect(function(plr)
local Data = {}
--Add in all their other data blah blah blah
Data.Time = PlayerTimes[plr.Name]
PlayerTimes[plr.Name] = nil -- I think there's a better way to remove non-number indexes from tables but I dont know it
DataStore:UpdateAsync(theirkey,function(OldData)
local CurrentTime = OldData.Time
Data.Time = Data.Time + CurrentTime
return Data end)
end)

Got a little carried a way and made a little more than I planned to, but in short, this will keep all times the players play in a table constantly counting upwards

Well, first you have to use a unit of measurement. You could use seconds, minutes, or hours as the base unit. The larger you go, the less accurate it will be. Therefore, I recommend storing seconds.

What you would do is create a intValue, and every second increment it by 1. Save the value when a player leaves, and load it when a player joins (You could also autosave it, just make sure it gets saved and loaded).

Then, to convert that time into seconds, minutes, ect. You can do the following:

local value = intValue.Value --Get the player's value

local function getTime(value)
    local day = 24*60*60 --Define how long each unit is relative to seconds
    local hour = 60*60
    local minute = 60
    local second = 1
 
    local days = math.floor(value/day) --Get max days that can fit
    value = value - (day * days) --Remove the days from the value
    local hours = math.floor(value/hour) --Get max hours
    value = value - (hour * hours) --Subtract hours from the leftover amount
    local minutes = math.floor(value/minute) --Get max minutes
    value = value - (minute * minutes) --Subtract from leftover 
    local seconds = value
    return days, hours, minutes, seconds
end

local days, hours, minutes, seconds = getTime(value)
--Do whatever you want with these values
print('Days: '..days..' Hours: '..hours..' Minutes: '..minutes..' Seconds: '..seconds)

This message was composed on a mobile device, sorry for any typos

No, I mean divide the number (which is in seconds) by 60, then divide it again (which is now in minutes), by 60.

So if you have 3600 seconds, if you divided it by 60, it would come up with 60 minutes, which would be the minutes, then it would divide by 60 again to get the hours.

a bool value? have can you add to a bool value