A UI System Architecture and Workflow for Unity

Jan. 8, 2018
protect

(This article was originally posted on my personal blog. )

[2019 update] Source code released!

I've released a homebrew version of this architecture, which you can get at my github page. There's also an examples repository, and a live demo. It's pretty much the same, except that in there I refer to the "UI Manager" as an "UI Frame", and the "Dialogs" are now called "Windows". Other than that, the original post below should still have helpful information.

FOREWORD

In mobile games, especially F2P, there’s no escape from developing a metagame, many times more technically complex than the core loop itself. This means one thing: UI work. Lots of it.

A lot of developers frown on the idea of doing UI development. More often than not, admittedly, there’s a lot of repetitive, less interesting things to be done – and also way more often than not, there isn’t an understanding of the nuances required for proper UX, which frustrates a lot of programmers when having to iterate a lot on seemingly minor things. Add to that the countless possible architectures that are proven, loved and hated, which usually spawn religious arguments upon using MVC, MVVM, XGH… You probably know the drill.

Now is the point where an article would usually pull a XKCD and say “fear not, for the ultimate answer is here!”, but not only I don’t believe in silver bullets, this also isn’t my first rodeo in the regard of proving myself wrong. That’s why I’m not talking about UI code architecture holistically. My focus will be disclosing one specific way to do things, which is good enough for medium-to-high complexity UI and was battle tested in released mobile games. That said, it’s most likely too complicated if you simply want to display a few UI elements here and there, and there isn’t complex navigation etc. The main idea is encapsulating complexity within the “core” so the end-code is highly agnostic and simple to implement.

In a nutshell, this architecture is simply a “window manager” of sorts, with history and flow control and an accompanying workflow and general guidelines on how to organize things. This means it’s easy enough to adapt it to any approach. If you want to go for single compound “view-controller” classes or go full StrangeIOC (although why would you ever), all or most of these ideas should work. So read on and pick yer poison.

 

(Seriously, why would you)

(maybe you’re into steampunk and have a strange attraction for boiler plates)

(not judging tho)

 

 

(don’t use Strange)

 

ACKNOWLEDGEMENTS

I built this system for Blood Runs Cold. On the code-side, I’ve simplified some things and added some improvements, but a lot of the structure is inspired by a design used by Renan Rennó when we worked together in Legends of Honor. One trick regarding how to make setting up transition animations artist-friendly is derived from my time in Aquiris working on Ballistic. The trigger for writing this post was a short chat on twitter with Nikos Patsiouras and Steve Streeting, so if you find this useful, thank them as well!

 

A BIT ABOUT UNITY UI

Unity’s UI is pretty awesome, but it can also be pretty shitty: it comes with a bunch of stuff out of the box and layouting is super powerful and easy once you get a grip on how it works. It can, however, drive you nuts with all the little hidden details and a few questionable design decisions. You should watch this Unite 2017 talk (from 23:51min on) for some nitty gritty details.

You’ll sometimes really want to write your own UI components, and you sometimes should. Nordeus for example wrote an entire custom UI hierarchy system to optimize reparenting in the hierarchy – which in the  near future possibly won’t be that much of an issueanymore. But remember it’s all about cost and benefit, and that if you’re frustrated about how something works (or why something is NOT working), you might jump to conclusions too soon and implement something that may already be there, out of the box.

If you’re making your own code, compose, don’t inherit. It’s pretty easy to extend the base UI classes and add extra functionalities piggy-backing on all the existing default UI components, so a lot of people tend to first think about inheritance. However, there’s a great chance that you’ll end up having to extend more than one class later simply because you’re not using a native base component. An example I’ve been through in a project was a component for localized text someone had made. It was pretty neat, and had a nice editor, but instead of being a separate component, it extended UnityEngine.UI.Text. Further down the road, we ended up with 2 or 3 more classes that also had to have a “localized” version. Had it been a separate component, we could probably simply slap it in anything without having to worry about how other parts worked.

Canvases define what parts of your UI needs to be rebuilt: if you change something on the hierarchy, even position, the whole mesh up to the bottom-most parent Canvas is rebuilt. This means if you have a single canvas for your whole UI, if some small textfield with a number is being rewritten every frame on some Update loop, your WHOLE UI will be rebuilt because of that. Also, every frame.

That said, you probably don’t want to update things every frame. Rebuilding the UI is pretty expensive and generates a quite a bit of garbage. Try to update things via events only when things change, or try to have things that need to update every frame inside their own canvas.

Ideally, you would even have all the static elements in one canvas, and all the dynamic ones in another one, but that’s usually not possible: it’s again about weighting the sweet spot between optimization and an easy workflow.

Use anchoring instead of absolute positioning. Having things responsive will make it easier for you to support multiple aspect ratios and resolutions. That said, make sure you also set up your reference resolution on your CanvasScaler from day 0. Your whole layout will be based on it, and changing it after things are done will most likely end in you(r UI artist) having to rework every single UI in the game. I usually go for 1920×1080.

Last but not least, use transform.SetParent(parent, false) instead of assigning to transform.parent: this is a very common mistake and the symptom is your UI elements looking fine if you drag and drop them in the Editor, but getting all screwed up when you instance them in real time. I can’t tell you how many times I forgot about it in the early days, and how many times I’ve seen code resetting position and scale and whatnot to make sure things don’t get all weird.

With this out of the way, let’s get down to business.

 

GLOSSARY

To better understand the rationale behind this system, some terminology needs to be defined. Remember these names are completely arbitrary, they are just names that were deemed “good enough” to represent something – the imporatnt thing is understanding the concept behind them.

We’ll use this wonderful mockup I made on Google Slides as an example:

Screen is any self-contained part of a given UI. Screens can be of 2 types:

  • Panels: a given chunk of UI that can coexist at the same time as other pieces of UI. Eg: status bars, elements on your HUD.

  • Dialogs: a Screen which is the main point of interest at a given time, usually using all or most of the display. Eg: popups, modals.

You can have multiple Panels open, even when a Dialog is open. You can have more than one Dialog visible at the same time, but only one of them is interactable (eg: you can have a Dialog open, but darkened out and blocked by a popup that is interactable on top of it). Panels either are open or closed at any point, but Dialogs have a history.

Widget is a reusable part of a ScreenIt can be displayed visually in several ways, but you most likely will have a single component that drives it.

Layer is responsible for containing and controlling a specific type of Screen.

Using our example again, here’s a possible way of dividing it with those concepts:

(1) is the Level Select Dialog, which is the main interaction element for the user at this point. However, you can still see (2), which is a Panel to display how much currency the user has, and even interact with (3), which is a navigation Panel. If you clicked one of the navigation buttons, (1) would be replaced with the target Dialog, eg the Shop or Options screens, but all the rest would remain the same. Last but not least, (4) would be a Widget: it’s a part that you’ll most likely instantiate dynamically several times, and could even be used in other parts (on an Achievements Dialog for example).

 

How to define what should be what and how granular should the division be? Well, common sense – or as we say in Portuguese, “good sense”. Remember the code overhead between doing a Screen (which needs to plug into the system, be registered etc) and a Widget (which is simply a component). If a big chunk of your display has contextualized information that should disappear as you navigate away, that’s most likely a Dialog; if it’s always there and cares little for navigation states, it’s a Panel. If it’s a part of something bigger, it’s probably a Widget. Try looking at some other games and looking out for these behaviours to get a better grip on the concept.

 

The UI Manager is the central control spot for everything. If you’re on the school of thought that “omg managers are monolithic super classes and you should avoid them like the plague!” just… use whatever name makes you feel better. This should actually be a very simple façade and the real meat is on the Layer code. You can even split this into several Commands, or however you like to do things – just make sure the code is localized in a way so the rest of your game can easily communicate with a single spot on the UI system. Never ever access Screens or Layers directly.

JikGuard.com, a high-tech security service provider focusing on game protection and anti-cheat, is committed to helping game companies solve the problem of cheats and hacks, and providing deeply integrated encryption protection solutions for games.

Read More>>