Dev Blog

Hi, this is the Stranded III development blog (see also Forum Thread, Comment Thread).

Overview (115 Entries)

Entry 96 - Time - November 7, 2020

Disclaimer: Warning! Boring blog post without ANY picture ahead!

I worked on a lot behind the scenes stuff recently.
One of the topics I took care of was time. More precisely what I call the "game time" which measures exactly how long you played. Time is very important because it starts/stops a lot of things in the game.
E.g. when you have a "bleeding" status effect it will reduce your health by X every Y seconds.

In Stranded III you can perceive the expired game time in two ways:
• There's a visible day and night cycle which heavily influences the game play (because at night it's hard to see stuff etc.)
• I also decided to add a visible timer to the in-game menus. So whenever any in-game menu is opened you can see the current time.

That timer will help you a lot to plan your day (and night) more efficiently while playing. It also shows you how many days you survived.

Time Factor
Game time is measured in real time seconds internally. This means one real second also increases the game time by one second. The displayed time however comes with a factor because playing in real time would be too boring. You would have to play a whole day to experience a full day night cycle. Nobody wants to do that! I'm still experimenting to find a good factor for Stranded III.

Here's how long a day - a full day and night cycle - takes in some games (can't guarantee that all of those are really correct):
• Minecraft: 20 min (72 * real time)
• Gothic (one of my favorite games): 96 min (15 * real time)
• Stranded Deep: 40 min (36 * real time)
• Terraria: 24 min (60 * real time)

Doing it like Terraria where one real minute is exactly one game hour is very tempting because it's so straightforward.

Time Precision (Technical)
When you're a software engineer and you have to measure or save something, one of the first questions which comes to your mind (or should come to your mind) is: Which data type should I use?

Interestingly enough Unity decided to go for a regular 32 bit floating-point (aka single-precision floating-point).
That value is in seconds so the decimals are required to express split seconds.

This is an acceptable approach but it comes with problems due to the way floating-point numbers work internally:
The farther the value is away from 0 the lower the precision! Oh no!
So when your game session doesn't take too long, everything is fine. You have quite a good time precision which is absolutely essential for games to work properly.
If your session takes longer however the precision starts to fade. This in return can make the game entirely unplayable.
Here's a post from a person who made a nice table about 32 bit floating-point precision.

Example 1: Playing for one day
A day (24h) has 24*60*60 = 86,400 seconds.
When you are using 32 bit floating-point and play for 24h, time precision will only be ~10 milliseconds (~0.01 sec).
That's already a lot in gaming if you consider that a single game frame with 60 FPS takes just 1,000 / 60 = 16.666 ms = 0.0166 sec.

Example 2: Playing for one week
A week is 7 * a day so it's 7 * 86,400 = 604,800 seconds.
According to the table this leads to a precision of only ~60 milliseconds (~0.06 sec).
Depending on where and how floating-point numbers are used, this is definitely enough imprecision to entirely break a game.

I found a very nice article explaining the issues with that in detail.

So the solution to the issue is to either use double or long (both twice as large, 64 bit) as data type for time values.

When saving the game however I will only store the absolute game time as double.
All other game time related values will be stored as 32 bit floating-point values with the game time subtracted to save memory.

E.g. there is a timer which is set to expire in five seconds. So it is set to
expirationTime = gameTime + 5f

Now instead of saving that (possibly huge) number as double, I only save gameTime once with the save game (as double) and subtract gameTime from expirationTime (so that it is just 5 again):
storedExpirationTime = expirationTime - gameTime

That way I can store it as float because it's a much lower value now.
When the game is loaded, I simply add the stored gameTime value again to get the old value:
expirationTime = storedExpirationTime + storedGameTime

32 bit saved! Huge success!
Doesn't sound like much but it makes a difference when you have a lot of time values.
Oh and of course gameTime also has to be set to storedGameTime when loading a game. Otherwise stuff would explode in horrible ways.


blog comments powered by Disqus