Contents |
The Bork3D Game Engine has the ability to load multiple user interface controls from a single configuration file. This allows you to decouple UI layout from your game code, so that you (or an artist) can iterate on the look and feel of your game without repeatedly recompiling it.
In a nutshell:
The first word in a UI element line is the class name. The engine declares four UI classes that can be used in UI files: RudeControl, RudeButtonControl, RudeButtonAnimControl, and RudeTextControl. You're free to define your own classes too (see below).
Each class has it's own declaration format.
Parameters:
Example:
RudeTextControl scoreText +1 right {310, 36} default outline none 0xFF666666 0xFF000000 0xFFFFFFFF 0xFFFFFFFF
To load a UI file, create a RudeControl and call the Load() method on it. Pass it the name of the file without the ".ui" extension. Loading a UI file into a RudeControl creates child RudeControl's for each control defined in the file. You can access these children with the GetChildControl() method. For example:
RBUITitle::RBUITitle()
{
...
// Load the "title.ui" UI file
m_ui.Load("title");
// Obtain references to the child controls
m_logo = m_ui.GetChildControl<RudeButtonControl>("logo");
m_rangeMedallion = m_ui.GetChildControl<RudeButtonControl>("rangeMedallion");
m_courseMedallion = m_ui.GetChildControl<RudeButtonControl>("courseMedallion");
m_courseSplash = m_ui.GetChildControl<RudeButtonControl>("courseSplash");
...
}
The usefulness of this system becomes most apparent when you conditionally load a different UI file depending upon the dimensions or orientation of the device you're working with. You may have a UI file for iPhone and another for iPad, one for portrait and one for landscape, etc. For example:
if(RUDE_IPAD)
m_ui.Load("title_ipad");
else
m_ui.Load("title_iphone");
The engine ships with the ability to load three types of controls from these configuration files, but the system is easily extensible so that you can give it the ability to load your own custom control types. To add your own control type, you'll need to create a factory function that provide RudeControl the ability to generate a new control of your type, and then register this function with the RudeControlRegistration system.
For example, here's the factory for the "RBWindControl" class, which is the UI control that displays the little wind indicator in the bottom-right corner of the screen in Anytime Golf. The factory takes a list of tokens provided by the configuration file parser and parses out the rectangle that defines the control's position on the screen. It then returns a pointer to the new control. The declaration of the RudeControlRegistration object below the function definition is what actually binds the factory function with the configuration file parser at init-time.
#include "RudeControl.h"
...
/**
* ConstructRBWindControl factory assistant for RudeControl. This is called by RudeControl::Load()
*/
RudeControl * ConstructRBWindControl(std::list<std::string> &tokens, const std::string &originalDesc)
{
RBWindControl *c = new RBWindControl();
RUDE_ASSERT(c, "Failed to construct control");
// Rect {top, left, bottom, right}
std::string rectstr = RudeControl::PopToken(tokens, originalDesc, "rect");
RudeRect rect;
RudeControl::ParseRect(rectstr, rect);
c->SetRect(rect);
return c;
}
RudeControlRegistration rbWindControlRegistration("RBWindControl", ConstructRBWindControl);