Calculating game time of a day/night cycle.

I maked a simple day/night cycle for my game, it work perfectly, rotating my directional light around X axis using this code:

using UnityEngine;
using System.Collections;
//0.125 degrees rotation corresponds to 1 minute. A global rotation of 90 is equivalent to midday, 270 is equivalent to midnight. 0 degrees is the dawn, 180 is the sunset.

public class DayLightCycle : MonoBehaviour
{
    #region Components
    public GameObject SunLight;
    public Transform SunTransform;
    public Light LightComponent;
    #endregion
    #region H/M
    //Variabili per l'orologio globale
    public int Hour;
    public int Minutes;
    #endregion
    #region Rotation constraint
    public Vector3 MidnightStartDay = new Vector3(0, 0, 0);
    public Vector3 MidnightEndDay = new Vector3(180, 0, 0);
    public const float GradForMinute = 0.125f;
    #endregion
    private Vector3 RotationComponent;

    public float TimeConstraint = 1;

    void Start () {
        LightComponent = SunLight.GetComponent<Light>();
        SunTransform = SunLight.GetComponent<Transform>();
    }

    #region Rotation
    void Update()
    {
        //percentual interpolation for lerping Day and night.
        float PI = 0;
       
        if (PI <= 0)
        {
            PI += (Time.deltaTime * TimeConstraint)/10;

            RotationComponent = Vector3.Lerp(MidnightStartDay, MidnightEndDay, PI);
        }

        SunTransform.Rotate(RotationComponent);
        //Here is the problem, the time displayed isn't exact.
        Hour = ((int)SunTransform.eulerAngles.x) / 15;
        Debug.Log(SunTransform.eulerAngles.x);

    }
    #endregion
}

At dawn it says 2 Hour, at the early night it says 15, then it jumps to 23 and go 22, 21, 20, 7, 6…

How i can fix this?

From my understanding a full day cycle is 360°, so I would expect that MidnightEndDay is something like Vector3(360, 0, 0).
A side note: Although I can’t see where you use it GradForMinute should be 0.25.

Even with 360 the things not change a lot. it stabilize the hour cycle, but they are still wrong… (and maybe i foudn the problem, but first:

Just checked with my calculator, and yeah, you are right! :slight_smile:

My theory now: in the script im assuming: 0 grad rotation equal dawn, but also 0 hours. Here comes the error, when is dawn isnt 0, but 6-7 AM depending on the current season. I can simply add a base value to be added to the hour calculation, like 6, but this works apparently, because when is 24 it returns 30.:face_with_spiral_eyes:

Still looking for a solution…
I tried changing the script in this way:

using UnityEngine;
using System.Collections;


public class DayLightCycle : MonoBehaviour
{
    #region Components
    public GameObject SunLight;
    public Transform SunTransform;
    public Light LightComponent;
    #endregion
    #region H/M
    public int Hour;
    public int Minutes;
    #endregion
    #region Rotation constraint
    public Vector3 MidnightStartDay = new Vector3(0, 0, 0);
    public Vector3 MidnightEndDay = new Vector3(360, 0, 0);
    public const float GradForMinute = 0.25f;
    #endregion
    private Vector3 RotationComponent;

    public float TimeConstraint = 1;

    void Start () {
        LightComponent = SunLight.GetComponent<Light>();
        SunTransform = SunLight.GetComponent<Transform>();
    }

    #region Rotation
    void Update()
    {
        float PI = 0;
     
        if (PI <= 0)
        {
            PI += (Time.deltaTime * TimeConstraint)/10;

            RotationComponent = Vector3.Lerp(MidnightStartDay, MidnightEndDay, PI);
        }

        SunTransform.Rotate(RotationComponent);

        Hour = ((int)SunTransform.eulerAngles.x) / 15 + 6;
        Debug.Log(SunTransform.eulerAngles.x);

    }
    #endregion
}

well, when is night it go to the 30 and return to 6… Im investigating this issue with breakpoints…

Was not able to find a fancy single liner, but you may try this:

Hour = ((int)SunTransform.eulerAngles.x) / 15;

if (Hour < 18)
{
   Hour = Hour + 6;
}
else
{
   Hour = Math.Abs(Hour - 18);
}

I’m new to Unity and C# ; but I would think the easiest route would be a terrain with 2 spheres(children) 1.TheSun which could also be linked(parented) to the 2nd larger sphere (sundial) then you’d need only to manipulated the dial on its X axis ; Again this is simple maybe even crude but it is the bare bones of my recent project-
PS I’m not much of a coder (Hands On) -

Ill try and give you the result.

The your solution isn’t good, the my problem isn’t related to rotating the sun, i already have it rotatin without depend on frame rate. The my problem is calculating the time using sun rotation. Your solution is: heavvy and complicated, and if you not have a terrain or a gameobject to attach the light? You have a lot to learn in optimization :wink:

Ok, so… Now it works, until it arrives to 12:00, the he goes reverse, to 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 and ther returns to work good… Maybe is a gimbal lock issue? (im using euler angles and not quaternions, because i not understand quaternions math…

What you’re doing sounds complicated. Why don’t you use 2 Timers? 1 Timer for day, and when it expires, it starts the Timer for night, which starts the Timer for day upon expiring.

Isn’t complicated, is strange, when the rotation is more of 90 it goes reverse, im thinking this have something related to gimbal lock, use of 2 timers will be more complex, the problem isn’t the algoritmh for calculating the time, is why it not works…

Could you please create a list of the results of
(int)SunTransform.eulerAngles.x
(one for every hour)

private int lastHour = -9999;



Hour = ((int)SunTransform.eulerAngles.x) / 15;
if (Hour != lastHour)
{
   Debug.Log(Hour);
   Debug.Log(SunTransform.eulerAngles.x);
}

So the result is more than 680 logs in console after 1 day cycle (and this is strange, the code should output only 2 messages for hour… )
I report here only 2 strings per hours:

  • 0, 0.25
  • 1, 15.28277
  • 2, 30.4822
  • 3, 45.31
  • 4, 61.58843
  • 5, 75.99202
  • 6, 90
  • 5, 86.3589

An then it go back to 0. So i think is someything related to gimbal lock and the way unity solve this converting from euler angles to quaternions.

Oops,

I forgot something in my example code:

private int lastHour = -9999;
private string logText = "";



Hour = ((int)SunTransform.eulerAngles.x) / 15;
if (Hour != lastHour)
{
   string curText = string.Format("Hour: {0} - Angle: {1}", Hour, SunTransform.eulerAngles.x);
   Debug.Log(curText);
   logText = logText + ", " + curText;
   lastHour = Hour; // <--- I forgot this, sorry
  
   // If the next line works it may help
   // System.Windows.Forms.Clipboard.SetDataObject(logText,true);
}

If you don’t mind, could you please run it again with the fix.

Emh, always 640 log messages…
1871921--120293--Cattura.PNG
perhaps, same problem and same values!

Up, anyone?

Hour = ((int)RotationComponent.x) / 15;

This command doesn’t exist…

O.K. let’s go back to simple.

Can you please try this:

private int curGameHour = 0;  // <- Set to the initial hour
private int lastHour = -1;

// . . .

void Update()
{
   // . . . 

   chkHour = ((int)SunTransform.eulerAngles.x) / 15;
   if (chkHour != lastHour)
   {
      lastHour = chkHour;
      curGameHour++;
      if (curGameHour >= 24)
      {
         curGameHour = 0;
      }
      Debug.Log(curGameHour);
   }

}

It’s unclear to me what you mean; you got an error ?

RotationComponent is a Vector3 calculated in line 39 (post 3#). You use it to rotate the suntransform. Instead of grabbing the transform.eulerangles.x later on, you can simply use the Vector3.x component, being RotationComponent.x

replace:
Hour = ((int)SunTransform.eulerAngles.x) / 15;
by:
Hour = ((int)RotationComponent.x) / 15;

This works here…

For some reasons (like a variable name that seem like a function from the unity namespace. Or a the fact i
was really tired that day) i readed it like a function and i searched it in the scripting api documentation.
Obiusly i found nothing.
When im at home ill test and give you the result.
(Orthographic errors = written from smartphone)

Not works. Because Transform.Rotate is the rotation applied per frame. Returns to me 0.