Coding UI for Castle on the Coast

⚠️ Warning! this article is not a tutorial! If you copy my code, it will not magically work! Not all functions or problems are shown for the sake of brevity. ⚠️

The Problem

A goal I had with Castle on the Coast was professional polish in all things, including the UI. That means:

  • Support for mouse navigation (easy)
  • Support for directional navigation (not supported by Unreal 4 very well)
  • No strange situations where menus get stuck on screen, both take input, stay active during gameplay, etc..
  • I’m not concerned about talking about the HUD, as it is comparatively simple.

My typical solution for sweeping issues in a game is to code a system. I coded a set of base widgets to drive my game’s UI code: Menu Buttons and Menus, with a singleton coordinating all the menus in the game.

Button Base Widget

Menus are made of buttons, checkboxes, sliders, and other things the player can mess with. A base class was made to represent these objects called “ButtonBase_WIDG”.

Overview of Contents: these are the functions and variables that drive it:

  • “control_” indicates a function for other blueprints to call.
  • “handle_” indicates a function bound to an event.
  • “override_” indicates a function meant to be overridden by child classes for unique behavior.
  • “nav_” variables are basically references to surrounding buttons to help with directional navigation.
  • The Event Dispatchers are designed to communicate to the menu holding the button.

The function naming is quite important as Blueprints aren’t great with access modifiers (private / public / protected).

States: When navigating a menu, players make buttons change states, so this must be represented in code. Buttons can have 3 different states: normal, hover, and pressed. This is represented by the enumerated type variable “state”.

State Machine: The button contains a state machine using this variable, which triggers many override functions to create behavior. For instance, it will trigger “override_onSelected”.

Child widgets take advantage of this state setting to play animations, for example “BasicButton_WIDG”‘s “override_onStateChange” function:

Since UE4 parent widget classes cannot hand off their components to child classes (anything visual you make in the UMG editor) I had to hook up mouse events manually per child class.

In future UI systems I would like to make animation and mouse event setup more automatic in child classes.

Locking: Menu buttons need to be able to lock and refuse input should their menu become disabled.

Events bound to input are bound/unbound as needed to not only prevent unnecessary input, but to optimize it.

By the way, yes, my input is handled through events dispatched by a singleton.

Menu Base Widget

Menus are essentially a group of menu items. The menu base class: “menuBase_WIDG” can turn these objects on and off, handles navigation between menu items, and listens for input from menu items.

Overview of Contents: these are the functions and variables that drive it:

  • we’ve already covered what override, control, and handle mean.
  • The most important variable is “buttons”, the components of the menu.
  • “button_firstToHighlight” is the button the menu starts on
  • “button_lastHighlightedButton” is important for mouse navigation. If player directs mouse away from all buttons, then uses a controller, the game must know where to start in terms of button navigation. The last highlighted button is the best place.

Locking: Just like the buttons, the menu can lock up as well. In fact, the menu is in charge of locking up all of its buttons in sync with itself.

Directional Input: buttons understand when they are selected with the mouse, but they lack a big enough of a picture to understand directional navigation.

The menu handles that by listening to player input, looking at the neighbors of a button, and highlighting the neighbor.

If your wondering, the function “Refresh Button States” basically loops through buttons setting them back to normal.

Making sure only one button is highlighted: When coding menus its easy for multiple buttons to end up being selected at once.

When “handle_buttonStateChange” triggers and detects a highlighted button, it sets all but the highlighted button to a normal state, preventing such an occurrence.

Game Manager Things

The Game Manager is the big bad singleton of Castle on the Coast. It handles systematic things like player input, menus, saving, etc.. It also handles some menu logistics:

Detecting Usage of the Mouse: basically I track the mouse’s position. If it moves too much, we know mouse is being used. For optimization this test is not running during normal gameplay.

Handling Multiple Menus: If I were smart at the time of coding this game, I would have setup an array of menu references representing the menus vying to be active at any given time.

  • Only the most recent menu in the array would be active. Think about it, most menus that pop up over another should be the ones that are active.
  • If no menus remain in the array, gameplay would resume.

But I wasn’t.

Instead my menus simply take control when they want and let the game manager know “hey there is a menu active right now” or “hey no menus are active right now.”

My game is simple enough it didn’t matter, but I always recommend the smarter approach for simpler, fool-proof code.

Cursor Widget

Unreal 4 handles the mouse cursor just fine, so this widget is really here to spice up the cursor’s representation in-game.

Sparkles are a common thing in the world of Castle on the Coast, so I wanted the cursor to sparkle!

UI Particles are not supported by default in Unreal, so I coded a pool of small widgets to represent particles.

Things to Improve

The UI works great. Players have had no issue with it, but my system had a few dev-side pain points I would like to address in the future.

Each Menu Has a Lot of Custom Logic

Every menu had to define the behavior of each button selection. I couldn’t simply tell a button “go to settings” or “load level” without diving into the menu’s widget blueprint. It’s not terrible, but its not FAST.

Veil: Alter Unknown solves this with its modular level logic system.

Directional Navigation is a Pain to Setup

The biggest weakness with my system is I had to define directional navigation manually. Each button had a reference to its neighbors, which was painstaking to handle.

Veil: Alter Unknown solves this by using the position of menu items as the default for directional navigation.

Animation isn’t so Modular

Each button has the same bouncy animations, but are really copies from when I duplicated widgets. If a game director asked me to change the bouncy style of animation for all my different kinds of buttons, it would not be a simple matter.

Editing Text is a Pain

To make localization easier, the game pulls its text from a table in Unreal, imported from a Google sheet. This makes things complicated to edit text…

It meant to fix a typo, I had to edit the Google sheet, export a CSV, make sure it was named right, and drag it into Unreal.

Veil: Alter Unknown fixes this beautifully by making the game engine the source of truth for text.

Each line is a separate data asset containing all translations, voice line references, and the speaking character. A set of tools were created to edit these data assets with little trouble, and the ability to export/import CSV files when working with translators

Website Powered by WordPress.com.