DevLog #4: User Interface
So, about that delicious Lords of War Rendering Cake... User Interface is a cherry on top of it. Because it renders on top of everything else, of course. In the previous DevLog, I mentioned that moving units is the next step, but to move something, there first needs to be a way of clicking on that thing. And to be able to click on things, there needs to be a way to interact with the player. Interface with them, so to speak. Like in... User Interface.
So let's get started...
...
This doesn't work as well in written form, does it?
Oh well. User interface, then! UI is a very important and relatively overlooked part of game development. Mostly because good UI just get out of the way as if it was never there. On the other hand, a bad user interface can be a complete dealbreaker, so it’s worth to put some thought into it. User Interface design is not today's topic. There are a lot of fundaments to build before actual UI design could be considered.
In the most basic of definitions, User Interface is a layer on top of the game that should be performing two roles: first, it’s a way of communicating to the player the current state of the game; and second, it’s a place where players can change that state. I think that the first role – displaying information to the player – is quite obvious to everyone. First thing people think about when they hear UI is textboxes and tables with stats. In more action-oriented games, mentions of UI are replaced with HUD (Heads-Up Display), but it’s just a different word to describe the same thing from the mechanical point of view.
What’s not so obvious is that second role – way for the player to change the game’s state. This is usually delegated to some sort of input tools. First thing that comes to mind is handling of keyboard or gamepads, so that’s input. But for strategy or RPGs, that’s not necessarily the case. For example, let’s say the selected unit has a movement planned for this turn. You can click the button on the UI to order it to execute that move. But you can also click ‘M’ key on the keyboard. So, should "continue movement" function be part of User Interface, or part of input manager? Implementing it in both would be silly, so it would be good to pick one... What would make more sense? Adding drawing and interacting buttons to input manager, or adding listening to keyboard events to the User Interface layer?
I think that for Lords of War expanding User Interface to also support keyboard interaction makes a lot of sense. I would even go as far as to point out that selecting keys for particular functions is as essential part of User Experience, as placing buttons in the correct places on the screen. What if your action game would use TFGH keys for controlling the player character? Would that be an input issue, or user interface issue? Of course, you could say it’s not an issue at all... But this is my DevLog ;p
With all that philosophy out of the way, it’s high time to start building that foundations I motioned. While UI in Lords of War will be complex (have you seen how many buttons Warlords 3 UI have?!), from a mechanical standpoint UI system won’t actually have that many functions. It will have lots and lots of buttons, but there won’t be much more beside buttons. Because of this, it won’t require any fancy drawing procedures. Actually, all it does is to plug into the drawing part of ViewportRenderer
.
Scene.Current.UI.Draw()
Look deceptively simply, doesn’t it?
Scene.Current
is just currently opened scene, but what’s that UI
property? I figured that in Lords of War it would make sense to make User Interface a feature of a scene. Each scene will have a dedicated UI, that will have exactly the features that it needs to have. This UIDesign is an object that holds UI logic and is owned by a particular scene. Whenever a scene is drawn, its UIDesign is drawn, too.
Now, I wouldn’t be myself if I didn’t create a bunch of classes to go along with this idea. UIDesignBase
is a base class for UI designs. That’s where all the functionality is implemented. Other classes that extend UIDesignBase
mostly just set their own unique parameters. They’ll likely add some unique features, too, later on, but that remains to be seen. Since UIDesign is separate objects, I’ll just have to implement a particular design once and then attach it to many scenes. Maybe scenes could even share instances of UIDesign? That could come in handy...
... Or maybe it won’t. Frankly, I’m not sure if I ever need such flexibility in Lords of War. But the potential is interesting.
But what UIDesign actually can do? Most importantly, it implements the feature of clicking on things! It’s UIDesign’s job to take note of mouse behavior and then try and figure out what spot was clicked on and what does that mean for the game’s state. All of this is done with just one function - HitTrace(mouse.x, mouse.y)
. It’s actually quite simple, but it leans very heavily on all the overcomplicated structures I build up to this point. It quickly checks everything – from (yet) non-existent UI, through all entities in the world down to actual map files, jumping between different coordinate spaces, down the rendering cake... To figure out what is under the mouse cursor and get all required info about it. It makes use of every single concept I talked about up until now.
The concept of Hit Trace is crucial to many mechanics in game development. In Lords of War it’s used to detect what’s under the mouse cursor, but games can use it for all sort of things – collision detection, aiming from first-person perspective, dealing damage, interacting with buttons... It’s very versatile concept and different engine often extend it to different areas. It doesn’t even have to be a point! It could be a line, a cone or a sphere. It could be used anywhere where there’s some sort of interaction between two things. And games do have quite a lot of interacting elements, don’t they?
And if you’re wondering what all this has to do with moving units... You need to select a unit you want to move first.
// Unit
if HitInfoType.Unit == hitInfo.Type then
Debug.Log("Clicked! Type: " + hitInfo.Type + "; Entity ID: " + hitInfo.Object + "; Hit world pos: " + hitInfo.HitWorldPosition.ToString())
// select unit
this.SelectUnit(hitInfo.Object)
end
There sure was a lot more stuff involved than I expected, when I said that next step will be making units move. We’ll just half-way there... But finding a path to the goal can’t be that difficult...
... Right?