Kai-Opitz.com

Moderation & Game Developing

A simple Fade-In and Fade-Out Manager in Unity

As I talked with some people about LifeCycle, I had a look back at things that I miss within the project. One of those things is a proper fade in and fade out within the scenes of the game. After those talks, I wanted to realize that, as it could not be that complicated to achieve. And now you can have a look at such a “Fader” and how you can increase the joy of the game.

The Problem

A game always has a sort of a scene change. Will it be the change from the main menu to the game itself or the switching between the levels. Levels themselves do have different visual and auditive themes. And as one can guess, if you switch those themes with a hard cut from one to the other you probably have “bad feeling”. It can be an unpleasant experience and dampens the joy. It also has a somewhat unfinished feeling or an unprofessional “smell”, whenever you experience it.

The Solution (Design)

There are some ways to solve this. For example, you can reduce the number of such scene changes. Open world games – most dominantly – use this. But still, cut scenes are often the case. As for the visuals, a cut has a cinematic approach, while the audio can be interesting to cope with.

If you have a strong level approach within your game, you can do an easy thing: Let the game and player fade into the level and fade out of it. You can simply change the screen to black in multiple gradient steps – and vice versa. And for the audio, you can reduce the volume to 0 or to the maximum, step by step. And that’s basically how you do it in Unity. It’s just that. You can see my results of the following here (and download it at Github.com):


The Solution (Technically)

The Model

As I use Unity, I can already use some libraries this engine has to offer. Besides the handling of time-deltas and the call of Lerp functions, there is not much needed. To have things well formatted in the code, I use the state pattern to have my Fader-script (the one thing attached to a game object) implemented. The state pattern enables the object to have a different behaviour, depending on it’s attribute’s values. And in this case, it is either a fade in or a fade out as a separate state.

In my implementation, the Fader-script is attached to an object in Unity and does have 4 inputs: An audio source (the level’s music/background sound), an Transition-image, the duration of the fade and the name of the following scene.

Fader Class Diagram showing all used classes for the Fader scripts.

Fader Class Diagram showing all used classes for the Fader scripts.


Fader does have a state, that is based on an AFaderStates object. FaderOutState and FaderInState inherit from that abstract class and therefore implement the functions AFaderStates has. So in that case I just have to mention the change of states and the Transit-method in Fader’s Update-method. This Transit-method is called in both states, but do have different implementations. Of course, a Fader-object always starts with the FaderInState.

The Code

public class Fader : MonoBehaviour {

    private Fader Instance;
    protected AFaderStates State;
    public AudioSource AudioSource;
    public string FollowUpScene;
    public Image TransitionImage;
    public float Duration;

    public void Start()
    {
        Instance = this;
        State = new FaderInState(this, Duration); //FaderInState is always used, when the scene starts.
    }

    public void Update()
    {
        State.Transit(); 
        if (Input.GetKey(KeyCode.UpArrow))
        {
            State = new FaderOutState(this, Duration, FollowUpScene);
        }
    }
}

Very important for the solution I implemented is the fact, that Fader is the only class deriving from MonoBehaviour. In my first approach, I had AFaderStates inherit from MonoBehaviour. In that case, each State should run its own Update-method. But instantiating new objects was not possible: Unity does prohibit this option when derived from MonoBehaviour and only allows new objects via AddComponent<T>() or ScriptableObjects. As those appeared to be an overhead, I removed the inheritance.

public abstract class AFaderStates
{
    protected AFaderStates Instance;
    protected Fader Fader;
    protected string NextScene; 
    protected bool isTransitioning;
    protected float Transition;
    public float Duration;

    public void GeneralSetup(AFaderStates state, float duration)
    {
        isTransitioning = true;
        Duration = duration;
        Transition = (state.GetType() == typeof(FaderInState)) ? 1 : 0;
    }
    /// <summary>
    /// Bringing the fade in/out to the screen.
    /// </summary>
    /// <param name="fadeDuration"></param>
    abstract public void Transit();
    /// <summary>
    /// Setting up basic values.
    /// </summary>
    /// <param name="fadeDuration"></param>
    abstract public void Setup(float fadeDuration);
}

The AFaderStates class just includes the abstract functions Transit and Setup. Each is implemented in one of the concrete states. Besides this, GeneralSetup does set some standard values, later needed in the concrete states. Most importantly, it does either give the Transition (a variable, that is used for the lerp of the camera and the audio source) a starting value of either 1 or 0.

public class FaderInState : AFaderStates
{
    /// <summary>
    ///Initializes a new FaderOutState object.
    /// </summary>
    /// <param name="fader"></param>
    /// <param name="stateActive"></param>
    /// <param name="duration"></param>
    public FaderInState(Fader fader, float duration)
    {
        Fader = fader;
        Setup(duration);
    }

    /// <summary>
    /// Refreshes the attributes used from the AbstractFaderStates. 
    /// </summary>
    /// <param name="fadeDuration"></param>
    override public void Setup(float fadeDuration)
    {
        Fader.AudioSource.volume = 0f;
        Fader.AudioSource.playOnAwake = true;
        GeneralSetup(this, fadeDuration);
    }

    /// <summary>
    /// Bringing the fade in to the screen and also increasing the volume of the Fader's music.
    /// </summary>
    /// <param name="fadeDuration"></param>
    override public void Transit()
    {
        if (!isTransitioning)
            return;
        Transition += -Time.deltaTime * (1 / Duration);
        Fader.TransitionImage.color = Color.Lerp(new Color(0, 0, 0, 0), Color.black, Transition);
        Fader.AudioSource.volume = Mathf.Lerp(1f, 0f, Transition);

        if (Transition > 1 || Transition < 0)
        {
            isTransitioning = false;
        }
    }
}

The main methode, to enable the fadings (either the camera or audio) lays in the Transit-method. Transit is used in the Update-methode of a MonoBehavior object. The time-delta between the frames is used to calculate a Transition value, which is used as a step in the lerp function for the color and the audiosource. As the minimum or maximum values are set (1 or 0), we have an exit statement at the end of Transit. For the FaderInState, the Setup including a starting volume of 0 is special compared to FaderOutState.

And for the FaderOutState, the Transit-method just has a positive sign in the Transition-value calculation. Of course, the end-clause does include the statement to load the Followup-scene here. The name, though, needs to be the relative path to the scene, depending on the scripts folder as the root. As Unity does not allow scenes as an input in the editor, I decided for this workaround.

The editor

To have a working solution, the Fader-script needs to be attached to an UI image game object. This game object will create a canvas, that is shown in the camera. As a last step for preparations, the image attached to the canvas needs to be stretched across the whole screen. The image used for this canvas is the one referenced in the Fader-script.

Fader Script with input values in Unity Editor

Fader in Unity Editor

Further usage

One can argue the loading of a scene needs more time than the current LoadScene-statement in the current master branch. Indeed, more sophisticated games need more time to load scenes. However, my system has already enabled the user to load such levels. In this case, the follow up scene is a “Loading-scene”, which only exists for purpose of loading the real next level. As for the fade out, you can also include the loading of a new scene within the start of the fading (within the Setup-method). Depending on the fade duration, you can already use this as a solution.


Next Post

Leave a Reply

© 2019 Kai-Opitz.com