Defining the XML file for a level

We could just as easily have used a single XML file that gives us information on the enemy's path as well as the waves that will comprise the level. However, since we already have some experience on Tiled, we will use it to create the enemy path and use our XML file to store details about the waves for a given level.

As such, the XML file for a typical level contains information about waves in which enemies will attack the field and will look like this:

<Level cash="150">
  <Wave spawn_delay="2" enemy_list="0,0,0,0,0,0,0,0" />
  <Wave spawn_delay="3" enemy_list="1,0,1,0,1,0,1,0" />
  <Wave spawn_delay="1.5" enemy_list="0,0,1,0,0,1,0,0,1" />
  .
  .
  .
</Level>

The root element of the document titled Level contains an attribute to describe how much cash the player starts out with. We then have a number of Wave tags, each describing a single wave. Each Wave tag contains two attributes: spawn_delay and enemy_list. The spawn_delay attribute describes the number of seconds the game will wait before spawning the next enemy in the list. The enemy_list attribute describes a list of enemies comprising this wave. The values for the enemy_list attribute corresponds directly to the enemy data we defined in the Defining the properties of the enemy section. Thus, a value of 0 corresponds to the enemy described by the first tag in enemy_data.xml.

A given level's XML file is parsed when the GameWorld class is created and added to the scene. But before we look at the code where this happens, we first need to be able to represent a wave. Hence, we define a struct titled Wave in GameWorld.h as follows:

struct Wave
{
  int num_enemies_;
  int num_enemies_spawned_;
  int num_enemies_walking_;
  vector<Enemy*> enemies_;
  float spawn_delay_;

  Wave()
  {
    num_enemies_ = 0;
    num_enemies_spawned_ = 0;
    num_enemies_walking_ = 0;
    enemies_.clear();
    spawn_delay_ = 0.0f;
  }
};

The struct tiled Wave contains a vector of pointers to Enemy objects and a variable to represent the interval in which the enemies have to be sent out into the field. In addition to this, the struct tiled Wave also maintains various counters to track how many enemies have spawned and how many of them are still walking (ergo, not dead!).

We can now look at the CreateWaves function from GameWorld.cpp, where we parse the level file to create a vector of Wave objects for the given level:

void GameWorld::CreateWaves()
{
  // generate level filename
  char buf[128] = {0};
  sprintf(buf, "level_%02d.xml", GameGlobals::level_number_ + 1);

  // read file
  unsigned long size;
  char* data = (char*)CCFileUtils::sharedFileUtils()->
    getFileData(buf, "rb", &size);

  // parse file
  tinyxml2::XMLDocument xml_document;
  tinyxml2::XMLError xml_result = xml_document.Parse(data, size);

  CC_SAFE_DELETE(data);

  // print the error if parsing was unsuccessful
  if(xml_result != tinyxml2::XML_SUCCESS)
  {
    CCLOGERROR("Error:%d while reading %s", xml_result, buf);
    return;
  }

  tinyxml2::XMLNode* level_node = xml_document.FirstChild();
  // save the initial cash for this level
  cash_ = level_node->ToElement()->IntAttribute("cash");

  tinyxml2::XMLElement* wave_element = NULL;
  // loop through each Wave tag
  for(tinyxml2::XMLNode* wave_node = level_node->FirstChild(); 
    wave_node != NULL; wave_node = wave_node->NextSibling())
  {
    wave_element = wave_node->ToElement();
    // get list of enemy indices
    vector<int> enemy_list = GameGlobals::GetIntListFromString(
      string(wave_element->Attribute("enemy_list")));

    // createa a new Wave object
    Wave* wave = new Wave();
    // save the spawn delay & list of enemies for this wave
    wave->num_enemies_ = enemy_list.size();
    wave->spawn_delay_ = wave_element->FloatAttribute("spawn_delay");
    // create all enemies in advance
    for(int i = 0; i < wave->num_enemies_; ++i)
    {
      Enemy* enemy = Enemy::create(this, enemy_list[i]);
      wave->enemies_.push_back(enemy);
      addChild(enemy, E_LAYER_ENEMY);
    }

    ++ num_waves_;
    waves_.push_back(wave);
  }
}

After all the tinyxml2 jargon, the function proceeds to save the initial bounty or cash that the player has to spend. This, of course, is part of the level's XML so a level designer can leverage it. This is followed by a for loop that creates a Wave object for every Wave tag in this level.

A vector of integers named enemy_list is generated from the comma-separated string of values using a helper function from GameGlobals (as you've seen in previous chapters). The size of this list describes the number of enemies that this wave will spawn. The interval at which they will be spawned is parsed as well.

Finally, an object of type Enemy is created and pushed into the enemies_ vector of Wave, before being added onto GameWorld. Since this is done for each wave for a given level, all the enemies for all the waves are added onto the node graph right at the start of the level. However, the Enemy class takes care that it does not get unnecessarily processed. We'll take a closer look in a later section when we define the enemy behavior.

For now, let's take a look at how the enemy path is designed in Tiled.

..................Content has been hidden....................

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