In order to make a system that scaled well, I didn't want to create a different solution for every different type of unlockable in the game: power ups, abilities, game modes, they're all completely different, but they have one thing in common - they can be unlocked. Enter, interfaces:
Interfaces are a perfect solution for a problem like this. They allow us to have a common interface for a variety of different objects, despite the classes themselves having nothing in common. By implementing this interface in everything that I want to be able to unlock, I only need a single system to manage the whole thing. Now that I've got the interface ready, I'll need that manager to handle it all, for this I'll be using a singleton.
Fairly simple class so far. Contains a queue for all of the unlocks, and gives us a function to add a new unlock to that queue. We have a reset function that clears the queue, and I've bound that to my main game managers reset delegate. Simple right? I know that's not what you're here for though, you want to know how to display this queue in an elegant, scalable manner.
For this, we're going to make a new component for displaying an unlock
So far, it's just boilerplate. A parameter for how long the transition should take, and some references to the title, message and image components. After that, we have 3 more component references that will be initialised automatically by our component. You may have noticed CanvasController, that's part of UiFaderPro, an open source project I created for easy Unity UI fading, which you can get for free.
Fairly basic initialisation code, just grabbing references. The next thing we need is a way to know if our display is currently undergoing a transition, which is quite important for the manager.
The only times that it could be transitioning are when it's both active and not fully opaque; at any other time, it's either fully entered or fully exited.
Now we'll write the functions to show and hide the display. Our show function will copy the data over from the unlock's interface, play our animation and fade in. The hide function will play the animation in reverse and fade out.
Now we're going to head over back to the UnlockablesManager, where we will finish up the code to display all of the unlocks. We could create a new instance of this display for every unlock, but this is wasteful, so instead we will have two that we will alternate between, as 2 is always sufficient. Whilst one is fading out, the other will be fading in, and vice versa.
Bit more complicated than everything else, but it's not too bad, lets break it down.
First of all, we only fire off the coroutine if there's anything to display, and we stop any current coroutines that are running so they don't interfere with each other. Next, we have our ProceedToNext function. This lambda returns true when it is time to proceed to the next unlock. It does this by making sure the current transitions are complete, and that the user has provided some kind of input to advance the queue. We dequeue an unlock from the list and show it to the user, then we enter the while loop. In this loop, we wait until its time to proceed to the next item. We then swap the front and back displays, so that the unused display is now in the front, and the current display is in the back. We then dequeue and show the next power up on the new front display, and hide the back display to hide the last unlock. Swap is a simple reference function that swaps to values.
I hope by reading this article you have a little more idea on how to design systems to be scalable and self managing, learned how to write an unlock system, or just learned a few new tricks! Please feel free to ask any questions in the comments below or by contacting me, and if you haven't already, check out Subsideria! From the 24th of August you'll be able to unlock all of these power ups and abilities for yourself!