Parsing states from an XML file

The file we will be parsing is the following (test.xml in source code downloads):

<?xml version="1.0" ?>
<STATES>
<MENU>
<TEXTURES>
  <texture filename="assets/button.png" ID="playbutton"/>
  <texture filename="assets/exit.png" ID="exitbutton"/>
</TEXTURES>

<OBJECTS>
  <object type="MenuButton" x="100" y="100" width="400" 
  height="100" textureID="playbutton" numFrames="0" 
  callbackID="1"/>
  <object type="MenuButton" x="100" y="300" width="400" 
  height="100" textureID="exitbutton" numFrames="0" 
  callbackID="2"/>
</OBJECTS>
</MENU>
<PLAY>
</PLAY>

<GAMEOVER>
</GAMEOVER>
</STATES>

We are going to create a new class that parses our states for us called StateParser. The StateParser class has no data members, it is to be used once in the onEnter function of a state and then discarded when it goes out of scope. Create a StateParser.h file and add the following code:

#include <iostream>
#include <vector>
#include "tinyxml.h"

class GameObject;

class StateParser
{
  public:

  bool parseState(const char* stateFile, std::string stateID, 
  std::vector<GameObject*> *pObjects);

  private:

  void parseObjects(TiXmlElement* pStateRoot, 
  std::vector<GameObject*> *pObjects);
  void parseTextures(TiXmlElement* pStateRoot, 
  std::vector<std::string> *pTextureIDs);

};

We have three functions here, one public and two private. The parseState function takes the filename of an XML file as a parameter, along with the current stateID value and a pointer to std::vector of GameObject* for that state. The StateParser.cpp file will define this function:

bool StateParser::parseState(const char *stateFile, string 
stateID, vector<GameObject *> *pObjects, std::vector<std::string> 
*pTextureIDs)
{
  // create the XML document
  TiXmlDocument xmlDoc;

  // load the state file
  if(!xmlDoc.LoadFile(stateFile))
  {
    cerr << xmlDoc.ErrorDesc() << "
";
    return false;
  }

  // get the root element
  TiXmlElement* pRoot = xmlDoc.RootElement();

  // pre declare the states root node
  TiXmlElement* pStateRoot = 0;
  // get this states root node and assign it to pStateRoot
  for(TiXmlElement* e = pRoot->FirstChildElement(); e != NULL; e = 
  e->NextSiblingElement())
  {
    if(e->Value() == stateID)
    {
      pStateRoot = e;
    }
  }

  // pre declare the texture root
  TiXmlElement* pTextureRoot = 0;

  // get the root of the texture elements
  for(TiXmlElement* e = pStateRoot->FirstChildElement(); e != 
  NULL; e = e->NextSiblingElement())
  {
    if(e->Value() == string("TEXTURES"))
    {
      pTextureRoot = e;
    }
  }

  // now parse the textures
  parseTextures(pTextureRoot, pTextureIDs);

  // pre declare the object root node
  TiXmlElement* pObjectRoot = 0;

  // get the root node and assign it to pObjectRoot
  for(TiXmlElement* e = pStateRoot->FirstChildElement(); e != 
  NULL; e = e->NextSiblingElement())
  {
    if(e->Value() == string("OBJECTS"))
    {
      pObjectRoot = e;
    }
  }

  // now parse the objects
  parseObjects(pObjectRoot, pObjects);

  return true;
}

There is a lot of code in this function so it is worth covering in some depth. We will note the corresponding part of the XML file, along with the code we use, to obtain it. The first part of the function attempts to load the XML file that is passed into the function:

// create the XML document
TiXmlDocument xmlDoc;

// load the state file
if(!xmlDoc.LoadFile(stateFile))
{
  cerr << xmlDoc.ErrorDesc() << "
";
  return false;
}

It displays an error to let you know what happened if the XML loading fails. Next we must grab the root node of the XML file:

// get the root element
TiXmlElement* pRoot = xmlDoc.RootElement(); // <STATES>

The rest of the nodes in the file are all children of this root node. We must now get the root node of the state we are currently parsing; let's say we are looking for MENU:

// declare the states root node
TiXmlElement* pStateRoot = 0;
// get this states root node and assign it to pStateRoot
for(TiXmlElement* e = pRoot->FirstChildElement(); e != NULL; e = e->NextSiblingElement())
{
  if(e->Value() == stateID)
  {
    pStateRoot = e;
  }
}

This piece of code goes through each direct child of the root node and checks if its name is the same as stateID. Once it finds the correct node it assigns it to pStateRoot. We now have the root node of the state we want to parse.

<MENU> // the states root node

Now that we have a pointer to the root node of our state we can start to grab values from it. First we want to load the textures from the file so we look for the <TEXTURE> node using the children of the pStateRoot object we found before:

// pre declare the texture root
TiXmlElement* pTextureRoot = 0;

// get the root of the texture elements
for(TiXmlElement* e = pStateRoot->FirstChildElement(); e != NULL;
e = e->NextSiblingElement())
{
  if(e->Value() == string("TEXTURES"))
  {
    pTextureRoot = e;
  }
}

Once the <TEXTURE> node is found, we can pass it into the private parseTextures function (which we will cover a little later).

parseTextures(pTextureRoot, std::vector<std::string> *pTextureIDs);

The function then moves onto searching for the <OBJECT> node and, once found, it passes it into the private parseObjects function. We also pass in the pObjects parameter:

  // pre declare the object root node
  TiXmlElement* pObjectRoot = 0;

  // get the root node and assign it to pObjectRoot
  for(TiXmlElement* e = pStateRoot->FirstChildElement(); e != NULL; e = e->NextSiblingElement())
  {
    if(e->Value() == string("OBJECTS"))
    {
      pObjectRoot = e;
    }
  }
  parseObjects(pObjectRoot, pObjects);
  return true;
}

At this point our state has been parsed. We can now cover the two private functions, starting with parseTextures.

void StateParser::parseTextures(TiXmlElement* pStateRoot, std::vector<std::string> *pTextureIDs)
{
  for(TiXmlElement* e = pStateRoot->FirstChildElement(); e != 
  NULL; e = e->NextSiblingElement())
  {
    string filenameAttribute = e->Attribute("filename");
    string idAttribute = e->Attribute("ID");
    pTextureIDs->push_back(idAttribute); // push into list

    TheTextureManager::Instance()->load(filenameAttribute, 
    idAttribute, TheGame::Instance()->getRenderer());
  }
}

This function gets the filename and ID attributes from each of the texture values in this part of the XML:

<TEXTURES>
  <texture filename="button.png" ID="playbutton"/>
  <texture filename="exit.png" ID="exitbutton"/>
</TEXTURES>

It then adds them to TextureManager.

TheTextureManager::Instance()->load(filenameAttribute, idAttribute, TheGame::Instance()->getRenderer());

The parseObjects function is quite a bit more complicated. It creates objects using our GameObjectFactory function and reads from this part of the XML file:

<OBJECTS>
  <object type="MenuButton" x="100" y="100" width="400" 
  height="100" textureID="playbutton" numFrames="0" 
  callbackID="1"/>
  <object type="MenuButton" x="100" y="300" width="400" 
  height="100" textureID="exitbutton" numFrames="0" 
  callbackID="2"/>
</OBJECTS>

The parseObjects function is defined like so:

void StateParser::parseObjects(TiXmlElement *pStateRoot, 
std::vector<GameObject *> *pObjects)
{
  for(TiXmlElement* e = pStateRoot->FirstChildElement(); e != 
  NULL; e = e->NextSiblingElement())
  {
    int x, y, width, height, numFrames, callbackID, animSpeed;
    string textureID;

    e->Attribute("x", &x);
    e->Attribute("y", &y);
    e->Attribute("width",&width);
    e->Attribute("height", &height);
    e->Attribute("numFrames", &numFrames);
    e->Attribute("callbackID", &callbackID);
    e->Attribute("animSpeed", &animSpeed);

    textureID = e->Attribute("textureID");

    GameObject* pGameObject = TheGameObjectFactory::Instance()
    ->create(e->Attribute("type"));
    pGameObject->load(new LoaderParams
    (x,y,width,height,textureID,numFrames,callbackID, animSpeed));
    pObjects->push_back(pGameObject);
  }
}

First we get any values we need from the current node. Since XML files are pure text, we cannot simply grab ints or floats from the file. TinyXML has functions with which you can pass in the value you want to be set and the attribute name. For example:

e->Attribute("x", &x);

This sets the variable x to the value contained within attribute "x". Next comes the creation of a GameObject * class using the factory.

GameObject* pGameObject = TheGameObjectFactory::Instance()->create(e->Attribute("type"));

We pass in the value from the type attribute and use that to create the correct object from the factory. After this we must use the load function of GameObject to set our desired values using the values loaded from the XML file.

pGameObject->load(new LoaderParams(x,y,width,height,textureID,numFrames,callbackID));

And finally we push pGameObject into the pObjects array, which is actually a pointer to the current state's object vector.

pObjects->push_back(pGameObject);
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.221.89.18