Creating Moddable Games with XML and Scripting Part I
by Oli Wilkinson


ADVERTISEMENT

Get the source code for this article here.

In this short series of articles, I will explore in how XML and scripting can be combined and used to create modifiable content for games. Game modification is nothing new, being popularised by titles such as Quake 2 and Half-Life. However, despite its popularity there seems to be a lack of articles discussing how to mod-enable your own games. With this series of articles I will explore the use of a JavaScript environment combined with XML to store your game data to allow you to mod-enable your own games.

Roadmap for the series

This series will span the development of a small game, "jsInvaders" and focus solely upon creating and embedding the script and XML parsing interfaces that serve as a facility to modify (mod) the content and behaviours within the game.

To follow this series, I am assuming that you have knowledge of C++, OpenGL and understand the principles of game creation. This series is not a game creation tutorial, so I will not be focusing on normal game aspects such as graphics, sound, game logic, et al. Here's a brief outline of how you can expect this series to progress:

  • Part I
    • Serves as an introduction to the series; briefly explains what XML is and discusses the use of XML in games. Introduces TinyXml, a small open-source XML parser and shows you how to use it for parsing the basic data required by the game.
  • Part II
    • A brief introduction to JavaScript (using the Mozilla "SpiderMonkey" engine). Discusses the development of a scripting API and how to write the 'glue' code that sits between the game and the scripting engine.
  • Part III
    • Brings the series to a close with expansion of the basic scripting API developed in Part II and discusses how to develop a Game Object Model to effectively link XML with your scripted components.

Brief Introduction to XML

Now that you understand where this series will be headed, I will now introduce you to the basics of XML documents. After reading this section, you should understand exactly what XML is, how to create it and be thinking of ways you can apply XML to your games. I suggest that you read Richard Fine's "XML in Games" available on GameDev.net, as it serves as an excellent introductory text on the subject.

The XML document specifications require each valid document to begin with an XML file declaration:

<?xml version="1.0" encoding="utf-8"?>

Quite simply, this declaration is stating that the document is XML version 1.0 and is encoded with the UTF-8 character set.

XML is a meta-language; in short you are able to define your own set of tokens (or 'tags'), rules and structures. Most XML files are defined by a DTD, or Document Type Definition which dictates the rules and structure of the document by supplying a list of legal tokens. DTD rule creation is important as it allows the XML parser to quickly accept or reject documents on loading. With that said, we are using TinyXml and it is a non-validating parser; it will ignore any DTD it finds and just processes the document elements with no rules. This is fine for our purposes as we can build our own validation rules into the parsing stage.

XML documents have a strict hierarchical structure; elements are contained within other elements, which in turn are contained within higher elements in the document tree. This tree-like structure needs a root, the document root, and is the single element which all other elements are descended from. Every element on the next hierarchical level is called a child of an element and elements with the same root element are said to be siblings.

The hierarchical structure of XML allows for easy parsing of the tree and can introduce great flexibility when it comes to manipulating the data. Whole branches can be detached and placed elsewhere with little difficulty.

Let's take a look at a basic declaration of a jsInvaders level:

<?xml version="1.0" encoding="utf-8"?>
<invadersgame>
      <level>
            <alien />
            <alien />
            <alien />
      </level>
</invadersgame>

You should be able to see that the document root element is <invadersgame>. The root then contains a level element, which in turn contains three alien elements. In order to clarify this, I'll break down the basic jsInvaders level document:

  • <invadersgame> is the document root
  • <level> is a child of <invadersgame>
  • <alien> is a child of <level>
  • The <alien> elements in a <level> element are siblings to each other

If you're used to Object Oriented Programming (OOP), then you'll probably be able to picture how you'd map these elements into classes. By referring to the code attached to this article, you'll see how I've begun mapping these objects into C++ classes for use in the game. So far in the code I have the game classes sketched out for dealing with levels, aliens and the player ship. I won't go into detail about much of the rest of the game classes as it is beyond the scope of this article.

If you examine the file jsiAlien.h, you'll see that I have defined several properties for the object. Things like 'color', 'position' and 'points value' are all properties of the Alien object that will need values to be of use in the game. XML document elements can also have attributes assigned to them, meaning that you can represent the data for objects such as the Alien object with an XML element tag.

<alien xpos="0" ypos="100" color="red" points="10" />

If you look at the declaration above you will see that I have assigned 4 properties to the XML element that represents the Alien. In this example the alien is red, is positioned at (0, 100) and awards 10 points to the player when killed. Taking this forward, we begin to build the XML file that defines a basic game level.

<?xml version="1.0" encoding="utf-8"?>
<invadersgame>
      <level>
            <!-- here's where we declare the aliens -->
            <alien xpos="0" ypos="100" color="red" points="10" />
            <alien xpos="100" ypos="100" color="green" points="10" />
            <alien xpos="200" ypos="100" color="blue" points="10" />
      </level>
</invadersgame>

It should be apparent that we now wish to have 3 aliens of different colours, each 100 units apart horizontally. You'll notice that I've begun commenting my XML document with the XML comment tags, <!-- this is a comment -->. I personally find commenting all of my code to be good practice, even in this simple example :). The benefits of good commenting will reveal themselves later on when the files start getting bigger.

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

Discuss this article in the forums


Date this article was posted to GameDev.net: 10/4/2004
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Featured Articles
Scripting Languages

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!