Building your GUI with Gwen
I've talked about different GUI solutions and how I started to integrate the Gwen GUI library by Garry Newman into our engine, Ginkgo, in order to power the live editor. The live editor is a tool to build and tweak levels of our upcoming game. It has access to all runtime data of the engine. In order to manage the connections between the data and the GUI, I chose the good old Model-View-Controller pattern. Here's how I'm using Gwen in our project.
The Data To Tweak
Ginkgo is a component-based engine. Game objects, which we call nodes, have basic geometric data. But their functionality is located in their components. Components are classes that wrap one specific aspect of a game object. They are coded in C++ and we use a Python-based Preprocessor-like script to generate their data definitions, which also include default values for all properties. Properties are (soft-)typed "data fields" of components. Some properties can be set via writing a specific piece of data into a specific memory location, made slightly safer than it sounds by our Property class. Some properties require accessor functions to be called. Since we do not have an inheritance system, but an aggregation system, we needed a solution to replace the comfort of the former while maintaining the flexibility of the latter. Thus we implemented a template solution; Template as "pattern", not as "STL". A template defines a game object, a required set of components and the default values of all the data of these components. There can be game objects without templates, but we currently don't support runtime generation of game objects without templates. Each game object can override its template's properties. Thus we end up with the following hierarchy of initialization of all properties. Higher levels override lower levels in the hierarchy shown in this graphics:
We use the above-mentioned properties to establish rtti-like access to game objects. Reading and writing properties is implemented via a delegation mechanism. Well, actually there are a couple of different ways to read/write data values to/from objects and only the most slow & uncomfortable one is based on delegates. Though you won't notice that from the "user" side anyway. The same mechanism is used to implement editor access. Soft-typed access, as I'm used to call it, lends itself to data bindings for GUI's. The functionality — apart from a number of convenience functions to allow for faster browsing of scene objects — is that there is a property tree that reflects a game object. The nodes of the tree are the game object hierarchy (game objects can be inside other game objects to group them) and the components. The properties are at the leaves of the tree. The Controller of the MVC structure I implemented manages the tree. The leaves got their own controllers that manage the binding between a property and the text field that allows editing it. The clue is that you set the runtime value as well as the definition value of the property. And you can edit a game objects's template with a similar tree widget. As a level designer, you have full access to the value hierarchy. This is especially important in a live editor, where runtime evens might override the runtime data of a property. We use the per-class property definitions when creating the game object. When the engine is compiled for edit mode, the same definition is used to store the values that get written back into the property's XML definition. These definitions are stored on save, together with the edited templates. Bear in mind that the original definitions are per class. We store an extra definition structure per instance for editing. Runtime values are not preserved, so as not to overwrite startup values. I.e. you want your objects to spawn in the right place, even if they moved during gameplay.
Extending the Library
At first, I thought Gwen is a well rounded library. While most nuts and bolts are in place, it still lacks some features I needed. Gwen comes with a property editor, but there are just text fields, number fields and a color picker that does not support alpha. All our data descriptions are string-based, so we're currently using the text field for all the editing. Later, I will have to implement at least a pop up list, an alpha-supporting color picker, and a clamping-supporting number box. In order to make the editor more comfortable to use, I wanted the property fields to display the definition value when you mouse over them. Usually they show the current runtime value. And there were other little changes I had to make to the text field-based property editor fields, e.g. exiting them without entering the data by pressing escape, or rewire them so that they only fire change events when the user presses enter. Per default, the event is fired once the user presses any key.
Here's how I've extended Gwen to suit my needs:
- I've added an
Event::Caller called onEntered to the class
Gwen::Controls::Property::Base. It is called when
OnTextEntered() is called by the underlying text box. Since continuous update triggered some cases where the value was incorrect (and immediately set back to the last valid value) I had to add this event to the library itself.
- I've added an
OnKeyEscape() function to the
Gwen::Controls::TextBox class that blurs (de-focusses) the text box.
- My Property Field Controllers are hooked up to the
Gwen::Controls::Property::Base subclasses via OnPropertyValueChanged. This event is fired by Gwen, if the user edits the property. It is responsible for writing the new value into the property data field as well as the property's definition. If a template is edited, the same event writes into the template definition. Property definitions and template definitions are saved. No API changes necessary.
- The above-mentioned behavior of showing the default value on hover is added via the
Event::Caller. No API changes necessary.
- I'm adding checkboxes to mark if a template default was overridden via the GUI by attaching them to the
Gwen::Controls::Property::Base. Since scroll bars were overlapping with checkboxes I had to adapt the rendering a little bit. I also had to add rendering for different checkboxes. A simple subclass to the existing checkbox does the job nicely.
I know that there are as many ways to architect a game engine as there are stars in the sky. Ours might not be the most performant one, and neither is it the most comfortable to work with. But with Gwen, we've found a GUI library that allows for a cross-platform live-editor that does not explode our code base and is simple enough to be portable to future platforms. It's not the solution to every GUI problem we'll ever face, but it proved to be a versatile, easy to integrate, and well behaved library. Now, if I manage to get rid of all the dynamic_casts I could even use it in deployment builds.
P.S.: Sorry that I did not include source code. My code is so intertwined with the property system that I'd have to add the source of that, too, which would explode this post.
P.P.S.: We're currently using good old XML as a data format. But its well encapsulated and we're planning to enter the 21st century by totally going for JSON in the near future.
P.P.P.S.: This posting concludes my series about Gwen. I feel like I should write about a less technical topic next.