Sunday, August 5, 2012

On XNA and UIs

HTML UI using Awesomium in XNA
A couple of years ago, while working on my old 'Settler' project, I looked around at the state of user interfaces available in XNA, having little interest in writing the nuts and bolts of my own.

There really wasn't that much, a lot of half working and abandoned projects. Some nice things in C, but my preferred working environment was C#. 

Wanting something a bit more useful in a complicated RPG than a simple shooter style interface, I eventually downloaded a trial of the DigitalRune UI library and fiddled with that.  It worked ok... but come a year or two later when I go back to look at my project the trial key has expired, so now I can't run my old code without either getting a new key or hacking out all the UI stuff.

I decided I wasn't going down that road again, so now I was looking for a better way to do a UI.



I saw something about Awesomium, and after reading a bit more, I noticed that several games, including some AAA MMOs were using it, though mostly for in-game browers or stores.  But it did look promising.

Basically Awesomium is a headless browser built on WebKit.  You can interface with it by code and it will render it's output to a bitmap.  That's all there is to it, at heart.

And it works REALLY well.  It didn't take too long for me to get a quick test project up and running with a cheesy HTML UI done up with some jQuery.  You can easily interface between Javascript events and your code (i.e. when a HTML button is pressed, you get an event in your code).

You might expect performance to be a problem, but it isn't at all.  The webView seems to draw quickly and efficiently.  In fact it processes quickly enough to be pointed at web video sites and show 1080p videos.  Like I said, fast.  Part of that is the nice code here, which is included in the test project.


The next problem I hit was XNA's rather poor input handling.  XNA simply polls the state of input devices when Update() is called (or whenever you do it, really, but that's by convention.  No reason you couldn't handle user input in your Draw function after all).

Now, that works fine for seeing if LEFT or RIGHT is down or if a button is pressed, but when typing even moderately rapidly it misses things.  Only natural, if a key isn't down at the precise moment when it is polling, it would get missed.    This was clearly obvious once I had some text boxes in the UI.

The simplest solution is to hook into the Windows message pump.  I fiddled around with this for a while (I can remember when you _had_ to do stuff like this, more or less, way back in the day).  But of course someone had already done it since this must be a common problem, and it was much faster to just use that code.

More UI, this time with jQuery dialogs.
Anyway, this lets you get keystrokes and mouse input in the normal event-driven way most would normally expect it.  This is also important as you can then take those keyboard events and pass them directly through to Awesomium, essentially making the entire input layer transparent to your program.

All that leaves is hooking up your events, and things will begin to just work.  I refactored my test code into a library, which I then imported into Settler2.  It took me less than five minutes and I was controlling the renderer settings from buttons in the HTML UI.

It was easy.  In my game, for instance, I added:

UI.CreateJSObject("Renderer", "Command", RendererCommandClicked);

and

public void RendererCommandClicked(object sender, JSCallbackEventArgs e)
   {
   System.Console.WriteLine(e.Arguments[0].ToString());
   
   String argument = e.Arguments[0].ToString();
   String text = String.Format("Renderer command: {0}", argument);
   (etc, do stuff with the renderer settings...) 



... and in UI.htm (the UI file loaded):

  <button onclick='Renderer.Command("NewTerrain");' >Regen Map</button>
  <br />
  <button onclick='Renderer.Command("DebugRenderTargets");' >Debug Rendertargets</button>
  <br />
  <button onclick='Renderer.Command("ToggleWireframe");' >Wireframe</button>


... and it just works.  I was pleasantly pleased.  This makes it very easy, in my opinion, to whip up user interfaces (though your mileage will off course vary depending on your artistic/HTML/CSS abilities).

I was really pleased with this, and it seems awfully handy.  So I've decided to release the code, which can be found at the public repository I've created for this and further stuff I decided to release: https://bitbucket.org/DrHeinous/step2studiospublic


No comments:

Post a Comment