How to Make a Day Night Cycle in Unity with Human Readable Time


Backstory

I’m currently working on a life sim prototype with a game designer I’m close to, and of course, we needed to figure out how to make a day night cycle in unity that could also be used for scheduling events. Needless to say, my first attempt didn’t go smoothly. After a quick brainstorming session with a close friend, Missie, we got a system worked out almost immediately.

Jump to: Full Code, Telling Time

Initial Setup

In this project, I’m using the 3D Universal Render Pipeline template in Unity 2021.3.12f1. If you intend to use the High Definition Render Pipeline, there are a couple of extra steps that really make this system shine.

Once you’re inside your project and have loaded up an exterior scene, make sure that a Directional Light is set to the Sun Source within the “Environment” Tab of the Lighting window. This will allow the sun to be seen moving through the skybox. If you don’t have the lighting window click Window -> Rendering -> Lighting and dock it somewhere useful and out of the way. I chose a tab next to the inspector.

Unity skybox sun source setting

Oh, and change the Environment Lighting Source from “Skybox” to “Color”. This will be used later to control the nighttime lighting.

Unity environment lighting setting set to color

Making the Day and Night Cycle

Full code at the end.

Create a new script. I’ll call mine “TimeProgressor”. Now if you’re new, I’ll go into a little detail here, but feel free to analyze the commented code at the end if you feel you don’t need the depth. I am, however, still going to assume you understand the basics (e.g. variables and calling functions).

using UnityEngine;

public class TimeProgressor : MonoBehaviour
{
    [Range(0, 24)] //Sets slider between 0-24
    public float timeOfDay; //Time in 24.0 hours
    public float orbitSpeed; //Directional Light rotation speed;

    //Updates scene when changes are made in inspector
    private void OnValidate()
    {
        ProgressTime();
    }

    //Called every frame in play mode

    void Update()
    {
        timeOfDay += Time.deltaTime * orbitSpeed;
        ProgressTime();
    }


    private void ProgressTime()
    {

    }
}

Great! Now add it to a game object in your scene. I created an empty and named it “TimeManager” and applied it to that. The [Range(0,24)] attribute, if you haven’t already noticed, creates a nice little slider limited to the range you defined. That’s going to be super handy later.

Day night system progressor
Weird mouse glitch

If you add some time to Orbit Speed, let’s say 0.01, and press play you can watch the Time Of Day slider slowly start to go up in value. Right now you can leave it and continue to make the rest of your game around this speed, since a single minute in-game is about a single second in real-time, which isn’t that bad. Feel free to experiment with this, of course. Now clearly that doesn’t do what you came here for, but it’s the base of everything else now.

This is a great start, but it doesn’t serve much purpose if you can’t see the sun move. Let’s fix that.

public class TimeProgressor : MonoBehaviour
{
    public Light sun;
    public float axisOffset;

    private void ProgressTime()
    {
        float currentTime = timeOfDay / 24;
        float sunRotation = Mathf.Lerp(-90, 270, currentTime);
        sun.transform.rotation = Quaternion.Euler(sunRotation, axisOffset, 0);

        timeOfDay %= 24;
    }
}

Okay, so axisOffset was a little bit of flavor that you don’t need. Just replace that with 0 if you would rather the sun move exactly on the axis.

Do not forget to reference the directional light in these changes:

I’ve bumped up the orbit speed to 1 just to really see it move, but once you hit play everything should be in working order.

Telling Time

The whole point of this day night cycle is to tell exactly what time it is, as if it’s a digital clock. There are a few different ways you can approach a clock. One is the way I tried before, where I would increment time and then move the sun with it, but that required a lot of figurative glue to get working right. The solution the whole time was to have the sun tell me.. just like in real life.

public class TimeProgressor : MonoBehaviour
{
    public int hour;
    public int minute;

   
    private void ProgressTime()
    {

        //Sun rotation code here

        hour = Mathf.FloorToInt(timeOfDay);
        minute = Mathf.FloorToInt((timeOfDay / (24f / 1440f ) % 60));


        //timeOfDay reset code here

    }

Another method of casting the timeOfDay float into an int would have been using hour = (int)Mathf.Floor(timeofDay), though I prefer this method. It is incredibly important that you put “f” for float in the equation, or else it will think that you’re trying to divide ints and of course ints can’t output decimals.

Awesome! Now you could finish here if you wanted but obviously, there are a few things we can do to improve this system. Such as a toggle for 12 and 24-hour clocks, displaying the time in-game, disabling the light at night so there are no shadow artifacts, and night lighting. Let’s set up the 12-hour clock and the night lighting. Here’s how I’m doing it but I wouldn’t be shocked if there was a more elegant way of doing either.

using UnityEngine;

public class TimeProgressor : MonoBehaviour
{
    public int hourPM;
    public Gradient nightLight;


    private void ProgressTime()
    {
       //Previous Sun rotation code
       //Previous Hour and minute code

        RenderSettings.ambientLight = nightLight.Evaluate(currentTime);
        if(hour > 12)
        {
            hourPM = hour - 12;
        }
        if(hour <= 12)
        {
            hourPM = hour;
        }
        if(hour == 0)
        {
            hourPM = 12;
        }
        //timeOfDay reset
    }
}

Set the gradient to something like the following. Remember: feel free to experiment with this!

Double check that your HourPM is working and watch the night transition unfold as you drag the slider. Satisfying right? Well, it would be if the light didn’t bleed through the ground before sunrise. We’ll cover that in the next section.

Wrapping up

We’ve got one last thing to correct before we can say this is ready for a prototype. Light bleed. You may have experienced this already, but if not, let me explain. Just before the sun rises, some light may “bleed” through the terrain, or whatever floor object is in place. Like so:

This is a simple enough fix. We just have to use an Animation Curve, then define what value the sun should be using that.

public class TimeProgressor : MonoBehaviour
{
    public AnimationCurve sunCurve;


    private void ProgressTime()
    {
         //previous ambient light curve code

        sun.intensity = sunCurve.Evaluate(currentTime);

         //previous PM setting code
         //previous time resetting code
        }
    }
}

When adjusting the curve, be sure the numbers are set similar to the below screenshot. Unity will often zoom the editor so that your axis is offscreen. I also moved the start point of the curve to a much later point since sunrise doesn’t start at midnight. Remember this is relative to timeOfDay. Your settings may require a different curve.

Y = Value
X = Time

Unity animation curve for sunlight

In this case Y goes to 1, which is exactly 1 intensity for sun.intensity. If you want it higher or lower be sure to adjust the height of the curve accordingly.

Congratulations! That’s how you make a day night cycle in Unity.

Homework

Create a GUI that displays the time.

Tip: .ToString(“D2”) will format a number with two places. For example, if you had minute = 2 then you would use minute.ToString(“D2”) to output “02”.

Full Code

Direct Download | Github

using UnityEngine;

public class TimeProgressor : MonoBehaviour
{
    [Range(0, 24)] //Sets slider between 0-24
    public float timeOfDay; //Time in 24.0 hours
    public float orbitSpeed; //Directional Light rotation speed;

    public float axisOffset; //optional offset so the sun doesn't rotate directly on the cardinal axis. Earth is 23.5f for example at the time of writing. 
    public Light sun; //The actual light
    public Gradient nightLight;


    public int hour; //display hours in 24 hour clock
    public int minute; //displays current minute

    public int hourPM; //displays time in 12 hour clock

    public AnimationCurve sunCurve; //the rules for the suns intensity. set in inspector

    //Updates scene when changes are made in inspector
    private void OnValidate()
    {
        ProgressTime();
    }

    //Called every frame in play mode

    void Update()
    {
        timeOfDay += Time.deltaTime * orbitSpeed; //increases timeOfDay by the desired amount every frame
        ProgressTime();
    }


    private void ProgressTime()
    {

        float currentTime = timeOfDay / 24; //get how much time has passed
        float sunRotation = Mathf.Lerp(-90, 270, currentTime); //rotate the sun somewhere between -90 and 270 degrees by how much time has passed

        sun.transform.rotation = Quaternion.Euler(sunRotation, axisOffset, 0); //rotate the sun
   
        hour = Mathf.FloorToInt(timeOfDay); //round timeOfDay down to the nearest int
        minute = Mathf.FloorToInt((timeOfDay / (24f / 1440f ) % 60)); //round minute down to the nearest int. Thanks Missie!

        RenderSettings.ambientLight = nightLight.Evaluate(currentTime); //set night ambience light. Without this the scene would be pitch black
        sun.intensity = sunCurve.Evaluate(currentTime); //disable sun under horizon

        if (hour > 12)
        {
            hourPM = hour - 12;
        }
        if(hour <= 12)
        {
            hourPM = hour;
        }
        if(hour == 0)
        {
            hourPM = 12;
        }

        timeOfDay %= 24; //resets day after 24 hours
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *