Creating a Flexible Day Night Cycle with Curves

In this tutorial, we're going to learn how you can create a flexible day-night cycle leveraging the power of Curves.

Updated 9 months ago Edit Page Revisions

Motivation

A lot of modern games feature worlds that have a fully fleshed out day-night cycle. With Unreal Engine 5's new Lumen lighting system, sunrises and sunsets especially have never looked better, so it's a perfect time to create one of your own!
However, making such a system isn't trivial. The sunlight has to be rotated the right way at the right speed. The system also has to be customizable and flexible to support different day and night lengths, so the developer can fine tune them to fit their game's exact needs. This tutorial will explore ways in which Curve assets can aid us in making a flexible day-night system.

Table of Contents

  • System Overview
  • Setup
  • Creating a Day/Night Timer
  • Driving Light Rotation with Curves
  • Controlling Light Speed with Curves
  • Making Proper Sun Curves
  • Limitations and Issues
  • Thoughts and Last Words

System Overview

We're gonna create a simple system that rotates a light for the sun around. We're gonna give control over the length of the day, the times the sun dips below the horizon and how quickly it moves during each phase of the day.

Setup

Start by creating a new level from the "Basic" template. That way, we already have all the necessary lighting actors set up.

LevelCreationBasicTemplate.png

Create a new Blueprint that will keep track of the time and manage the light rotation accordingly. I named mine BP_CycleController, but you're free to choose your own name. For simplicity's sake, I will use my chosen name when referring to this Blueprint going forward, though.

Last but not least, I also renamed the existing directional light to "SunLight", just to make its purpose more clear.

Creating a Day/Night Timer

Inside of BP_CycleController, create a few variables:

Variable Name Purpose Category
DayTimer Keeps track of the elapsed time Timer
DayDonePercentage Value from 0.0 to 1.0, used for Curves later Timer
DayLength Specifies the length of a day in seconds Day Night Cycle

All of the above variables are floats. Make sure you make DayLength public by clicking the eye icon next to it in the variables tab: PublicVariableSetting.png

Add the following code to increment DayTimer independent of the frame rate. DayDonePercentage is set correctly, even if DayTimer exceeds DayLength, thanks to the modulo operation. Just for safety and because it is more expressive code in my opinion, it is clamped between 0.0 and 1.0 afterwards:

Driving Light Rotation with Curves

With the timer values being properly set, we can move on to actually rotating the sun light. Create a new function RotateLight to, well, rotate the light. As inputs, add a Light input with the type Directional Light and a RotationCurve of type Curve Vector. Now is also a good time to add variables for those two to the Blueprint as a whole, under the new category Sun:

NewSunVariables.png

To select the sun light in your scene, you can use this handy actor picker:

ActorPickerTool.png

Now let's create the curve asset. You can find it under "Miscellaneous". When creating a new Curve asset, you will be prompted to select the type of curve. Select "Curve Vector" and press "Create":

SelectCurveType.png

I chose to name my asset Curve_SunRotation_Vector to denote the asset type, as well as the type of curve, as per this style guide. You don't need to open it for now, we will edit it later. For now, just set our curve variable to the newly created asset. Now let's write the logic for rotating the sun light based on the values of the curve asset:

Ok, let's break down what's happening here. Firstly, I check if the references are valid to make sure I'm not accessing functions of invalid references. Then I get the current value based on DayDonePercentage. Later on, we will define curve vectors that range from 0 to 1. Here's where DayDonePercentage being clamped between 0 and 1 really comes in handy. Then we turn the resulting vector into a rotator to use for the final sun light rotation after multiplying each member of the vector by 360 (our curve vector values will also be between 0 and 1 since that's easier to work with).

And with that, the rotation logic is done. However, I want to improve it even further by enabling users of the system to slow down/speed up the sun light's journey through the sky during certain times of the day. This can be used to create a sunrise/sunset that lasts longer, for instance.

Controlling Light Speed with Curves

To be able to control the light speed with a curve, we will need to create a new variable, SunProgressionCurve, this time of type Curve Float. Also create a new curve asset of the same type and name it Curve_SunProgression_Float. Then set the newly creating variable to reference the new curve float asset. Next up, we have to modify our RotateLight function like so:

Instead of using DayDonePercentage to determine which rotation value we will use from the curve vector asset, we now get a float value from our new curve float asset. This can be used to influence the speed of the sun's journey across the sky. We could even use this to make the sun go backwards during a certain time of the day! In the above example, I've allowed the user to not specify a progression curve, reverting back to the previous method of using DayDonePercentage if the reference is invalid. Whether you do that too is up to you.

With the logic for rotation being complete, let's actually create a neat rotation and progression curve, so we can finally see our system in action.

Making Proper Sun Curves

Let's start with the rotation curve.

When you open up the curve asset, you will be greeted by the curve editor. If you've never worked with curves before, the editor might look confusing, but it's actually not that bad. Right click anywhere in the right window to bring up a context menu. From there, you can add a new key.

AddKeyMenu.png

Since vector curves consist of three curves for X, Y and Z respectively, this will currently create a key for all three curves. We would like to edit each curve individually. To temporarily disable a curve, press the pin icon displayed in the left window:

DisablingCurves.png

Now let's talk about what we're actually defining here. As you know, rotations in Unreal consist of a roll, a pitch and a yaw. These three values correspond to X, Y and Z respectively:

RollPitchYawFromVector.png

So here's what we're gonna do: we're gonna define the roll (X), pitch (Y) and yaw (Z) of the sun over the course of the day using this curve asset. If you add a key now, you will notice that you can not only set its value, but also its time:

KeyValueTime.png

A time of 0 will correspond to the start of the day, while 1 will correspond to the end of it. 0.5 will thus be noon.

Now you could fiddle around with different values to get something that looks decent, but I'll make it quicker by just giving you a list of values for each individual curve right here:

X: Nothing. We're not gonna add any keys here.

Y: (Time: 0.0, Value: 0.4), (Time: 0.15, Value: 0.5), (Time: 0.5, Value: 0.6), (Time: 0.85, Value: 0.5), (Time: 1.0, Value: 0.4)

Z: (Time: 0.0, Value: 0.0), (Time: 1.0, Value: 0.5)

You can press F to zoom your editor window to fit all the keys inside. This will also adjust the scale of both the X and Y axis individually.

After you've added all the keys, you might want to smooth out the keys on the Y curve, as it's not a smooth curve at the moment:

NotAVerySmoothCurve.png

To do that, select all the keys by holding down Ctrl while clicking on them. All the selected keys will be colored brighter. Now right click anywhere in the right window and select "Auto" in this menu. Your curve should now look like this:

AVerySmoothCurve.png

You can now adjust the tangents of each individual key to smooth out the curve further like so:

KeyTangents.png

And with that, our rotation curve is done. To test it out, remove the progression curve reference (if you're using my exact code with the fallback to DayDonePercentage from above). Also make sure you set DayLength to a reasonable value (like 10.0) and you should see the sun rise after a while, move across the sky and then set! Only to do it all over again shortly afterwards.

So, why do these values work? Y corresponds to the pitch of the sun, which is responsible for making the sun rise above the horizon at 0.15 "day percentage" and making it set below it at 0.85 "day percentage". In the curve editor, the Y curve has a illustrates that quite nicely, if you imagine 0.5 being the horizon.

The Z value corresponds to the sun's yaw. If we didn't set the Z value to increase constantly, the sun would just travel up and down without rotating. Together, the Y and Z curves would to raise the sun above the horizon and make it travel around the player.


Now let's set up a progression curve. The easiest one is just setting a key with (Time: 0.0, Value: 0.0) and (Time: 1.0, Value: 1.0). This would basically emulate how the system would behave without a custom progression curve. To make the sunrise and sunset last longer, you can insert these keys: (Time: 0.15, Value: 0.15), (Time: 0.3, Value: 0.22), (Time: 0.7, Value: 0.78, Time: 0.85, Value: 0.85). Since we defined the sunrise to happen at 0.15 and the sunset to happen at 0.85, this will slow down the sun as soon as it rises above the horizon and until it sets. It speed up again in the middle. You can use the same technique to slow down the sun around 0.5, which will create a longer noon, as an example.

With that, you can a nice sun rotation that slow down during the most beautiful parts of the day, at least in my opinion.

Limitations and Issues

So, what about a moon?

Valid question. You can use the same technique to add a rotating moon into the sky. However, two directional lights in a scene don't always play nicely together. I can get them both to rotate and to alternate lighting up the scene without any issue, but I'm admittedly not skilled enough to get them to both show up as disks in the sky (if that's even possible). I've also had issues with the sky going black when both lights were pointing downwards. Overall, there are some issues that I couldn't figure out since lighting isn't my area of expertise.

Just know that the system is set up to theoretically handle many directional lights at the same time. If you can solve the issues named above, you can have a nice day night cycle that actually involves a moon. If you know how to fix these issues, let me know and I'll update this article with more information about a moon.

Thoughts and Last Words

Thank you for making it this far! I hope you enjoyed my breakdown of this system. Fun (or not so fun) fact: it was actually intended for a scrapped game of mine. All the screenshots in this tutorial were taken from a prototype island area from that game. Even though my project might never see the light of day, all the effort that went into designing and coding my day night system at least lives on in the form of this tutorial. I hope it can help someone create a feature for a game that will actually be finished one day!

As always, if you have any feedback, questions or suggestions, find me in the Wiki Discord. My name is TheCreator there.

As for me, I'll head out now that this tutorial is done! Creator out!