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
- 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
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.
Start by creating a new level from the "Basic" template. That way, we already have all the necessary lighting actors set up.
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
BP_CycleController, create a few variables:
|Keeps track of the elapsed time
1.0, used for Curves later
|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:
Add the following code to increment
DayTimer independent of the frame rate.
DayDonePercentage is set correctly, even if
DayLength, thanks to the modulo operation. Just for safety and because it is more expressive code in my opinion, it is clamped between
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
To select the sun light in your scene, you can use this handy actor picker:
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":
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.
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:
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:
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:
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:
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:
You can now adjust the tangents of each individual key to smooth out the curve further like so:
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!