Skip to main content

Designing a flexible radial menu control

It’s been a while since the last article I published. I was busy wrapping up a project I’m working on currently, which, as most game development projects do, turned out to require a little more attention than was planned. The project I was working on was my first porting gig. As can be expected, this exposed me to a bunch of problems I never considered before. One of the tasks I was assigned was writing up a radial menu control, and that’s what I would like to write about today.

Radial menu

At its core, a radial menu is nothing more than a UI control that consists of a circular items layout and an optional direction indicator. Seems simple enough, right? Well, as it often is with simple concepts, there is a ton of things that go into writing a radial control that is responsive, easy to use and flexible enough.

Initial guidelines

Let’s start by quickly going over some of the initial discussions I had with people who had done this sort of work before. It’s very important to go through this, because without this context some of the decisions I have decided to take can be difficult to understand. Our initial design consisted of 3 different radial menus, two of them being half- and one being a full-circle. The strategy that was suggested to me was quite simple. It assumed that:
  • The visual side of each of the radials would be a set of elements, visually identical to how the specific control should look like in production,
  • Each of the elements of the complete radial menu asset would be assigned in code to specific, hardcoded angle ranges,
  • An element of the radial would get highlighted when the angle value of the analog stick vector would fall into the elements angle span
  • In code, I would write a single ExecuteRadialAction function that would branch into multiple instructions, each of them hardcoded to specific angle ranges
As somebody accustomed to designers coming to me regularly with new ideas and feature/change requests, I had multiple problems with this rigid approach.

My way

First of all, I refused to hardcode any values in code. It was bound to start festering sooner or later. It also stood against the principle of designing lookless controls, which I wanted my radial menus to be. If you are not familiar with the concept, I encourage you to read this article. Here’s mine, somehow shorter take on this: A control’s setup should be flexible enough to allow designers to change the look of every single element that the control consists of. A Button control that can be found in WPF offers behaviors, not looks. It can look any way a designer wants it to look like and it will still retain its functionality.
So, I wanted my radial menu to offer behaviors without enforcing any looks. Radial menu elements could be placed anywhere and look whatever the artists wanted them to look. To achieve this, I used the game engine’s systems configuration functionality. There, the designers were able to specify both the angle at which each item would start, and how big the angle span of each item would be. This was reasonable enough, but turned out to be a bit too complicated to set up, so in the end I scaled it down a notch. Still, the idea behind it was solid and it would have allowed designers to set radial menus up however they wanted them to be.
Next up was the idea that each radial element should have an angle span assigned to it in code. While there was nothing particularly harmful in that (except for the hardcoded angle span values I already mentioned), I found it more manageable to move the angle spans up one level. Instead of the basic radial menu structure being “radial menu -> radial item”, I decided to introduce an intermediary in the form of a radial slot, resulting in “radial menu -> radial slot -> radial item”. This let me achieved two goals:
  • It was even more apparent that a radial item is both lookless and positionless,
  • Radial menu got its dependencies simplified a fair bit, since it didn’t even have to include a base radial item class anymore
With this done, it was already clear that I should be able to highlight individual radial items without any issues. Even better, I was able to implement a custom event handling for each one of them, because the game engine allowed me to specify a custom class for each element of the visual tree. Each of those custom control classes could trigger their own behavior and event callbacks. Thus, I was able to delegate all custom functionalities to each individual item if needed. This was important, because in some cases choosing some of those items would call widely different logic responses, communicating with different systems and dependencies. Being able to include those dependencies in each of those custom radial item classes greatly reduced the number of includes that would otherwise have to be brought into the radial menus.

The core responsibilities

To better understand the structure of the radial menu as I envisioned it, let’s break down the functionality of each part the control consisted of.

Radial menu

It’s the container in which children are arranged circularly around its center.
What it does What it doesn't do
Evokes events on its children (like press or highlighting) Process analog input (more on that down below)
Spreads its radial slot children equally in either a circular, or a custom fashion Initializes or builds its children visually
Gives access to currently highlighted element
Places an optional angle indicator into its socket in the center of the control and rotates it

Radial slot

It’s the container or a socket of the actual radial item control.
What it does What it doesn't do
Interfaces the angle span of an individual radial item
Acts like a socket into which an actual radial item is inserted
Locks to rotation of its radial item children on radial controls items distribution

Radial item

It’s the element that represents an actual piece of the pizza, if you will.
What it does What it doesn't do
Processes UI events Deals with its position and rotation
Processes any logical callbacks if necessary
Looks!

If you have not yet completely fallen asleep, you might have noticed I listed processing analog input as something the radial menu doesn’t do. In fact, none of the radial menu parts I listed seem to have access to this information. Well, this is where the last piece of the puzzle comes in.

Radial menu host

Since the radial menu itself is designed to be completely reusable regardless of the context, it has to have a parent element that feeds it the data it needs. Let’s call this element a “radial menu host”, or “host” for simplicity’s sake. The host has two main responsibilities:
  • Feeds radial menu the current angle of a stick vector,
  • Instantiates the radial items and feeds them to the radial menu along with their angle positions as defined by desingers in the hosts configuration
While the radial menu control is a sealed class and is therefore meant to not be derived from, you can have as many host implementations as you need.
In my case, I was able to contain all the logic connected to angle-feeding the radial menu its angle in the base radial menu host class. This has turned each specific implementation of the host class into a sort of a radial item factory, with custom behaviors if needed. The base host class contained an AnalogInputProcessor (AIP) object that would calculate the stick angle on every tick. The AIPs functionality was somehow configurable, because in some cases designers wanted the logical direction to ‘stick’ to last direction for some time after the stick was released. The configuration of this object also made it possible to specify whether the radial menu would be fed the left or the right analog stick angle vector. This turned the AIP class into a sort of a state machine, where input would either be processed as neutral, active or sticking to last angle. Thanks to this state machine, all the host class had to do was to tick the AIP and retrieve current angle from it, no conditions and no questions asked. It would then pass that angle directly to the radial menu control it hosted, again, without any modifications. I recommend you do the same for your radial menus.
The radial menu host contains all its controls (radial items and an optional angle indicator) as templates and instantates them on initialization. Then, those elements get passed to the radial menu. A radial item would get passed on to a slot, which would be then rotated to the correct position. An angle indicator would be placed in its socket in the center of the radial menu control. The host would also set the angle span that the radial menu would have available for its items distribution.

How it felt in use

I’m happy to report that this approach has paid off in production. It was possible to implement radial menu controls of various looks and feels, almost completely configurable without code support. One port that felt a bit off to some other developers was the radial slot. Apparently, its inclusion made the whole structure feel a bit bloated and it was easy to forget the slots purpose. I never had this problem, but then, I was the one to come up with the idea.
The other problem that popped up was that of animating each individual radial items position/rotation. I couldn’t quite figure a nice way of animating the position of the radial slot, which would in turn animate the position its radial item child. Since the position of the radial slot was simply enforced on it during radial menus item distribution, I wanted to avoid having to store it in the slot or otherwise modify. Instead, I opted for animating each radial item child directly in relation to their 0,0 position (the center anchor of the radial slot). This way I didn’t have to store neither the slots position nor rotation. This was not ideal the most ingenious of solutions and if I had to point to one thing I’d improve in the future, that would be it.

Comments

Popular posts from this blog

Float precision and Time in Unity

Float precision and Time in Unity Recently, I was tasked with addressing an interesting issue that occured in a Unity game. After the game run for about 2 days, it virtually stopped streaming in map assets during world locomotion. Assets were streamed in with a huge delay (up to x-teen seconds) and it simply wouldn’t do. After finding out what caused this strange bug, I thought it would make an interesting article to share with you. Both the problem, and the process of finding its origin were quite an interesting experience. Debugging Since the problem only occurred after at least 2 days of ‘soaking’, I knew time is going to play a role here. I started investigating the issue by looking at the custom streaming code used in the game. It consisted of a bunch of asset loading and unloading functions, called every tick. At the start of each tick, before calling the functions, the code would cache the current Time.realtimeSinceStartup, which is a timer managed by the Unity engine tha

Array property customization in Unreal Engine 4

With the drive towards as much data-driven gameplay as possible, there comes a need for easy to edit and nice to interact with data assets that can accommodate all that data. While Unreal Engine 4’s UI framework allows us to display a wide range of data structures, its default handling of nested properties can quickly result in deeply nested structures that need to be expanded in order to edit them. This can really hurt productivity in some scenarios. Fortunately, we have the ability to fully customize how our data is laid out. While there are nice tutorials all around the web that explain how to customize our custom classes and structs, I’ve not been able to find an article that would explain how one would go about customizing how collection types display their data. In this article I will describe how to customize the display of an array property in UE4. I will follow it with the one for customizing maps display in the near future. Defining the issue I intend to explain the pr

My CD Projekt RED story, part 3

My CD Projekt RED story, part 3 We are now 2 years into my CD Projekt RED adventure and so far, it’s been mostly smooth sailing. I got promoted to a QA Analyst position, an equivalent of a specialist position for developers. I was earning around 2600zł or $650 per month, which would sometimes go as high as 3500zł with additional crunch pay. At this point I felt fairly rewarded for my efforts and could even start planning my wedding. I also received the first bonus for my participation in the creation of both Witcher 3 expansions. This amounted to roughly 13.000zł or $4250, which was an amount of money I had a hard time wrapping my head around. I still lived in a single room in an apartment I shared with other people, but at this point it was mostly my own choice. I’ve had my own wedding on the horizon so I needed every coin I could save for the occasion. Getting out of QA It was during that time that I decided I want to transition away from doing QA work. I approached the audio te