Crossfade gallery, a simple and cool UI element

Crossfade is cool

Last week I’ve released a new UI demo project with some effects made entirely in Unity3D’s UI and it had quite a good reception, in particular at some point I was asked to share.

untitled

Well, I’m not ready to put it on GitHub yet (some parts of the code need more polish before being released without shaming myself), but I do want to share. So I’ll give a downloadable link to the project in my newsletter and start a new tutorial series on the topic here.

Today’s topic is the first panel: the sequential crossfader.

What does crossfade mean?

You have probably seen this transition a gazillion times in your life. It’s when an image fades away and a new one takes its place.

How it’s done is quite simple: for each pixel you lerp its color between the color of the former image and the one of the next.

Luckily for us, we don’t have to even deal with that simple operation, UnityEngine.UI.Graphic has already all that we need.

Let’s wrap Graphic

wrap it tight
wrap it tight

From this class we only need one thing: the CrossFadeAlpha function. But we will need to do 3 things with it.

  1. reset the values to an initial state
  2. fade in
  3. fade out

To do this, we’ll just use a small MonoBehaviour component, but please notice that if you want to do this with whole panels instead of a single graphic, you’ll need to add a canvas group as target and change the functions to manually lerp its alpha value.

So, here’s the code:

using UnityEngine;
using UnityEngine.UI;

public class UICrossFader : MonoBehaviour
{
    [SerializeField]
    Graphic toCrossFade;
    [HideInInspector]
    public float inAlphaLevel = 1f;
    [HideInInspector]
    public float outAlphaLevel = 0f;
    [HideInInspector]
    public float duration = 1f;
    [HideInInspector]
    public bool ignoreTimeScale = false;

    public void FadeIn()
    {
        toCrossFade.CrossFadeAlpha(inAlphaLevel, duration, ignoreTimeScale);
    }

    public void FadeOut()
    {
        toCrossFade.CrossFadeAlpha(outAlphaLevel, duration, ignoreTimeScale);
    }
    
    public void Preset(bool isIn)
    {
        if (isIn)
            toCrossFade.CrossFadeAlpha(inAlphaLevel, 0f, true);
        else
            toCrossFade.CrossFadeAlpha(outAlphaLevel, 0f, true);
    }
}

Why are almost all the variables public but hidden? because if you want to control this behaviour you’ll want to do it by code and you don’t want anyone who doesn’t know what he’s doing to fiddle with it.

As you can see the Preset function can either set something to be visible or invisible (in or out) according to the isIn parameter and does so in just a frame, regardless of the timescale. This is because that function is meant to work behind the scenes, not while the show is going on.

The fade functions are just another wrap for the CrossFadeAlpha with pre-set values. This component is indeed very simple, but keep in mind that not everything in UI is a Graphic, and that also you may want to extend this to do more complex stuff one day.

Managing a sequence with a coroutine Start

flight-1179587_640

    public GameObject listParent;
    [SerializeField]
    float startDelay;
    [SerializeField]
    float interval = 4f;
    [SerializeField]
    bool doLoop = true;

We need just a few variables to decide:

  • what list of elements we’re going to fade (all the children of listParent who have a UICrossFader)
  • after how much time it will start
  • how much time each element should stay visible before we start the fading
  • if this sequence should be looped or not
    IEnumerator Start()
    {
    	bool executeLoop = true;
        UICrossFader[] crossfadeImagesList=null;

        yield return new WaitForSeconds(startDelay);

As you may know (or not, since as of now this seems to be an undocumented feature), the Start function of Monobehaviour can be used as a coroutine. All that you need to do is to change its return type to IEnumerator. I know, it’s crazy, but they did it this way and it IS very comfortable not to write a coroutine call every time you need it.

In the beginning we just declare a couple of variables we’ll need later on and let the execution to wait for the right time to start.

    if (listParent != null)
    {       
        crossfadeImagesList = listParent.GetComponentsInChildren<UICrossFader>();
        if (crossfadeImagesList.Length <= 0)
            Debug.LogError("SequenceFader error, list empity, check that children of listParent do have UICrossFader component");
        else
        {
             //everything else...
        }
    }
    else
    {
        Debug.LogError("SequenceFader error, listParent not assigned!");
    }

The first thing we do is to check that listParent has been set, otherwise an error is due. Then we need to see is if it has stuff to crossfade in it or another error must be raised. And at last, if we have something to crossfade we’ll start doing so.

Now let’s get into that “everything else”:

                foreach (var item in crossfadeImagesList)
                {
                    item.Preset(false);
                }

                System.Collections.IEnumerator crossfadeEnumerator = crossfadeImagesList.GetEnumerator();
                crossfadeEnumerator.MoveNext();
                UICrossFader currentFader = (UICrossFader)crossfadeEnumerator.Current;
                currentFader.Preset(true);
                UICrossFader last = currentFader;
                yield return new WaitForSeconds(interval);

Another bit of initializations is now due: we begin with nothing visible, then we get the first element of the list and set it as visible. We also record it as a last element to have been visible. Then we wait the moment we’ll show the next image.

                while (executeLoop)
                {
                    currentFader = (UICrossFader)crossfadeEnumerator.Current;
                    last.FadeOut();
                    currentFader.FadeIn();
                    last = currentFader;
                    if (!crossfadeEnumerator.MoveNext())
                    {
                        if (doLoop)
                        {
                            crossfadeEnumerator = crossfadeImagesList.GetEnumerator();
                            crossfadeEnumerator.MoveNext();
                        }
                        else executeLoop = false;
                    }
                    yield return new WaitForSeconds(interval);
                }

Usually one would just have the iterator MoveNext be called inside the while condition, but now we may want to stay inside the loop once that all the list is traversed and start from the beginning, so instead we’ll use the executeLoop flag. At each loop we get a new current element to fade in and start the crossfade by calling in the appropriate functions of the UICrossFader, then we update the last and check if this was the last element of the list while at the same time advancing the enumerator with its MoveNext.

to loop or not to loop? this is the question!
to loop or not to loop? this is the question!

If it was the last element, then we have 2 possibilities: if we should not loop, the executeLoop is set to false and we’re out for good; if we should loop then the enumerator is reset to its first element.

After this check we just wait one more interval before continuing the loop.

That’s all folks!

And we’re done, now your images will transition one after another with a nice crossfade. You can use this for all sorts of purposes and even extend it to a “video” fader in which a dialog is played one image after another (I did so for a then killed project back in the day). If you want to get updates on the next tutorials or just download the full project, get on my newsletter, I’ll give a download link for it next week too (maybe with some minor updates since I’m polishing the code to write this tutorial series). For any questions or comments don’t hesitate writing me here or hitting me up on twitter.

P.S.: I’m currently looking for a gamedev job, if you are interested give a look at my portfolio. Also, if you are a fellow Unity3D dev, let’s connect!

using UnityEngine;
using UnityEngine.UI;

public class UICrossFader : MonoBehaviour
{
    [SerializeField]
    Graphic toCrossFade;
    [HideInInspector]
    public float inAlphaLevel = 1f;
    [HideInInspector]
    public float outAlphaLevel = 0f;
    [HideInInspector]
    public float duration = 1f;
    [HideInInspector]
    public bool ignoreTimeScale = false;

    public void FadeIn()
    {
        toCrossFade.CrossFadeAlpha(inAlphaLevel, duration, ignoreTimeScale);
    }

    public void FadeOut()
    {
        toCrossFade.CrossFadeAlpha(outAlphaLevel, duration, ignoreTimeScale);
    }
    
    public void Preset(bool isIn)
    {
        if (isIn)
            toCrossFade.CrossFadeAlpha(inAlphaLevel, 0f, true);
        else
            toCrossFade.CrossFadeAlpha(outAlphaLevel, 0f, true);
    }
}
public class SequenceFader : MonoBehaviour
{
    public GameObject listParent;
    [SerializeField]
    float startDelay;
    [SerializeField]
    float interval = 4f;
    [SerializeField]
    bool doLoop = true;
    bool executeLoop = true;


    IEnumerator Start()
    {
    	bool executeLoop = true;
        UICrossFader[] crossfadeImagesList=null;

        yield return new WaitForSeconds(startDelay);

        if (listParent != null)
        {       
            crossfadeImagesList = listParent.GetComponentsInChildren<UICrossFader>();
            if (crossfadeImagesList.Length <= 0)
                Debug.LogError("SequenceFader error, list empity, check that children of listParent do have UICrossFader component");
            else
            {
                foreach (var item in crossfadeImagesList)
                {
                    item.Preset(false);
                }

                System.Collections.IEnumerator crossfadeEnumerator = crossfadeImagesList.GetEnumerator();
                crossfadeEnumerator.MoveNext();
                UICrossFader currentFader = (UICrossFader)crossfadeEnumerator.Current;
                currentFader.Preset(true);
                UICrossFader last = currentFader;

                yield return new WaitForSeconds(interval);
                while (executeLoop)
                {
                    currentFader = (UICrossFader)crossfadeEnumerator.Current;
                    last.FadeOut();
                    currentFader.FadeIn();
                    last = currentFader;
                    if (!crossfadeEnumerator.MoveNext())
                    {
                        if (doLoop)
                        {
                            crossfadeEnumerator = crossfadeImagesList.GetEnumerator();
                            crossfadeEnumerator.MoveNext();
                        }
                        else executeLoop = false;
                    }
                    yield return new WaitForSeconds(interval);
                }
            }
        }
        else
        {
            Debug.LogError("SequenceFader error, listParent not assigned!");
        }
    }
}

 

Share
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Leave a Reply

Your email address will not be published.