Recently I undertook the task of setting up the Actus Dei Engine’s user interface system. After getting a second renderer up for the user interface I had to approach its architecture. I knew this process could be simple if I planned it out carefully. Despite that, I did find myself turned around a few times. The first step was obvious. Create a base class for the components that would make up the UI and a User Interface class to store them. I also gave the User Interface class the ability to pause the gameplay when accessing menus. Seeing as how each component would most likely only render one quad, it seemed wasteful to make an additional draw call for each one. I created the Overlay2D class to store a list of components. The overlay will then store all the component’s quads into a buffer on the overlay. If it does not require updating, we make it a static buffer. Now we have one draw call per visible overlay. Text is stored inside components but rendered separately using Sprite Batch and Sprite Font from the DirextXTK_UWP package. Considering that an overlay can represent a menu screen, a submenu, a heads up display or just a single log component, I made it so you could render more than one at a time. I also run update through the overlay’s ID lists which allows active components to update things like animations.
Rather than incorporate the controls of the user interface into the overlays, I chose to create a base class called UIController. Implementation requires the game side code to create derived classes of UIController containing the logic for controlling the overlays. Like overlay, this class also receives a list of component ids. Before the update is run, the user interface’s active controllers receive input. They can use this input to modify overlays, components and to toggle other controllers on and off. They also check input against its currently focused components and may respond based on the values they return. Some examples in our game would be a controller for the title screen and a controller for the pause menu and gameplay calls like a pause. An example of a component that returns a value to a controller, would be a button component that returns a stored value based on which input is given while it is selected. With this, the user should be able to make their overlays dynamic with Input and components.
The last major component to the system is a static class that listens for messages to the user interface from other systems. The user interface class creates a component for its debug log which can then be added to any overlays. The listening class can then receive strings from other classes to add to the user interface log. The listener class can also receive messages which the user interface will send to a designated controller to be interpreted. Now the user interface can react to messages from other systems. To implement the system on the gameplay side the user must write his controller classes and any new components they need. Then they will create an instance of the User Interface class, add overlays, controllers and components to it using its functions and pass it to the engine.
By Daniel Hayward