How to create Modular and Scalable UI systems in Unreal Engine

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
tell me how many times have you tried developing a UI system only to find it getting messy over time maybe you end up with a monolithic Master widget that touches every bar to the code base and changing one thing breaks many or if you wanted let's say to change the font for all your text and your widgets you'd have to manually find every text Block in a very tedious and erir prone process or you don't know what's better for optimization creating and removing Widgets or hiding and showing them and you are wondering what are some of the best practices I should apply to develop a modular scalable and optimized UI system hi I am Umber and for a long while I struggled with that as well so for the B 3 to 4 weeks I did some research about the best UI practices from multiple resources which I'll be linking in the description to demonstrate the importance of these practices we're going to take a look at this simple UI system which is written in two ways one that doesn't follow these practices and the other does with each one we will be showing why the bad practice fails and how the bitter practice can be a bitter solution this video aims to let you know that these practices exist and when and why you should apply them not a step-by-step tutorial showing how they are implemented which we'll be doing in another time so without further Ado let's get started tip number one create base reusable widgets now let's say our U designer wanted to change the text color in the whole project from white to Yellow to do that usually you would have to find every text Block in every widget which can be frustrating wastes time and we will have lots of Misses what we can do instead is create base widgets for our most common widgets for example buttons text blocks and borders in this example I have created this button and gave it some parameterization Properties by marking them as instance editable and hooking them in pre-construct we can simply change the color now with a single click if for some reason some widgets don't update which is a nonn bug all we have to do is make sure we save everything filter our content by widget select everything and reload asset tip number two use named slots for templating at sometimes we have widgets that share similar Parts but also have some changing parts for example if we look at these panels they all share the same black border and header text but they have different content to do that normally you would have to duplicate the border and text Block in all of these bannel what we can do better is use name slots which are widgets that expose a single child in any of their instances here we have a base panel with a black border and a text block and a body name slot in this inventure screen we can override this body slot with whatever we want and we can do the same here for this setting screen tip number three use different layers to manage screens and pels now this question tricked me for a while how can I manage different screens without having a messy system that is tightly coupled for example we want to show this inventor screen when I press the I key and when I press the q key my quest screen should come up and the inventor screen should hide then if I overlapped with this this dialog box a dialogue model should hide everything and show up and if I Bose the game now my boss menu should show on top one solution to achieve that which I used to do is to have one widget containing all these panels when any panel needs to show up it needs to reference every other panel and control its visibility ending with very cobbled and hard to maintain or scale solution so what can we do better as I came across this y Tech blog and found it similar to the UI manager in the lar project we can use different layers to manage different types of widgets let me explain a layer is a stack of widgets it allows us to pop and push widgets to it in our example we will have four layers A game layer for our HUD or widgets that are always visible a game menu layer for gameplay menus like our inventory and Quest screens then a menu layer which we'll be pushing menus that are not part of the game playay for example our POS and settings menus then a final model layer for popups and dialog screens and then the system has a little UI manager which encapsulates these layers and exposes their functions to allow the creation or removal of these widgets this can be a subsystem a blueprint function library or something else this way we can create or remove widgets without them knowing anything about each other or about their layers if you wanted to add a new layer nothing will be affected and we no longer have a monolithic Master widget that touches every part of the code base tip number four put data into separate objects in this example we have a weapon slot that needs to know the icon of the currently equipped weapon an easy solution would be to use a function binding but that has two problems the first one is that it turns every frame which we don't need and the second one is that it keeps us cobbled to our weapon component as we cust it a little Improvement would be to Cache the weapon component and use property binding but still we have the same two problems what we can do better is to use events and only update the icon when a new weapon is equipped this solves our optimization problem but if you look at the widgets reference viewer the coupling problem to the weapon component still exists how can we communicate data between gameplay code and UI without coupling them together and without having a monolithic game instance that knows everything about the system this is important for many reasons what if later on we decided to remove the weapon component completely or get our weapon icons from a database why should our UI be affected by that so what can we do better in this Unity talk Ryan hipple recommends creating separate objects to hold the data that need to be communicated fortunately for us a similar system to that can be achieved in a real engine by using the view models plugin after enabling the plugin we can create a view model child class and add the data we need then our game code updates the data when something changes and our UI in the other side listens to the changes in the view model to update its widgets now our reference viewer is Happy the two problems are solved and we can change our code or UI without breaking anything now let's focus on optimization but before we get into it let's name a few things that can cause poor performance for UI as explained in this great talk about UI optimization for example frequent call to the create widget function widget takes how many widgets are visible in a frame how many of those are testable how nested our widget hierarchy is and what is the size of the textures used for UI and many other things and let's make it clear that everything depends on your context so make sure to profile first tip number five use UI in validation now we already mentioned the first tip about optimization when we talked about using events instead of function or properity bindings the second tip allows us to cach the widgets that don't change every frame and to use their cash layout instead of recalculating it every frame for example this health bar doesn't need to change or be reprinted every frame it does only update when the player takes damage so to cach it what we can do is trap it with an invalidation box which Cates its layout and updates it only when the widget changes any of its properties tip number six animate using materials now let's take a look at this example here we have a button that needs to animate when covered to do that normally would animate it through sequencer but that can be very limited and it runs on the CPU what we can do better is use material instances these have a couple of advantages the first one is that they run on the GPU which can handle more complex calculations the second is that they allow us to make more advanced animations without having to add more widgets or textures for example what if we have some looping animations or particle effects or glowing effects I don't believe we can achieve that easily with sequencer for this example I've B this spoton material from the UI material lab project that I recommend you take a look at it the third Advantage is that as we mentioned in validation boxes using materials and animating their parameters through sequencer is an exception that doesn't invalidate our widgets layout if we debug our invalidation and take a look at this widget which is animated with no materials we can see it invalidating while this one which is animated using materials doesn't invalidate tip number seven break up your widgets into categories for construction now back to the question of should I create and remove widgets or should I hide and show them the simple answer to that is you should be using both let me read this part about UI optimization from unreal Ducks any widgets that require Fast Response times should be loaded in the background even when not displayed for example an inventor screen is used very frequently and should be highly responsive so keeping it loaded but not visible is good practice for widgets that that are not present for long periods of time and do not require Fast Response they should be loaded synchronously at runtime and destroyed when dismissed that's why when we discussed layer our game menu layer had only one widget that contains the inventory screen the quest screen and the ability screens grabbed by a widget switcher tip number eight use collaps instead of headden now when we hide widgets like this armor bar for example their layout is still calculated even if they are not painted making them collapse is the only option that totally ignores them tip number nine use n testable instead of visible for some widgets we don't need to interact with them for example the health bar and the equipped weapon slots keeping these visible calculates their head test grid which allows us to interact and click on them but we don't need that so making them not testable we avoid some unnecessary calculations tip number 10 use canvas panels and overlays sparingly now I used to use canvas panels everywhere they are easy to use and they allow us to position widgets in any way we want but this was Shing for me as it turns out that for canvas panels to allow us to draw widgets on top of each other it uses more draw CS of course using them occasionally is not a big issue but when possible try replacing them with grid panels instead by using the nudge property we can still position our widgets where we want without having those extra draw calls tip number 11 use spacers instead of size boxes when possible I usually used size boxes to make some padding or spaces between my widgets but it turns out that size boxes use multiple bosses to calculate their size and render themselves a better solution that we can do if we need content to take up aain size in width and height is to use spacers which are significantly cheaper tip number 12 use soft references in this example we have a character selection screen that stores an array of characters and just by having this array all of its elements with everything that they are referencing like meshes textures materials and maybe other classes are loaded into memory even if we are just choosing one character we are still loading all of them a better solution to avoid that is to use soft references instead which breaks that reference chain and only load the class that we need when we need to tip number 13 consider custom icon fonts now let's get into the last two extreme tips we have for this video one of the ways to optimize UI is to minimize our texture memory size in this GDC talk the developers of gar did something creative the single biggest way we saved memory on Ragnarok was by utilizing a custom icon font using applications like font Forge we loaded in black and white icons into a font sheet and then we were recalling them in game by using a Unicode value and this was a huge savings for us instead of having material and texture costs for over 200 icons we could instead leverage a single font that was baked into a 2048x 2448 texture That Was Then compressed further down I was very interested to test that so I opened up a website called fontello I uploaded a font that I already had which extracted the elements that were used in that font which are called glyphs then I merged some glyphs from the website downloaded the font and imported it inside unre now to use any of these icons instead of adding image all we need to do is to add a text block and write down the unic code value for that icon to dis remind the Unicode values I use the demo page that came with that that font but for some reason the unic code values weren't understood by blueprints so instead I copy pasted the GL itself in the text block and it worked this can be very beneficial in Saving texture memory and having less widgets for example being able to write this with a single text block tip number 14 consider s mesh widgets for lots of instances of the same widget in some games you would have multiple instances of the same widgets for example if we have enemy health bars or mini map icons or puis in the world what we usually do is use a widget component but it turns out that these widget components can be costly to render as for a widget to render this way it must be painted first then rendered on a texture Target instead there is an unpopular way to do that using a widget called s mesh widget which allows us to draw 2D mes with a single draw call think about it as foliage but for UI now this one wasn't well documented but thanks to this example by Nick Darnell I was able to implement that for these health bars to explain how it works briefly we need to B a static mesh to the mesh widget we do that through something called a slate Vector art data and inside our mes widget we use the draw mes function and the enable instancing function now how can we control the position and the parameters of each instance each instance is a Ford Vector consisting of four Flo variables the X and Y determine the screen position the Z determines the scale and the W determines something called a base address which I'm still figuring out these values are then used in a UI material that we created and assigned to our mesh to change how the instance will look and where it will be positioned like most of the topics discussed in this video we will be making different videos showing how to implement them in detail this video aimed to let you know that these ways of doing things exist not necessarily that they are the right way to do things there's no right or wrong when it comes to patterns and strategies so make sure you understand when and why you should use them I totally recommend taking a look at the resources which explain these topics into more detail by people more knowledgeable than myself thank you for making it to the end of this video this was Muhammad and I shall see you in the next [Music] one
Info
Channel: AmrMakesGames
Views: 1,509
Rating: undefined out of 5
Keywords: best practices, common ui, game dev, game development, game menu, game ui, gamedev, heads up display, indie game dev, indie game development, managing ui, modular, optimization, scalable, slate, ue5 best practices, ue5 tips, ui architecture, ui best practices, ui design, ui optimization, ui system, ui tips, umg, umg widget, unity, unreal best practices, unreal engine, unreal engine 5, unreal hud, unreal tips, unreal ui, unreal umg, unreal widget, viewmodel, widget, widgets
Id: v9k-J2GeEKI
Channel Id: undefined
Length: 19min 15sec (1155 seconds)
Published: Sun Dec 31 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.