User Interface: Our Approach
Let's say that you start up a game. This game then greets you with a menu screen, and from this menu screen you can start the game. Once you start the game you can see your current health and stamina on the bottom left of your screen and there is a small but helpful minimap on the top right.
All of the things that I have described here are part of the Graphical User Interface (GUI). Having a GUI is critical for most games. Without it you won’t be able to get any visual helpers and reminders and definitely no minimap.
Because GUI is so important I have been working on creating a User Interface (UI) system that is easy to use and which also performs well.
What problems do I want to solve?
The main problem that I want to solve is having an easy to use interface. It might take a while before we have a UI editor, therefore the UI system needs to have an easy to use interface that does not require you to type many lines of code just to create a button.
The second problem that I want to solve is that the UI system needs to perform well. It is very easy to create a UI system, but it is very difficult to create a UI system with a simple interface that is also performant.
How do I solve these problems?
We created an easy to use interface based upon the UI interfaces of other engines. I made the UI system performant by not taking the naive approach, but instead use a technique commonly used by applications which are both described below.
The naive approach
The naive approach of rendering UI would be to re-render all UI elements to the screen every frame. This means that you need to redraw a lot of pixels every frame. Depending on how many UI elements you have, this could become very slow very quickly.
E.g., let’s say that you want to draw the inventory of the player, but the player has hundreds of different items that all have a unique sprite to identify what the item is. This means that every frame you would have to draw a sprite for each item that the player possesses.
Our approach however does not render the UI elements to the screen every frame. In fact, it never renders any of the UI elements to the screen. It renders the UI elements to a separate texture instead. This texture is then rendered to the screen every frame.
Since you do not have to clean the texture every frame like you have to do with the screen, you do not have to redraw already drawn UI elements. Instead you only have to update the segments of the UI texture where the UI elements have changed.
But how do we know that a UI element needs to be redrawn? Well, we simply keep a list of all invalidated UI elements, redraw those and then empty the list.
This is what happens to the UI elements that are invalidated.
- Clear the section of the render target that the UI element was located at.
- Redraw all of the UI elements that are overlapping with the UI element’s old bounds.
- Update the UI element’s bounds.
- Redraw all of the UI elements that are overlapping with the UI element’s new bounds, including the UI element.
And that is it.
But how do we make sure that the redrawn elements do not invalidate other elements? E.g., a button is placed on top of an image. This image is also has a piece of text on top of it. If the image was redrawn because of the button, the image would now be on top of the text. This would mean that the order of the UI elements is not correct anymore.
This problem however is easily solvable. All graphics Application Programming Interfaces (API’s) provide you with something called a scissor rectangle. This allows you to define an area of the texture that you want to render to. Everything outside of the scissor rectangle will be discarded. E.g., let's take the previous example again. In order to make sure that the text is not overwritten, we can define the scissor rectangle as the bounds of the button. This way everything outside of the button’s bounds is discarded and therefore not drawn.
On the left you can see what the UI normally looks like, and on the right you can see what areas of the UI texture is updated whenever a UI element is changed.
|What the UI normally looks like||All of the UI updates, a change in color means that it has been redrawn|