Fading the Music on a Scene Change

A lot of games in Unity are organized into several scenes, most notably a title scene and a play scene.  If your game has background music, you're likely to want different background music for each scene.

Just sticking a music clip on an AudioSource in the scene would accomplish that, but the music would cut off abruptly when you change scenes, which is jarring and unprofessional.  Much better to fade it out over several seconds.  That requires not letting the object be destroyed when the scene changes, and handling the scene-change event — which thanks to recent changes in the Unity API, is not as easy as it used to be.

 

Here's a script that does the job, and also demonstrates the correct way to respond to scene changes in code these days.

/*
Attach this to an object with an AudioSource to have it
stick around and then fade out when we switch scenes.
*/

using UnityEngine;
using UnityEngine.SceneManagement;

public class FadeAudioOnSceneLoad : MonoBehaviour {
	#region Public Properties
	
	public float fadeOutTime = 3f;
	
	#endregion
	//--------------------------------------------------------------------------------
	#region Private Properties
	
	AudioSource audioSource;
	bool fading;
	float fadePerSec;
	
	#endregion
	//--------------------------------------------------------------------------------
	#region MonoBehaviour Events
	void Start() {
		audioSource = GetComponent();
		Debug.Assert(audioSource != null);
		
		Object.DontDestroyOnLoad(gameObject);
		SceneManager.sceneLoaded += OnSceneLoaded;
	}
	
	void OnDestroy() {
		SceneManager.sceneLoaded -= OnSceneLoaded;
	}
	
	void Update() {
		if (fading) {
			audioSource.volume = Mathf.MoveTowards(
			    audioSource.volume, 0, fadePerSec * Time.deltaTime);
		}
	}
	
	#endregion
	//--------------------------------------------------------------------------------
	#region Private Methods
	
	void OnSceneLoaded(Scene loadedScene, LoadSceneMode mode) {
		fading = true;
		fadePerSec = audioSource.volume / fadeOutTime;
		Destroy(gameObject, fadeOutTime);		
	}
	
	#endregion
}

That's it!

You can register for the scene change event in either Awake or Start, but there is an important difference.  Awake may fire before the first scene (the one originally containing this object) is fully loaded.  As such, the callback would be invoked right away, which in this case, would cause the music to fade out.  That's not what we want here, so we register the callback in Start instead.  Start always fires after the scene is fully loaded.  And so the callback will first get invoked on the next scene change.

When that scene change happens, we just set a flag that lets the Update method know it should fade out the music, and schedule the object to be destroyed after that's done.

To use this, just save the above as FadeAudioOnSceneLoad.cs, and then attach this script to an object in your scene with an AudioSource component (probably configured to play on awake and loop).  And then you can forget about it — when another scene is loaded, this object will stick around just long enough to fade out, and disappear.

Sure, this blog post is not exactly a hearty meal... but sometimes a tasty little morsel is all you need.  Happy holidays!