Crossfade gallery, a simple and cool UI element

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!");
        }
    }
}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Input management with Dependency Injection

Object oriented programming has a lot of patterns that can be very useful for making games. One of those patterns is the Dependency Injection, a pattern that helps to decouple classes that would otherwise be tightly connected. So let’s take something that’s really connected and see how dependency injection can help us: the input management.

Wait what’s this Dependency Injection?


Usually if you have a thing (call it client) that uses another thing (call it service), when you change the service, then you have to also change the client. And that’s bad. Let’s say the client is your game logic and you are porting your game from pc to mobile, and that therefore you need to switch from a keyboard + mouse input to a touch one. Since all inputs are changed (perhaps radically since your WASD is now a UI element) you now need to change some input-read line in your game logic even if you used an intermediate class to get those button inputs.

The Dependency Injection way to do it instead is to have the input manager call the game logic functions. Without it knowing whose functions they are. You just set them as callbacks and call them when needed. Who sets the callbacks? The naive option is: the client. But then you still have a direct dependency between the classes. Enter the DIC: Dependency Injection Container. He takes the callbacks from the client and gives them to the service, thus eliminating the dependency between them (and adding another class to your code, that’s not a free lunch).

And what are those de-leee-gates?

Delegate

A delegate is just a way to pass a function as an argument, it can also be stored as a variable and given a type name to be checked so that only the functions that match a certain signature can be stored or passed as a delegate of a specific type.

Let’s read some Input!

    [SerializeField]
    string XbuttonName = "Fire1";
   
// other button names 

    [SerializeField]
    string LeftStickHorizontalName = "Horizontal";
    [SerializeField]
    string LeftStickVerticalName = "Vertical";
//other axis names

First of all we’ll need the names of the input buttons and axis we’re going to read, for this example I’ve used a regular xbox controller. We’ll do this with the old unity input system, not the (currently) experimental one, so we’ll need a string name for it. If you’ve read my other tutorials you know I’ve a personal feud with strings, but this is one of the few cases you really have to use them: if you are building an input manager you don’t want to force whoever uses it to edit code just to rename an input field, so you really want to have that in the inspector, which means a serialized string. Notice that for thumbsticks we’ll need two axis per stick, so two thumbsticks means four axis.

    public static InputManager instance;

    [SerializeField]
    InputManagerDIC inputDIC;

    [SerializeField]
    float triggerSensibility = 0.2f;

As for the other variables, the instance reference will be used to make this class a singleton, the inputDIC is needed to ask for the injection, and the trigger sensibility trashold will be used to get a button behaviour from an axis, because back in my days triggers were fucking buttons and I like it that way.

public delegate void buttonReaction();
public delegate void axisEffect(Vector2 axisVal);

Although we could make this all with predefined System Actions, I’d rather estabilish a more specific interface that reminds whoever writes the game logic code what is supposed to act as a button and what is supposed to act as an axis. It’s just a reminder, nothing more.

good old controller
good old controller
    public static buttonReaction XbuttonPress = delegate () { };
    //other press callbacks ...
    public static buttonReaction XbuttonPressContinuous = delegate () { };
    //other continuous callbacks 
    public static axisEffect leftStickEffect = delegate (Vector2 a) { };
    public static axisEffect rightStickEffect = delegate (Vector2 a) { };
    public static System.Action InputStartRead = delegate () { };

Each callback is initialized to an empty delegate because if for whatever reason we don’t want to use something, we don’t want a nullreference exception to pop out after the change.

Now, we can define a lot of callbacks for each Input since every button has four relevant conditions:

  • just pressed
  • pressed (continuously)
  • just released
  • released (continuously)

In this example I’ll use four buttons and the triggers and read only two condition for the buttons (just pressed and continuous press) and one for the triggers (continuous press), for each of the conditions I want to read I need to define a callback.

The same goes for what to do with thumbsticks, but in that case I just want to read a direction out of them and let the game logic interpret it.

The last callback isn’t really needed but for this tutorial I’ve also built a public repository where you can download a test scene and I need to clean the UI state at the beginning of every frame, so I want a callback for that too.

void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(gameObject);
        inputDIC.LoadInputManager();
    }

As I said before this is going to be a Singleton. And at the beginning of execution we want the DIC to inject his callbacks in the InputManager, so we’ll call his loading function here.

    void Update()
    {
        InputStartRead();
        if (Input.GetButtonDown(XbuttonName))
        { XbuttonPress(); }
        //read other buttonDowns
        if (Input.GetButton(XbuttonName))
        { XbuttonPressContinuous(); }
        //read other buttons
        if (Input.GetAxis(leftTriggerName) > triggerSensibility)
        { leftTriggerPressContinuous(); }
        if (Input.GetAxis(rightTriggerName) > triggerSensibility)
        { rightTriggerPressContinuous(); }

        leftStickEffect(new Vector2(Input.GetAxis(LeftStickHorizontalName), Input.GetAxis(LeftStickVerticalName)));
        rightStickEffect(new Vector2(Input.GetAxis(RightStickHorizontalName), Input.GetAxis(RightStickVerticalName)));
    }

And at last here’s the action. At first we call the “start reading” callback, then for each button we check the relevant states. Notice that for the trigger we read an axis input and only when it’s over the trashold we’ve set before we call a callback just as if it were a regular button. From the game logic standpoint that trigger will be undistinguishable from a button, it even uses the same delegate type for the callback. For the thumbsticks instead we’ll read the two axis in a single Vector2 variable and use that to call the appropriate axisEffect callback.

How about a UI class for testing this?

a really simple ui
a really simple ui

I’ve made it as basic as it gets, sorry but no fancy stuff here:

    [SerializeField]
    Toggle xButton;
    //other toggles
    [SerializeField]
    Text rStick;
    //other texts

For each button I’ll set a toggle on and off, while for the sticks I’ll show the direction in a text. All the references are passed with serialized fields in the inspector.

    public void LogCallTLCont() { ShowLogButton(lTriggerButton, "TL Cont"); }
    public void LogCallTRCont() { ShowLogButton(rTriggerButton, "TR Cont"); }
    public void LogCallA() { ShowLogButton(aButton, "A "); }
    public void LogCallB() { ShowLogButton(bButton, "B "); }
    public void LogCallX() { ShowLogButton(xButton, "X "); }
    public void LogCallY() { ShowLogButton(yButton, "Y "); }
    public void LogCallACont() { ShowLogButton(aButton, "A Cont"); }
    public void LogCallBCont() { ShowLogButton(bButton, "B Cont"); }
    public void LogCallXCont() { ShowLogButton(xButton, "X Cont"); }
    public void LogCallYCont() { ShowLogButton(yButton, "Y Cont"); }
    public void LogCallL(Vector2 direction) { ShowLogAxis(lStick, "L stick with dir", direction); }
    public void LogCallR(Vector2 direction) { ShowLogAxis(rStick, "R stick with dir", direction); }

    void ShowLogButton(Toggle toggle, string text)
    {
        toggle.isOn = true;
        Debug.Log(text);
    }

    void ShowLogAxis(Text field, string text, Vector2 direction)
    {
        field.text = direction.ToString();
        Debug.Log(text + direction);
    }

All the callbacks are actually using the same couple of functions, logging and setting an UI element each time. But who’s going to reset all those toggles when we didn’t read the button’s release? Our reset function of course:

    public void ResetUI()
    {
        xButton.isOn = false;
        yButton.isOn = false;
        aButton.isOn = false;
        bButton.isOn = false;
        lTriggerButton.isOn = false;
        rTriggerButton.isOn = false;
        rStick.text = Vector2.zero.ToString();
        lStick.text = Vector2.zero.ToString();
    }

 It’s Injection time

dependency injection input time
dependency injection input time

Also the DIC is really simple, all it does is to set the callbacks in the InputManager, so it only needs a load function and a field to specify from which class instance it should take the callbacks:

    [SerializeField]
    UserExample target;
    public void LoadInputManager()
    {
        InputManager.XbuttonPress = target.LogCallX;
        InputManager.YbuttonPress = target.LogCallY;
        InputManager.AbuttonPress = target.LogCallA;
        InputManager.BbuttonPress = target.LogCallB;
        InputManager.XbuttonPressContinuous = target.LogCallXCont;
        InputManager.YbuttonPressContinuous = target.LogCallYCont;
        InputManager.AbuttonPressContinuous = target.LogCallACont;
        InputManager.BbuttonPressContinuous = target.LogCallBCont;
        InputManager.leftStickEffect = target.LogCallL;
        InputManager.rightStickEffect = target.LogCallR;
        InputManager.leftTriggerPressContinuous = target.LogCallTLCont;
        InputManager.rightTriggerPressContinuous = target.LogCallTRCont;
        InputManager.InputStartRead = target.ResetUI;

    }

So, as you can see the InputManager has no dependecy towards the client class and the UserExample doesn’t even know that his functions are linked to an input. Any maintenance change on either class will stop here in the DIC and will be as trivial as just changing wich callback is assigned to what variable since that’s all that can happen here.

But what if I just changed Input Settings instead of doing all that?

That’s cool and that’s also the proper way to do it (until you are not porting from pc/console to mobile). Really, until you are not changing between radically different input sources in unity, you’re better off using Unity3d’s input system to remap controls and avoid changing code. I only used the Input management as the easiest-to-explain example, if one thinks this technique is just for that, he’s totally missing the point. This technique can (and according to some people should) be used for absolutely everything.

That’s all folks

Thanks for the read. This time no copy-paste, you get a repository with the whole project already set up and ready to use here. If you have any questions or comments please do express that either in the comments here or just hit me on twitter. And if you don’t want to lose my future stuff, consider my newsletter.

P.S.: I’m currently looking for a job, if you are interested take a look at my portfolio.

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Pooling in unity3d – a simple tutorial

We’ve been through it: instantiations at runtime can be a huge problem. It can cause a spike in the workload of any game, and then you have to find ways to fix that when it happens. Or you can use a technique called pooling, like pros do.

you can have a lot of pools
you can have a lot of pools

What’s pooling in unity3d?

The same it should be anywhere else: instead of instantiating objects wherever and whenever you need them, you create a pool beforehand (while the loading screen is on) and then when it’s needed you grab an object from the pool.

To make a pool you’ll need 3 things:

  • a pool script
  • a pool interface
  • a pool host
    Let’s start with the trivial stuff first.

Pool interface iPoolable

It's objects in a pool, got it?
It’s objects in a pool, got it?

When an object is “in the pool” it’s better if it’s inactive in all conceivable ways. But of course the pool script should not be required to know how to inactivate every single object because that would be a maintenance nightmare. Therefore we’ll need this simple interface to be implemented by one script at the root of the pooled gameobject:

public interface iPoolable
{
  Pool source { get; set; }
  void Initialize();
  void Deactivate();
}

The source property is a reference to the pool script that holds the object’s pool, oviously. Why should the pooled object need to know that? So that it can return to the pool by itself by calling:

source.PutInPool(gameObject);

The Initialize function’s duty is to prep the object to enter the scene in an active state, just as if it was actually instantiated at the very end of the function.

The Deactivate function instead is called when the object would have been removed from the scene weren’t we pooling it. It has to minimize the impact on both memory and cpu/gpu for holding the object in the pool. This usually involves at least a SetActive(false) and a stop on all coroutines on the object if any, but one can get creative and also switch layers and stuff like that.

The pool host

This isn’t a strictly needed element for pooling to work, but if like me you don’t enjoy having a bunch of gameobjects floating around in your game’s hierarchy, you want to do this:

public class PoolHost : MonoBehaviour
{
    public static PoolHost instance;

    void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(gameObject);
    }

    public static void Hold(GameObject poolable)
    {
        poolable.transform.parent = instance.transform;
    }
}

It’s just a singleton with one static function to hold the pooled gameobject inside the one this script is attached to. You could, of course, separate the pools by type, but why?

but why?

The actual Pool script

This script is designed to be used as a field in another script. This approach enables you to do all sorts of customizations, for instance your weapons can have a pool of bullets, so that you can make more weapons by changing the bullet prefab reference in the pool field.

[System.Serializable]
public class Pool
{
    Stack<GameObject> pool;
    [SerializeField]
    int initialPoolSize;
    [SerializeField]
    int expansionPoolSize;
    [SerializeField]
    GameObject prefabReference;

    public void Populate()
    {
        //stuff..
    }

    void ExpandPoolPopulation(int amount, GameObject prefabReference)
    {
        //stuff..
    }

    public GameObject Allocate()
    {
        //stuff..
    }

    public void PutInPool(GameObject handled)
    {
        //stuff..
    }
}

As you can see it’s entirely serializable, so that we may edit its parameters from the inspector when we use it as a field. The initialPoolSize variable is used to set how many gameobjects we should instantiate in the Populate call, while the expansionPoolSize indicates how many should be instantiated when the pool is empity and an allocation is requested.

Guess what the prefabReference is. Right! It’s where you drag-and-drop your prefab from the project explorer.

drag-and-drop

So, the idea is that the object using this pool needs to Populate it at first, then it can Allocate instances from it and when it’s done it (or the pooled object itself) can PutInPool back the pooled thingy.

Now, let’s see the single functions:

    public void Populate()
    {
        pool = new Stack<GameObject>();
        ExpandPoolPopulation(initialPoolSize, prefabReference);
    }

This one is damn easy. It initializes the pool variable with a brand new Stack (of course you can use my garbageless list instead!), then calls ExpandPoolPopulation to instantiate the very first objects in the pool.

    void ExpandPoolPopulation(int amount, GameObject prefabReference)
    {
        for (int j = 0; j < amount; j++)
        {
            GameObject result = GameObject.Instantiate(prefabReference);
            result.GetComponent<iPoolable>().source = this;
            PutInPool(result);
        }
    }

This one is really straightforward too. It just instantiates the prefab a number of times, sets the source for the object and then puts it in the pool. It’s as obvious as it gets.

    public void PutInPool(GameObject handled)
    {
        handled.GetComponent<iPoolable>().Deactivate();

        handled.SetActive(false);

        handled.transform.position = Vector3.zero;
        handled.transform.rotation = Quaternion.identity;
        handled.transform.localScale = Vector3.one;

        PoolHost.Hold(handled);

        pool.Push(handled);
    }

Here the first thing to do is to call the Deactivate function through the iPoolable interface so that it performs the object specific stuff, then we take care of the operations that can be done on every single gameobject, like moving it in the poolHost so that our hierarchy is clean. Obviously you don’t necessarily have to reset the transform values, but if that’s something that causes you problems, well, you are doing something very wrong.

Last thing we do, is to put the reference to the gameobject inside the pool stack, so that its ready to be used.

    public GameObject Allocate()
    {
        if (pool.Count <= 0)
            ExpandPoolPopulation(expansionPoolSize, prefabReference);

        GameObject result = pool.Pop();

        result.SetActive(true);
        result.GetComponent<iPoolable>().Initialize();
        return result;
    }

Now that we know how the stuff gets inside the pool we can look to how it’s taken out of it. The allocate function first of all checks wether the pool is empity or not. It it’s empity you need more stuff, so you have to expand it.
If this allocation is too much for you, just raise the initialPoolSize and put an error message here, but you are more likely to be better off this way.

Then it recovers the gameobject instance from the pool and initializes it so that it’s prepared just as if it was instantiated right now and returns it to whatever script called this function.

That’s all folks!

Thanks for the attention, copy-pasteable code is just ahead. Remember that the populate function is quite expensive so you’ll probably need to use it behind a loading screen, if you don’t need to then you probably don’t need pooling either.

For any question please don’t hesitate to contact me on my twitter account, and if you don’t want to miss my next tutorial consider subscribing to my Newsletter!

And by the way, I’m currently looking for a job, if you think you may have one for me, take a look at my portfolio!

public interface iPoolable
{
  Pool source { get; set; }
  void Initialize();
  void Deactivate();
}


public class PoolHost : MonoBehaviour
{
    public static PoolHost instance;

    void Awake()
    {
        if (instance == null)
            instance = this;
        else
            Destroy(gameObject);
    }

    public static void Hold(GameObject poolable)
    {
        poolable.transform.parent = instance.transform;
    }
}


[System.Serializable]
public class Pool
{
    Stack<GameObject> pool;
    [SerializeField]
    int initialPoolSize;
    [SerializeField]
    int expansionPoolSize;
    [SerializeField]
    GameObject prefabReference;

    public void Populate()
    {
        pool = new Stack<GameObject>();
        ExpandPoolPopulation(initialPoolSize, prefabReference);
    }

    void ExpandPoolPopulation(int amount, GameObject prefabReference)
    {
        for (int j = 0; j < amount; j++)
        {
            GameObject result = GameObject.Instantiate(prefabReference);
            result.GetComponent<iPoolable>().source = this;
            PutInPool(result);
        }
    }

    public GameObject Allocate()
    {
        if (pool.Count <= 0)
            ExpandPoolPopulation(expansionPoolSize, prefabReference);

        GameObject result = pool.Pop();

        result.SetActive(true);
        result.GetComponent<iPoolable>().Initialize();
        return result;
    }

    public void PutInPool(GameObject handled)
    {
        handled.GetComponent<iPoolable>().Deactivate();

        handled.SetActive(false);

        handled.transform.position = Vector3.zero;
        handled.transform.rotation = Quaternion.identity;
        handled.transform.localScale = Vector3.one;

        PoolHost.Hold(handled);

        pool.Push(handled);
    }
}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •