2,579 total views, 2 views today
Hey all, what we are going to do today is try and create a modular UI system where we can add and remove UI screens with a little bit of ease.
But first let us look at a common implementation of a UI system and how we normally think of creating it. We consider UI of a game to be made of just some panels and transitions between them. We are just going to be concentrating on the navigation and switching between the screens as going deep in creating the full functional UI like our example is out of scope of this. Let’s take an example, shall we?
Here is a game called “Rise Up” an interesting game but what we want to see is how would anyone go by implementing this UI system and how can it be made a little better.
Here is a common implementation, easy to understand and can be used in something similar with approximately the same number of panels.
Before we start discussing about the code above, I want to explain the code above about the Gameover/Home pair and Challenge/Simple Gameplay pair. Both the pairs are very similar and can be modified just a little to show the other but that might not be the case for everyone so don’t stick to that.
The problems start appearing when we start to add a few more panels and the transitions in the game, the code complexity increases more than we can handle it efficiently and easily. What will happen is we will have to manually write activation and deactivation calls for each transition and create new functions calls where ever the need be. Here in this little example we have 8 screens and 8 transitions. Now assume that we want to create a different Game over screen and then create a transition back to Home. We will need to add and remove the following,
We had to mess around the code in a couple of places which is not that ideal. And this much fussing around the code will eventually cause us to miss a transition or two creating unexpected results, albeit easier to debug but harder to maintain and extend. Consider you wanting to add a level selection screen with 9 panels being accessed from Home, now that will your code undesirably long, and that too only for the navigation purpose.
What I have been using since a couple of years is something a little different. Let us first analyze what we can do in our current scenario. We can say we have 6 root panels having either different content containers or different content. The root panels would be, Home, Shop, Gameplay, Help, Gameover and Subscription Purchase. The shop will include the balloon and protector shop so we can say that they can have different content containers. The Gameplay panel will contain the Challenge HUD and the Simple HUD with only the difference being the top content.
Now using this understanding, we can say we have 6 Modules and 2 of these have something more. To handle that something more we are going consider the Shop. The same way we decided we can call our Root Panels Modules let us call the Balloon and Protector Shop Sub-Modules. Now for the last stretch before we start writing some code. What if we create a lot more options for the balloon and we do not want a scroll as that would be counter intuitive to out UX. Let us create a few pages and their selectors so that we can switch to them after selecting to shop for balloons. It Will look like that.
How to solve this problem? It’s simple we will have Sub-Modules contain other Sub-Modules. Now the concern is handling transitions. We will use a hierarchical approach to route navigation requests. The navigation request will always be sent to a level above which the transition is to take place but with exceptions to pop-ups. We are not going to discuss them for now. If you want to know more about them in this Modular UI system, make sure to comment here, on Facebook and Twitter and I will surely continue this series of tutorials.
The hierarchy will look something like this.
Now using the tiers to understand requests to access other modules and sub-modules. Let us say you are at page-2 of Balloons Shop and you want to go to page-4 we will route the request to Balloons Sub-module as it is responsible for navigations directly under it. Now after selecting the balloon we want to go back to Home, we will route this request to the UI Manager which will switch from Shop to Home. I hope you get how we want to structure the system and its delegation of responsibilities.
Now, we have to figure out what will happen if we switch to a Module and a Sub-Module. Will it show the first registered Sub-Module? Will it show the First Sub-Module in the hierarchy? Should we specify a Default Sub-Module to activate? Will it have a last known accessed Sub-Module which we can use? That is sort of in our hands how we want do implement this and I have found it easier to let the game’s UX decide which approach to take. For now, I will be using the first in hierarchy approach, i.e. when a Module or Sub-Module is accessed the first known Sub-Module that has the smallest sibling index will be activated.
So, let’s start by creating the UI Manager and an “enum” to tag the Modules with. The enum for the possible Modules should be something like this,
Notice that we have used ModularUI namespace and created a subspace of “Types” in it. We will be using namespaces to keep our code easy to maintain by separating each major group. And also, to make our code more friendly to a broader scope of projects. There might be cases where a project might be using a name similar to ours and once, they import our Modular UI they will start getting compilation errors which will have to be solved by either extensive refactoring or namespacing either ModularUI or their code to stop the conflicts. We will be using namespaces just to avoid those issues, better safe than sorry.
Now it’s time for the “UIManager”,
Just for the observant few if you have noticed “currentModule?.Deactivate ();” the “?” is for a null check  introduced in C# 6 which is part of the .Net 4.6 and above. And since I’m using Unity 2018.3 I have been using these C# 6 features. If you get errors in these scripts about these lines of code then I would suggest you to update the “Scripting Runtime Version” to “.NET 4.x Equivalent” in the Player Settings for your project if you are using anything from Unity 2017.2 and above. If you are using older versions of Unity then I would request you to please refactor these to null checks and then function calls.
And I have used an Enun Attribute which lets us use enum values in OnClick Calls to make it easier for us to use the “GotoModule” functions with integer arguments. We cannot use enum values in any Unity event as Unity does not serialize the enum. Enun Attribute I modified and used can be found here.
Now let’s move on to the Module and Sub-Module. We could have the modules containing modules systems just like we did with Tier-3 and Tier-4, which would remove the need for Sub-Modules but I wanted Sub-Modules for ease of understanding and extensibility with customization possibilities. I also did not want Modules having modules as I wanted to have a fixed number of roots which are easily identifiable and accessible.
The Module will look like this,
Here I have transferred all the known submodules from a list to a Dictionary to decrease the search time during Switching a submodule from O(n) to O(1). This will not be noticed for small Modules having just a couple of Sub-Modules but in those where you have a few dozen Sub-Modules and also when the Sub-Modules activation is nested in a few tiers.
Now let’s move onto SubModules,
As I have said the Sub-Module is pretty much like the Module except that it does not have a Module type with it.
I would also like to highlight “Invoke ( “Deactivate”, 0.xxf );” from ModuleManager and SubModuleManager’s “Awake” magic function. This has been used so that all Sub-Modules inside the hierarchy can get a chance to complete their initialization. There are a few drawbacks of this system which we will be discussing right before we complete this part.
Now that you have been shown the Invoke Deactivate Hack let’s see one more hack we have used but in UIManager, “Invoke ( “ActivateDefault”, 0.2f );”, this has been done for similar reasons we added the Invoke Deactivate. The difference is only that we want to wait for all the Modules to complete Deactivation and then Activate the Module.
Now let’s see the implementation of this in Unity.
As you can see the system we made is working as expected. And you can also extend the Modules by adding new Panels in the ModuleType enum. You can add as many Sub-Modules to a given Module or Sub-Module without even touching a script. All you have to do is keep track of what you add, where you add it and create a transition for it. You will also need to make sure you change OnClick values for changing Sub-Modules if you add any new Sub-Module in between an already mapped Sub-Module.
Now lastly let us talk about the drawbacks of the current implementation of this approach,
Firstly, if you have many Modules and a deep hierarchy with Layout Helpers then there will be a significant burden during scene loading. To prevent this, we can spawn modules when we need them. I have been using a Scriptable Objects for spawning and tracking these spawned Modules. This will be Part-2 of this Modular UI Series.
Secondly, we have too set the Modules and Sub-Modules we need as active in the Scene for them to work correctly. And that is also because we have used Awake for initialization. Since Unity 5.x Awake functions are only called on MonoBehaviours whose GameObject is active in hierarchy. Refer Unity Documentation here. This will also be solved when creating the lazy loading system for Modules.
Thirdly, we have to manually assign Sub-Module references to Modules and Sub-Modules. We can create registration functions for Modules and Sub-Modules like we did for the UIManager. I will be adding this in Part-3. It is and interesting and fun thing to implement.
Now that we have decided what we will be doing for the next few weeks, I have a few suggestions to ask from you.
Would you like to see me extend this Modular UI with Animations, or other things you want to see?
Or would you like me to create a different system? XR, Vuforia, Shader graphs, Scriptable Objects, Particles, VFX Graphs, Animations, Physics, Behavior Trees and Navigation for Game AIs or any other game system?
Do comment on what you think about this tutorial, we are here to listen and all suggestions are welcome.