Sunday, March 29, 2009

Let's do something C++ can't

Recently I was writing some code to load levels. This is a common task but I think I ended up with some pretty neat code that can be used in a variety of ways.

My levels are rather unusual. A level can be generated totally by my code. So I can be as a vague as 'make a level' or I can say 'I want a monster here and here and walls here' and describe everything down to the last detail. I'm still somewhat in the early stages of development, so the layout of the level file is likely to change a lot as I add new things.

My first thought was XML. I'd write something like:

 
<level>
<name>Level One</name>

<simpleDescription>
<setting>
<stone/>
<wood/>
<setting>

<enemies>
<orc/>
<dragon/>
<enemies>
</simpleDescription>

</level>
 


Obviously this is code that's going to change quite a bit as I develop the game. Writing an XML parser would mean everytime there's a code change - the data files and the parser must change. Not only that but I'd be editing these files by hand. XML isn't the most pleasant medium to write in.

XML Alternatives

So what can be used instead of XML? Dimly, ever so dimly I recalled an XML alternative. The way I remember this alternative was that it used whitespace in a novel way. The above xml code would be rendered:


Level
Name : LevelOne
SimpleDescription
Setting
Wood
Stone
Enemies
Orc
Dragon


This seemed great - unfortunately I couldn't find any reference to it, perhaps my google-foo is weak. All I could find was JSON, which is a subset of Javascript laid out to look like XML. I toyed with JSON for a while but in the end it was just as unpleasant as XML for me.

After failing to find the whitespace xml alternative my mind wandered to kjAPI a C++ game programming library (it's wonderful though the public release is somewhat tricky to compile.) kjAPI has this great reflection system, you mark a number of fields to be 'metaclassed'. Once the field's are metaclassed they can be directly manipulated by it's GUI editor - and they can be serialized to XML. This is done automatically no parser needs writing just automatic load and save functionality. Excellent.

Of course C# has this in the form of the serialize attribute - but this generates computer XML. I'm going to be writing these level descriptions - I have a hard enough time with human xml! Never mind writing my XML so that the serialize methods of C# will correctly map it to my class data.

Next I considered going the C# serialize route but doing all my editing in a GUI. Then I started to think about how such a GUI would need to appear, and I started to look at datagrid controls and things ... soon my initial enthusiasm waned. Also if I'm going the GUI route - why bother with the XML? I could just use a binary file. Deciding the GUI option would be a unreasonable time sink I came upon my current solution.

I'd do what all programmers love to do - roll my own!

A general solution that fits my needs

First here's the data file.


+Level
{
_name = "Level One"
_simpleDescription = +SimpleDescription
{
@AddSetting("Wood")
@AddSetting("Stone")
@AddMonster("Orc")
@AddMonster("Dragon")
}
}


Ok - it looks a lot like the others, but it's got a clever trick. I have some general code that searches the current C# assembly and creates an object the type 'Level' then it assigns it's fields _name and _simpleDescription. The code can do this for any class in the C# assembly provided the data files names match up correctly.

So if I have a class item.


class Item
{
string _name;
string _description;
}


I can write


+Item
{
_name = "Some toast."
_description = "A piece of toast, it's cold and slightly burnt."
}


Then in C# code I can write something like (I've called my generic object loader 'dresser'):


Dresser dresser = new Dresser("the text of the data file goes here.");
Item item = dresser.GetNextObject<Item>();


And that's it. That item is now filled up, it's name is set to 'Some toast' and the description is set too.

How it works

The '+' sign means create a new object. The object type is whatever follows the +[that is what goes here]. I use C#'s reflection system to find the class with the name, then using various trickery I create an instance of that class at runtime. The name in the data file and the name in the C# file must be the same. Then I go through the fields and set them in the same way, I search the class using reflection for a field's name and then set it directly (using reflection private fields can be set).

The @ symbol is something I've added only for this example (my level's aren't of the orc, dragon kind). It merely means calls a function of this name. So once again I search for the method and call it with the appropriate arguments. Neat right?

This means I can change and extend the code as I like and I only need change the data where the two don't match. There's no parsing code to rewrite.
Post a Comment