Unity3D: Day Night Cycle and Transition

A Skyrim-style day-night cycle for Unity3D: rotating sun, light intensity transitions, fog color lerping, and ambient light phases.

This tutorial demonstrates implementing a day-night cycle system similar to those found in games like Skyrim, Oblivion, and Fallout using Unity3D.

Overview

The implementation covers rotating the sun, transitioning light intensity, adjusting fog colors, and modifying ambient lighting. A follow-up article, Advance Skybox Blending, adds skybox transitions on top of this controller.

Main Algorithm

  1. Monitor game time and assign phase states (Day, Dusk, Night, Dawn)
  2. Update sun position via rotation calculations from world center
  3. Adjust light intensity values
  4. Modify fog color based on current phase
  5. Update ambient light color
  6. Handle skybox blend transitions

Implementation Code

using UnityEngine;
using System.Collections;

public class DayNightController : MonoBehaviour
{
	public float dayCycleLength = 1440;
	public float currentCycleTime = 0;
	public float hoursPerDay;
	public Transform rotation;
	public DayPhase currentPhase;
	public float dawnTimeOffset;
	public int worldTimeHour;
	public int minutes;
	private float timePerHour;

	public Color fullLight = new Color(253.0f / 255.0f, 248.0f / 255.0f, 223.0f / 255.0f);
	public Color fullDark = new Color(32.0f / 255.0f, 28.0f / 255.0f, 46.0f / 255.0f);
	public Color dawnDuskFog = new Color(133.0f / 255.0f, 124.0f / 255.0f, 102.0f / 255.0f);
	public Color dayFog = new Color(180.0f / 255.0f, 208.0f / 255.0f, 209.0f / 255.0f);
	public Color nightFog = new Color(12.0f / 255.0f, 15.0f / 255.0f, 91.0f / 255.0f);

	private float dawnTime;
	private float dayTime;
	private float duskTime;
	private float nightTime;
	private float quarterDay;
	private float halfquarterDay;
	private float lightIntensity;

	void Initialize()
	{
		quarterDay = dayCycleLength * 0.25f;
		halfquarterDay = dayCycleLength * 0.125f;
		dawnTime = 0.0f;
		dayTime = dawnTime + halfquarterDay;
		duskTime = dayTime + quarterDay + halfquarterDay;
		nightTime = duskTime + halfquarterDay;
		timePerHour = dayCycleLength/hoursPerDay;
		if (light != null)
		{ lightIntensity = light.intensity; }
	}

	void Reset()
	{
		dayCycleLength = 120.0f;
		hoursPerDay = 24.0f;
		dawnTimeOffset = 3.0f;
		fullDark = new Color(32.0f / 255.0f, 28.0f / 255.0f, 46.0f / 255.0f);
		fullLight = new Color(253.0f / 255.0f, 248.0f / 255.0f, 223.0f / 255.0f);
		dawnDuskFog = new Color(133.0f / 255.0f, 124.0f / 255.0f, 102.0f / 255.0f);
		dayFog = new Color(180.0f / 255.0f, 208.0f / 255.0f, 209.0f / 255.0f);
		nightFog = new Color(12.0f / 255.0f, 15.0f / 255.0f, 91.0f / 255.0f);
	}

	void Start()
	{
		Initialize();
	}

	void OnGUI()
	{
		string jam = worldTimeHour.ToString();
		string menit = minutes.ToString();
		if (worldTimeHour < 10){
			jam = "0" + worldTimeHour;
		}
		if (minutes < 10){
			menit = "0"+minutes;
		}
		GUI.Button(new Rect(500, 20, 100, 26), currentPhase.ToString() +" : "+jam+":"+menit);
	}

	void Update()
	{
		if (currentCycleTime > nightTime && currentPhase == DayPhase.Dusk)
		{
			SetNight();
		}
		else if (currentCycleTime > duskTime && currentPhase == DayPhase.Day)
		{
			SetDusk();
		}
		else if (currentCycleTime > dayTime && currentPhase == DayPhase.Dawn)
		{
			SetDay();
		}
		else if (currentCycleTime > dawnTime && currentCycleTime < dayTime && currentPhase == DayPhase.Night)
		{
			SetDawn();
		}

		UpdateWorldTime();
		UpdateDaylight();
		UpdateFog();

		currentCycleTime += Time.deltaTime;
		currentCycleTime = currentCycleTime % dayCycleLength;
	}

	public void SetDawn()
	{
		if (light != null)
		{ light.enabled = true; }
		currentPhase = DayPhase.Dawn;
	}

	public void SetDay()
	{
		RenderSettings.ambientLight = fullLight;
		if (light != null)
		{ light.intensity = lightIntensity; }
		currentPhase = DayPhase.Day;
	}

	public void SetDusk()
	{
		currentPhase = DayPhase.Dusk;
	}

	public void SetNight()
	{
		RenderSettings.ambientLight = fullDark;
		if (light != null)
		{ light.enabled = false; }
		currentPhase = DayPhase.Night;
	}

	private void UpdateDaylight()
	{
		if (currentPhase == DayPhase.Dawn)
		{
			float relativeTime = currentCycleTime - dawnTime;
			RenderSettings.ambientLight = Color.Lerp(fullDark, fullLight, relativeTime / halfquarterDay);
			if (light != null)
			{ light.intensity = lightIntensity * (relativeTime / halfquarterDay); }
		}
		else if (currentPhase == DayPhase.Dusk)
		{
			float relativeTime = currentCycleTime - duskTime;
			RenderSettings.ambientLight = Color.Lerp(fullLight, fullDark, relativeTime / halfquarterDay);
			if (light != null)
			{ light.intensity = lightIntensity * ((halfquarterDay - relativeTime) / halfquarterDay); }
		}

		transform.RotateAround (rotation.position, Vector3.forward, ((Time.deltaTime / dayCycleLength) * 360.0f));
	}

	private void UpdateFog()
	{
		if (currentPhase == DayPhase.Dawn)
		{
			float relativeTime = currentCycleTime - dawnTime;
			RenderSettings.fogColor = Color.Lerp(dawnDuskFog, dayFog, relativeTime / halfquarterDay);
		}
		else if (currentPhase == DayPhase.Day)
		{
			float relativeTime = currentCycleTime - dayTime;
			RenderSettings.fogColor = Color.Lerp(dayFog, dawnDuskFog, relativeTime / (quarterDay+halfquarterDay));
		}
		else if (currentPhase == DayPhase.Dusk)
		{
			float relativeTime = currentCycleTime - duskTime;
			RenderSettings.fogColor = Color.Lerp(dawnDuskFog, nightFog, relativeTime / halfquarterDay);
		}
		else if (currentPhase == DayPhase.Night)
		{
			float relativeTime = currentCycleTime - nightTime;
			RenderSettings.fogColor = Color.Lerp(nightFog, dawnDuskFog, relativeTime / (quarterDay+halfquarterDay));
		}
	}

	private void UpdateWorldTime()
	{
		worldTimeHour = (int)((Mathf.Ceil((currentCycleTime / dayCycleLength) * hoursPerDay) + dawnTimeOffset) % hoursPerDay) + 1;
		minutes = (int)(Mathf.Ceil((currentCycleTime* (60/timePerHour))% 60));
	}

	public enum DayPhase
	{
		Night = 0,
		Dawn = 1,
		Day = 2,
		Dusk = 3
	}
}

Setup Instructions

  1. Attach script to sun/directional light component
  2. Position sun at dawn location
  3. Set currentPhase to Dawn and currentCycleTime to 0
  4. Create empty GameObject named "sun_pivot" at world center
  5. Assign sun_pivot to the rotation variable
  6. Match project render settings fog color to DawnDuskFog
  7. Match ambient light setting to fullDark
  8. Configure light intensity (recommended: 0.6–0.75)