Non-Monobehavior Game Clock?

Hello all, only recently arrived and have been posting a few answers. Finally my turn to ask a question I can’t find an answer for!

Bit of background, but feel free to skip to the paragraph before the code.

I’m up to the stage in my project where I’m implementing a global clock to run tasks that take time (such as smelting ingots and other crafting tasks) and other daily events, with a current time scale of one IRL second to one minute in game (making it 24 IRL minutes to one in game day, or 1440 seconds).

My first implementation was a Clock ScriptableObject, with a method that gets called repeatedly with InvokeRepeating() by a singleton clock object (currently the time display UI) that increments the minutes in the day by one every one second, which resets to zero when you’ve hit 1440 minutes.

Simple stuff. Anything that needs to access the current time can do so by referencing the SO and I even got a working day/night lighting set-up working with this. Of course, because of the incrementing, shadows slightly jump through the day rather than smoothly gliding. This is particularly noticeable in the early and late hours of the day.

So today I went about smoothing that out. The obvious solution is to just make my minutes a float/double (rather than an int as it currently was) and my clock singleton that can just Time.deltaTime the value.

But the crazy part of me wondered if I could make a clock that didn’t need monobehaviour/singletons? And it’s working in so far as I can set a starting time that will loop around once it passes amount of minutes in a day, with the following monstrous code:

    [SerializeField, Range(0, 1439)]
    private float startingTime;

    [SerializeField, ReadOnly]
    private float startTimeRef = 0;

    private void OnEnable()
    {
        startTimeRef = startingTime;
    }

    [ShowInInspector, ReadOnly]
    public double TimeMinutesDouble
    {
        get
        {
            if(Application.isPlaying)
            {
                float currentTime;

                if (startTimeRef > Time.timeSinceLevelLoad)
                {
                    currentTime = startTimeRef + Time.timeSinceLevelLoad;

                    if(currentTime >= minutesInDay) 
                    {
                        startTimeRef = Time.timeSinceLevelLoad;
                    }
                    else
                    {
                        return currentTime;
                    }
                }
                else if (Time.timeSinceLevelLoad - startingTime > minutesInDay) 
                {
                    startTimeRef = Time.timeSinceLevelLoad;
                }

                currentTime = Time.timeSinceLevelLoad - startTimeRef;

                return currentTime;
            }
            else
            {
                return 0f; 
            }
        }
    }

What I can’t do with this is set the time to a specific time, particularly if that time is less than the present time.

So two questions:

  • Might anyone be able to suggest some logic to set the current time
  • Or, is this just a silly idea and should I just use monobehaviours + singletons like a normal person?

Thanks in advance and apologies for waffling.

I’m sure if you Google around, you can find a clock implementation that is c# and not implement monobehavior or be a singleton. But, general rule of thumb, unless there is a specific reason for doing so, why waste all this time worrying about your clock when you could be working on the rest of your game?

If you have something that works and it does what you need and isn’t for some reason killing your project, I would go with it and move on.

That’s just my two cents. :slight_smile:

By quickly looking through your code, you can replace Time.timeSinceLevelLoad by your own value.
You can do

float myTimeSinceLoad = 0;
void Update()
{
   myTimeSinceLoad += Time.deltaTime;
}

and then the SetTime method would look like this:

void SetTime(float time)
{
   startTimeRef = time;
   myTimeSinceLoad = 0;
}

Keep in mind that startTimeRef can’t be readonly.

//EDIT: But really, your solution seems too complicated. You can get by increasing single value, then performing var time = value % 1440; which will return time of the day for you. All that code is honestly little too complicated for something that simple

You are right that it’s probably worthwhile moving on, though there’s also no harm in asking.

I primarily was trying to avoid having yet another singleton in my game. Though changing my minutes over to a float and incrementing time in update with my clock singleton gets me the smooth shadow movements I was looking for (weird visual artifacting aside).

But yeah, clock’s working, time’s advancing; time to implement more functionality.

The complication lies in the fact that my clock is a scriptable object, thus I don’t have the luxury of using Update. I can of course increment by Time.deltaTime with a monobehaviour, but that’s what I was trying to avoid, hence non-monohaviour in the threat title.

Yes I’m over complicating things, but I thought I’d ask the question nonetheless.

Not sure if there’s anything other than the update functions and coroutines which has a repeated call, and both require Monobehavior.

I can only think of 2 alternatives.

1, is looking into the jobs system which is part of Unity’s DOTS initiative. Since that stuff is multithreaded, there must be something in there which runs recursively outside of the main thread’s update.

2, is to move away from update and have “time” only known when the player asks for it.
eg:

  • you have a block of iron blasting in the furnace.
  • the block of iron only knows the time it was put into the furnace(start time), and the time it is supposed to be done(end time)
  • when the player goes to check, the current time is referenced with the start and end time cached in the iron block to get the running progress. if the time equals or exceeds the end time, the block is done. If not, report percentage of progress.

This should work, I think. Just typed it here and it’s still early in the morning for me.

[SerializeField, Range(0, 1439)]
private float startingTime;

private float currentTime;
private float lastTime;

private void OnEnable()
{
   currentTime = startingTime;
   lastTime = Time.timeSinceLevelLoad;
}

[ShowInInspector, ReadOnly]
public double TimeMinutesDouble // why is this a double?
{
   get
   {
       if(Application.isPlaying)
       {
           // don't go back in time if a new level was loaded?
           if(lastTime > Time.timeSinceLevelLoad)
               lastTime = Time.timeSinceLevelLoad;

           currentTime += (Time.timeSinceLevelLoad - lastTime);
           lastTime = Time.timeSinceLevelLoad;

           return currentTime % 1440f;
       }               
   }
   else
   {
                return 0f;
   }
}

Thanks for the suggestions folks.

I’ve ended up making my time an ever increasing double (to avoid capping the value) with Time.deltaTime in a singleton object. Having the time persist while working in the editor is probably going to be handy, and I’ve set up a number of inspector functions with Odin Inspector to jump the time around.

Only thing I can’t do i scrub through the day like I could before. I’m sure I’ll find a solution for that anyway.

Anything that needs to wait a certain amount of time until completion can just compare the time when it started to the current time, which solves some problems I foresaw with resetting the time to zero as the days passed.

Now to actually get onto these crafting mechanics…