• entries
25
39
• views
46906

# Data Driven UI

823 views

Prologue

For one of my first XNA games, I decided to hand code my UI. This left me with a ton of statements like this:

StaticImage img = new StaticImage(someImage);
img.Position = (50,50)
img.CenterHorizontal = true
...

I thought this would be OK, since the project was intended to be small and essentially over upon release. Then I decided to port it to the iPhone; when I looked over the code again, I threw myself on my sword.

Shameful.

The UI code made my codebase a bloated, cluttered mess. It was hard to port correctly (not to mention hard to develop initially), and it cropped up all over the place. The UI animations added even more. In the end, I think more of the code was devoted to UI than to actual gameplay.

I vowed right then and there - never again. I got to work creating a new UI system that would allow me to data drive everything with minimal code work.

Development

Here were some of the requirements:
(1) No UI should need to be created in game code aside from a "LoadWindow" call.
(2) Creating custom UI code should require as little typing as possible - reasonable defaults should be used for virtually everything that wasn't explicitly specified.
(3) I should be able to specify simple animation sequences. Fades, Slides, Rotations, etc.
(4) This should interface with string table support in case I want to do any future localization.
(5) The UI files should be human readable. Not necessarily XML, but definitely not binary.
(6) The system should support relative, auto-adjusting layouts. If I ever want to move to a different platform/resolution, I don't want to have to recreate everything. Full UI portability is not 100% feasible, but every little bit helps.

Most of these problems have pretty straight-forward solutions. The actual widgets are defined in code, and a factory parses an XML-esque file to instantiate them. Here's an example:

StaticImage Name: image_dolly Image: background Position: 50,50/StaticImageLabel Name: label_title Text: "Hello World" FontName: Vera/LabelAnimation Fade widget=image_background StartPosition=res,* EndPosition=*,* /Fade/Animation

When I call LoadWindow on this file, it loads up all the widgets and animations. I can then make a call to "PlayAnimation" to actually play the animation.

That's the high level look. Now let's take a look at how the system was built to fulfill the above mentioned requirements:

(1) No UI should need to be created in game code aside from a "LoadWindow" call.
This should be fairly self-explanatory. The UI loader knows how to interpret all the possible widgets (all the ones I've implemented so far) and any parameters that need to be set on them like position and color. Sometimes writing the UI file can end up more verbose (I don't support looping to create groups of widgets), but generally the files trend smaller than the equivalent code that would otherwise be necessary.

(2) Creating custom UI code should require as little typing as possible - reasonable defaults should be used for virtually everything that wasn't explicitly specified.
This is also straight-forward. If something isn't specified in the UI file, a default is used. Conversely, I have asserts in place for where parameters are required and not specified (for instance, the Image parameter of a StaticImage). The defaults generally make sense - centering is off by default, a default system font is created for strings, the default color is white, and so on.

(3) I should be able to specify simple animation sequences. Fades, Slides, Rotations, etc.
There was a little work involved in this one.

First, the system recognizes two types of animations: Parallel Animations (default) which are animations where all the steps run in parallel - ie: fading all the widgets in at the same time. And there are Chain Animations, which happen in sequence - moving a widget to one location and then to the next and then to the next.

Each animation can have a series of operations - fades, slides, stalls, and rotations right now. Each of those operations applies to a single widget. So an operation can slide a widget from off-screen to the center of the screen as part of a larger animation. For convenience sake, I also define an "all" widget, which tells my UI system to apply that animation to every widget in the file - useful for full-screen fades.

An animation operation can also be another animation. So if I have a Chain Animation named slide_in_out, I can reference that animation from within a Parallel Animation and thus have the chain occur while other things are going on.

There are lots of little nuances that go into this - animations can loop, for instance; operations can have easing associated with them for more interesting transitions; operations can also show widgets once they begin or hide widgets once they end.

Finally, you may be wondering what the * characters in my example represent. They basically represent the value of the associated parameter when that widget was initially created. For instance, in the above example, *,* gets interpreted as 50,50 when the animation executes. This is a convenience token that allows me to move widgets around without finding every animation that references the widgets and changing those as well.

(4) This should interface with string table support in case I want to do any future localization.
Whenever a string is read within the UI file - for example, for labels - the underlying code first looks to see if that string exists within a master string table. If not, it logs a message and uses the string as-is. This allows the code to run unhindered but also lets me know where my string table is lacking.

Unfortunately, right now the system does not have good support for formatted strings. So I can't put a string in my table that reads "Player 1 has {1} points" and have the system resolve {1} automagically. That requires code.

(5) The UI files should be human readable. Not necessarily XML, but definitely not binary.

You'll notice that the above example looks XML-esque while not actually being XML. There's no dark secret here; I had some file loading code in my codebase but no XML loaders (Objective-C is not as nice about this sort of thing as C#). I opted for the lazy route. I could just as easily put <> tags around everything but opted not to.

(6) The system should support relative, auto-adjusting layouts. If I ever want to move to a different platform/resolution, I don't want to have to recreate everything. Full UI portability is not 100% feasible, but every little bit helps.
This is honestly where my system is the weakest. WPF has all sorts of nice little grid layouts and tables and what-not that will automatically rearrange/resize widgets. Getting that to work well would require more time than I wanted to devote, so I opted for a lazy route:

I have two variables: res and halfres. When they are seen for a vector2 parameter, they are translated to, well, the screen's resolution or half the screen's resolution respectively. This makes doing things like centering or sizing a little easier.

Epilogue

Obviously the system has holes. If I had more time to devote, I'd work on some nice auto-layout features. I might also add a variable system to specify UI global variables (ie: a common "down color" for text buttons). Sound integration is on the TODO list. The biggest thing I want to do, though, is write a nice layout tool so I don't have to hand-write these files: they're 150% better than writing the associated code but still time consuming.

Regardless, the system is a big step up from where I was. It's made my iPhone development much less painful and allows me to focus less of my time on nice UI effects and more on the actual gameplay.

There are no comments to display.

## Create an account

Register a new account

• ### What is your GameDev Story?

In 2019 we are celebrating 20 years of GameDev.net! Share your GameDev Story with us.