Implementing a "proper" GUI in Spellbound


A screenshot of the spellcrafting menu.

Compared to old projects of mine, I really want to focus on making Spellbound technically sound. There's a saying in software development that you should always write code under the assumption that the next person to read your code is a homicidal maniac who knows your exact home address, and in game development this is no different. Even though I suspect I will be the only one who ever touches this code base, it's good advice to follow, if not a little extreme in it's presentation.

It is of course a work in progress, but for everything I've done so far, I've tried to ensure that features are both modular in design and have minimal side effects. The brief phase of my life where I studied functional programming introduced this perspective to me.

Anyhow, one of the areas I tried to streamline things is in the game's user interface system, since that's often one of the most daunting parts of building a game (that and collision). Some people find building GUIs extremely boring and tedious, however I am not one of those people. I set about to build a system in which I can:

  1. Add elements to a GUI container
  2. Anchor these elements relative to nine positions around the border
  3. Nest GUI elements within elements and position them relative to their parent(s)
  4. Scale it easily based on the size of the window/screen

It was fairly straightforward, but I'll highlight the details of the operation.

Anchoring

Without getting too crazy with the alignment combinations, I figured there were 9 basic anchor possibilities, assuming that it is only the position of the element that is being anchored, and not the dimensions:

TOP_LEFT
TOP_MIDDLE
TOP_RIGHT
LEFT_MIDDLE
CENTER
RIGHT_MIDDLE
BOTTOM_LEFT
BOTTOM_MIDDLE
BOTTOM_RIGHT

If I wanted to, I could have added Rule of Thirds anchor points, and also allowed two unique edges between two elements to be anchored, for example Button 1's right edge is positioned relative to Button 2's left edge, with a margin of 4px. But for simplicity's sake, I opted to abstract it a bit and assume for example that nested elements would want to stay within the bounds of the parent.

A GUIElement's position is determined by the anchor point; the relative point on the element will anchor itself to the same point on the parent. For example, the top left will anchor the element's top left point to the parent's. And the bottom right anchor will attach the element's bottom right point to the parent's bottom right point.

Then the offset is applied on both axes, in "GUI coordinates". Or, "GUI Pixels". Or... "Gixels".

Scaling and Rendering

Spellbound is a pixel art game, which makes the scaling on higher resolution monitors very simple. Global scale is determined by the height of a chunk in pixels (again, these are the game's pixels assuming no zoom) divided by the window height, also in "gixels" (is that a good name? I can't really tell).

The elements are double buffered; drawn to images first and then those images are drawn in the appropriate positions at a scale of 1, and then the entire render is scaled up to fit the window size based on the aforementioned calculations. So nothing happens between pixels, which makes the animated movement of elements a bit choppy, but it's well worth it for the convenience factor of aligning offsets to a grid. It's something I might assess later when the game is more complete.

Nesting

The final step to all of this is to recursively draw and handle events for the elements in the GUI. Drawing elements must happen bottom-to-top, and event handling needs to propagate "downwards", from the end of the element list to the beginning.

But each element needs to also draw and handle events for it's children, since in my implementation, the GUI has no idea about any of the nested elements. And they need to do those two things in the same order.

The GUIElements also implement a few "on" type methods, such as onMouseClick(ogx, ogy, button), where [ogx, ogy] represents the "gixel" (sorry) coordinates relative to the actual position of the element. So the GUI passes the event down to a handle method constant to each element, and the handle method triggers the overridden "on" method for the event in question.

Elements have the freedom to capture events that are outside their physical bounds, since the GUI does not discriminate which events are passed where. It only cares whether the element is currently "active", another toggle in GUIElement that I use to determine whether an element is drawn to the screen and also accepting input events.

Once an event is captured (events can also be acted on without being caught), the propagation stops.

One exception to the above are modals. The GUI keeps track of a stack of GUIElements that, when in the stack, override the drawing and propagating such that they get center stage in front of the player. Much like you'd expect a modal to behave.

Eventually I'll need to add another exception to keep track of the currently active keyboard focus. I will have to think about how that will play with the other elements.

It's Boring but it Works

This might not be the most interesting topic, but for some reason I really like writing about it. And I hope that other developers who enjoy focusing on the tiny-but-important details in their own projects will enjoy reading this post for what it is.

And, I promise to never write the word "gixels" ever again.

Get Spellbound

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.