What and Why?
I return with a new engine feature, basic user interface! User Interface programming and design has been a field that yours truly has had an interest for quite some time. Seeing that we developed very basic on screen sprites, I figured why not try my hand at creating some basic buttons?
My biggest experience with game interface programming has been done via Unity, therefore, many of my design decisions and criticisms of my implementation may compare and contrast directly with Unity's Canvas and UI system. One that I have gotten to know pretty well over the years. Before attacking this, I found it useful to map out the exact capabilities that we'd have to write:
The Buttons are essentially a different type of Sprite but with the needed extra functionality to process click events. The position of the mouse click is passed into ProcessClick(). Which is polled each update, the reasoning for such will be explained later.
But, the mouse?!
I mentioned before that the existing engine had no support for tracking the mouse whether it be it's state or direct position on screen. To get this done, we had to do some digging in the proper Windows API. This required some brushing up on WindowHandles, and putting these functionalities in an appropriate place. We have an existing UserInput system that until recently, was only detecting simple key presses. There, we can no poll if user has the mouse button clicked or not.
Okay, but what about detecting clicks?
The real meat of the challenge was detecting whether or not the user clicked on a given button, in a sensible way. Currently, our process for listening for user input has been to check every frame for user input. Our engine was not event driven. Meaning, the engine wasn't listening for an input event to fire off. For moving our meshes and sprites in prior assignments, we just checked the state of the keyboard every frame. This isn't a bad way, but it is a way to do it, in fact Unity I believe does this. You'd do the following in your Update
Finally, how about image bounds?
After we know how and where buttons clicked are performed, how do we figure out if they've been clicked on our button?! I mentioned above that I made note that we declare the position/size of our on screen sprites via bounds of [-1,1] on screen. This is similar to how Unity's canvas system allows the developer to position elements based on variable screen size. (Unity also allows you to specify the direct pixel offset as well).
Because Windows's calls that give us the mouse position give it to us in absolute pixels, we must convert our screen space bounds of [-1,1] to actual pixels within the window.
Above is the lines where we can compute exactly where is the center pivot of the image in relation to the actual size of the window. The window size is passed in at button creation for this to happen. We do the same to determine the left, right, top, and bottom bounds of the image.
These are then used in a ProcessClick() that will fire off the saved callback specified to fire off when a click is detected.
How did this all fit?
With the lack of proper mouse support. a true UI system, to get this system working most of it is shoehorned into graphics. Which, while it does work, I really don't like it. Because this is part of a new Interface system. I would have made a proper Interface system, just like Graphics, UserInput, etc. But with direct interface with Graphics. Since of course, they'll need to talk to one another.
This challenge was more of a personal exercise on understanding what needs to go into creating a sensible Interface system. The biggest hurdle was working with a code base that had no regard for the matter, and trying to fit my logic into sensible places that fought with my personal opinions on where things should go while working within a specific time frame.
Try it out!
Below you can find executables that allow you to mouse around and test out the menu for yourself. Unlike prior downloads of the game, movement of the camera, a mesh, transparency, all of that has been taken out. When you click a button, they are specified to log the event to the log file, which you can find in the executable's main directory.
Thanks to those at Hathos Interactive for allowing me to use some assets from their current project for this example!
NOTE: While the site is quite bare, the team will be releasing an update to their current project sometime this winter / early next year.