Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
111 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Creating Moddable Games with XML and Scripting Part I


Parsing the XML

Now that you understand the concept of XML and how document elements relate to the data contained within class objects, I will take you through the process of parsing the XML document. The act of 'parsing' is when the raw document is read in by your program and transformed into the elements that make up the document tree. With jsInvaders, the parsing stage will consist of reading the XML document into memory and creating objects within the game environment based on the data within it.

For this section you will require an XML parser; I am using TinyXml, a small open-sourced library that was developed for easy handling of XML documents. Bear in mind that there are several XML parsers available which all follow the basic concepts, so pick one you feel comfortable with if you don't wish to use TinyXml.

With TinyXml, all of the elements are contained within a TiXmlDocument structure. The first thing you need to do is create an instance of this object and use it to load and parse the XML file. This is shown in the code fragment below (taken from jsiGame.cpp):

int jsiGame::loadXmlLevels(const char *filename)
{
  TiXmlDocument     *xmlDoc = new TiXmlDocument(filename);
  if (!xmlDoc->LoadFile())
  {
    // Fatal error, cannot load
    return 0;
  }

  // Do the document parsing here

  xmlDoc->Clear();

  // Delete our allocated document and return success ;)
  delete xmlDoc;
  return 1;
}

The TiMxlDocument->LoadFile() method will fail if there is an error with the document; for example it may not exist at the specified location or there may be an error with the actual structure of the XML document itself. After you have processed the XML document, you have to call the Clear() method of the TiXmlDocument in order to free up the memory allocated to loading and parsing the document.

With the document structure we defined earlier to hand, we can begin parsing the XML file with TinyXml. To get the document root, we call FirstChildElement() from the document that was just loaded.

TiXmlElement *xGame = 0;
xGame = xmlDoc->FirstChildElement("invadersgame");

if (!xGame)
{
  // The game element couldn't be found, 
  // we have to say goodbye!
  // clear and delete the doc before exiting
  xmlDoc->Clear();
  delete xmlDoc;

  return 0;
}

The TiXmlDocument class is based on TiXmlElement, so the FirstChildElement() call can be used with any TinyXml element. If the requested element couldn't be found, the call will return 0, so it's important to check for failures and handle them appropriately. In the example above, the object xGame now holds a pointer to the root element in the document. This element will now be used as a root for parsing the level data.

TiXmlElement *xLevel = 0;
xLevel = xGame->FirstChildElement("level");

if (!xLevel)
{
  // No level elements were found
  // return with an error
  // clear and delete the doc before exiting
  xmlDoc->Clear();
  delete xmlDoc;
  return 0;
}

// the level has been found, parse it here...

As before, the object xLevel will point to the first level element tag. When jsInvaders encounters a level element, a new instance of a level class is created which is then passed the relevant TiXmlElement for parsing the level data.

To parse the alien elements contained within a level, a similar procedure is adopted. The main difference in the next step is that there will be several alien elements (siblings) and that we will be parsing the attributes from the XML file and using them to fill the jsiAlien class.

Because the alien elements are siblings to each other, we can iterate through the level element searching for more occurrences of the object using TiXmlElement->NextSiblingElement().This is shown in the code fragment below (taken from jsiLevel.cpp):

int jsiLevel::parseXmlLevel(TiXmlElement *xLevel)
{
  TiXmlElement *xAlien = 0;
  int nAlienCount = 0;

  xAlien = xLevel->FirstChildElement("alien");
  // Loop through each alien in the level
  while(xAlien)
  {
    // create a new alien
    jsiAlien *alien = createAlien();
    // Parse this particular alien

    // grab the position data from the attributes
    // check it exists before adding the attribute
    if (xAlien->Attribute("xpos"))
    {
      alien->position.x = (float)atof(xAlien->Attribute("xpos"));
    }
    if (xAlien->Attribute("ypos"))
    {
      alien->position.y = (float)atof(xAlien->Attribute("ypos"));
    }

    alien->color = jsiUtility::colourFromText( xAlien->Attribute("color") );

    // move to the next element
    xAlien = xAlien->NextSiblingElement("alien");
    // increment our counter
    ++nAlienCount;
  }
return 1;
}

You will notice that every time an alien element is encountered, the game requests that the level creates an instance of the jsiAlien object. It should be apparent that the number of aliens created in the level maps 1:1 to the XML document :)

Getting an attribute associated with an element is easy, it's just a matter of calling TiXmlElement->Attribute( <name> ).

if (xAlien->Attribute("xpos"))
{
  alien->position.x (float)atof(xAlien->Attribute("xpos"));

}

Like other TinyXml functions, the Attribute call will return 0 if the attribute doesn't exist. I'm checking for the existence of an attribute before I read it, this allows the author of the XML document to exclude attributes they won't need. It's worth remembering that all attributes are stored as text and you will need to convert them to your desired format before you store them in your objects.

At this stage, we've actually covered all the XML we'll need for now. When it comes to loading textures and scripts, it's a simple case of adding more elements to the XML document and parsing them in the level loading routines. In any case, you'll see that I've expanded the XML document slightly in the next instalment to account for the extra features the game has.

Naturally, TinyXml has more features that the ones we're using here (including the creation and saving of XML documents) so once you get to grips with how it works, it may be useful to start exploring how these work for yourself. I won't be covering them here because I don't need them in this particular project, but it's worth bearing in mind that you might need them someday.

That's it for the first part of this series. I'm hoping that we covered enough XML parsing to feel comfortable with what we're trying to achieve here and how to use TinyXml in your own projects. I urge you to experiment, if only by adding configuration options or score saving routines to use XML data, I guarantee that you'll be bitten by the bug and see how useful a file format it is.

Next time around, I'll be covering how to set up a scripting environment within your project and how to link the game objects we've just created to the scripting runtime. In particular, I'll be focusing on creating a script API that will be used to control the motions and behaviours of objects within the game.

Until next time...

Oli

Useful Links

"Extensible Markup Language (XML) 1.0 (Third Edition)" W3C
http://www.w3.org/TR/REC-xml

"jsInvaders" Oliver Wilkinson
http://www.evolutional.co.uk/jsi

"TinyXml" Lee Thomason and Yves Berquin
http://www.grinninglizard.com/tinyxml

"W3Schools XML" W3Schools<
http://www.w3schools.com/xml

"XML.com" XML.com
http://www.xml.com

"XML in games" Richard Fine
http://www.gamedev.net/reference/articles/article1928.asp




Contents
  Introduction
  Parsing the XML

  Source code
  Printable version
  Discuss this article

The Series
  Part I