The Tower class

Our Tower class will inherit from CCSprite and will contain all the properties that we saw in the TowerDataSet and TowerData structures. In addition to that, the Tower class will also possess the behavior of targeting and shooting enemies, as well as upgrading itself.

Let's take a look at how the Tower class is declared in Tower.h:

class Tower: public CCSprite
{
public:
  Tower();
  virtual ~Tower();

  static Tower* create(GameWorld* game_world, 
    int type, CCPoint position);

  virtual bool init(GameWorld* game_world, 
    int type, CCPoint position);
  // copy the data within the TowerDataSet library inside GameGlobals
  void SetTowerProperties();

  // update functions
  virtual void Update();
  void UpdateRotation();

  // functions that take care of upgradation & resale
  void Upgrade();
  void Sell();

  // basic tower behaviour
  void CheckForEnemies();
  void SetTarget(Enemy* enemy);
  void Shoot(float dt);
  void ShootBullet();
  void ShootLightning();
  
  // show the range for this tower
  void CreateRangeNode();
  void ShowRange();

  // accessors & mutators
  void SetSpriteName(const char* sprite_name);
  void SetIsRotating(bool is_rotating);

  inline void SetBulletName(const char* bullet_name) 
    { bullet_name_ = bullet_name; }

  inline void SetIsLightning(bool is_lightning) 
    { is_lightning_ = is_lightning; }
  inline bool GetIsLightning() { return is_lightning_; }

  inline void SetRange(float range) { range_ = range * TILE_SIZE; }
  inline float GetRange() { return range_; }

  inline void SetPhysicalDamage(float physical_damage) 
    { physical_damage_ = physical_damage; }
  inline float GetPhysicalDamage() { return physical_damage_; }

  inline void SetMagicalDamage(float magical_damage) 
    { magical_damage_ = magical_damage; }
  inline float GetMagicalDamage() { return magical_damage_; }

  inline void SetSpeedDamage(float speed_damage) 
    { speed_damage_ = speed_damage; }
  inline float GetSpeedDamage() { return speed_damage_; }

  inline void SetSpeedDamageDuration(
    float speed_damage_duration) { 
   speed_damage_duration_ = speed_damage_duration; }
  inline float GetSpeedDamageDuration() { 
    return speed_damage_duration_; }

  inline void SetFireRate(
    float fire_rate) { fire_rate_ = fire_rate; }
  inline float GetFireRate() { return fire_rate_; }

  inline void SetCost(int cost) { cost_ = cost; }
  inline int GetCost() { return cost_; }

  inline int GetType() { return type_; }
  inline int GetLevel() { return current_level_; }

protected:
  GameWorld* game_world_;

  // properties that define the tower
  // these take values straight from the TowerDataSet
    & TowerData structs
  int type_;
  const char* bullet_name_;
  bool is_lightning_;
  bool is_rotating_;
  float range_;
  float physical_damage_;
  float magical_damage_;
  float speed_damage_;
  float speed_damage_duration_;
  float fire_rate_;
  int cost_;

  // the level of upgrade the tower is currently at
  int current_level_;
  // the tower's current target
  Enemy* target_;

  // a sprite to represent the base for a rotating tower
  CCSprite* base_sprite_;
  // a node to draw the circular range for this tower
  CCDrawNode* range_node_;
};

The top half of the declaration of this class deals with the behavior of this tower. You can see the Upgrade and Sell functions that do exactly what their names suggest, followed by functions CheckForEnemies, SetTarget, and the shoot functions. You can also see a function that will create and show the range for this tower.

If you look at the bottom half of the class declaration, where all the member variables are declared, you will notice that they are identical to the member variables inside the TowerDataSet and TowerData structures. The SetTowerProperties function of the Tower class takes care of filling these member variables with values from GameGlobals, based on the type_ and current_level_ variables. Let's take a look at how this happens in Tower.cpp:

void Tower::SetTowerProperties()
{
  // tower properties are set from the TowerDataSet 
    & TowerData structs
  SetBulletName(GameGlobals::tower_data_sets_[
    type_]->bullet_name_);
  SetIsLightning(GameGlobals::tower_data_sets_[
    type_]->is_lightning_);
  SetIsRotating(GameGlobals::tower_data_sets_[
    type_]->is_rotating_);
  SetSpriteName(GameGlobals::tower_data_sets_[
    type_]->tower_data_[current_level_]->sprite_name_);
  SetRange(GameGlobals::tower_data_sets_[
    type_]->tower_data_[current_level_]->range_);
  SetPhysicalDamage(GameGlobals::tower_data_sets_[
    type_]->tower_data_[current_level_]->physical_damage_);
  SetMagicalDamage(GameGlobals::tower_data_sets_[
    type_]->tower_data_[current_level_]->magical_damage_);
  SetSpeedDamage(GameGlobals::tower_data_sets_[
    type_]->tower_data_[current_level_]->speed_damage_);
  SetSpeedDamageDuration(GameGlobals::tower_data_sets_[
    type_]->tower_data_[current_level_]->speed_damage_duration_);
  SetFireRate(GameGlobals::tower_data_sets_[
    type_]->tower_data_[current_level_]->fire_rate_);
  SetCost(GameGlobals::tower_data_sets_[
    type_]->tower_data_[current_level_]->cost_);
}

If you remember, each tower is represented by a TowerDataSet object inside the tower_data_sets_ vector. Hence, we use the type_ variable as an index to access all the properties of a particular tower stored inside tower_data_sets_.

Notice how the current_level_ variable is used to access the appropriate TowerData object. This structure makes it a no-brainer for us to implement the upgrades to our towers. We will simply increment the current_level_ variable and call the SetTowerProperties function to equip this tower with the subsequent upgrade. This is exactly what we do in the Upgrade function from Tower.cpp:

void Tower::Upgrade()
{
  // are there any upgrades left?
  if(current_level_ >= NUM_TOWER_UPGRADES - 1)
  {
    return;
  }

  // increment upgrade level and reset tower properties
  ++ current_level_;
  SetTowerProperties();
  // debit cash
  game_world_->UpdateCash(-cost_);

  // reset the range
  range_node_->removeFromParentAndCleanup(true);
  range_node_ = NULL;
  ShowRange();
}

At the top of the function, we ensure that the tower doesn't upgrade infinitely. The NUM_TOWER_UPGRADES constant is assigned a value of 3 in GameGlobals.h. Then we simply increment the current_level_ variable and call SetTowerProperties.

Since upgrades are not free, we must tell GameWorld exactly how much this particular upgrade has set the player back by calling the UpdateCash function. Finally, since an upgrade may increase the range of the tower, we remove the range_node_ and call the ShowRange function. This will recreate range_node_ and visually communicate to the player the increase in the tower's range after the upgrade.

Let's focus on the targeting functionality of a tower by defining the CheckForEnemies and SetTarget functions of the Tower class within Tower.cpp:

void Tower::CheckForEnemies()
{
  // only check the current wave for enemies
  Wave* curr_wave = game_world_->GetCurrentWave();
  if(curr_wave == NULL)
  {
    return;
  }

  // search for a target only when there isn't one already
  if(target_ == NULL)
  {
    // loop through each enemy in the current wave
    for(int i = 0; i < curr_wave->num_enemies_; ++i)
    {
      Enemy* curr_enemy = curr_wave->enemies_[i];
      // save this enemy as a target it if it still 
     alive and if it is within range
      if(curr_enemy->GetHasDied() == false && ccpDistance(
     m_obPosition, curr_enemy->getPosition()) <= (
  range_ + curr_enemy->GetRadius()))
      {
        SetTarget(curr_enemy);
        break;
      }
    }
  }
  
  // check if a target should still be considered
  if(target_ != NULL)
  {
    // a target is still valid if it is alive and 
   if it is within range
    if(target_->GetHasDied() == true || ccpDistance(
   m_obPosition, target_->getPosition()) > (
   range_ + target_->GetRadius()))
    {
      SetTarget(NULL);
    }
  }
}

The first thing this function does is query GameWorld for the wave that is currently on screen, and returns in the case that there is none. There might be a couple of occasions when the player might place a bunch of towers before the first wave has even started.

If the tower currently has no target, we must loop through each enemy in the current wave in an attempt to find if any are within the tower's range. We must also ensure that the tower doesn't target an enemy that is already dead. Once these two conditions are met, we save the enemy as this tower's target by passing a pointer to the enemy's object into the SetTarget function, before breaking from this loop.

In addition to finding enemies within range, the tower must also realize when enemies have escaped its range or have died and stop shooting at them. In the last part of the CheckForEnemies function, we check for conditions if an enemy has died or if it has walked out of the tower's range. If these conditions are met, we set the target to NULL.

Let's now look at the SetTarget function that is called from within the CheckForEnemies function in Tower.cpp:

void Tower::SetTarget(Enemy* enemy)
{
  target_ = enemy;

  if(target_ != NULL)
  {
    // shoot as soon as you get a target
    Shoot(0.0f);
    schedule(schedule_selector(Tower::Shoot), fire_rate_);
  }
  else
  {
    // stop shooting when you lose a target
    unschedule(schedule_selector(Tower::Shoot));
  }
}

The SetTarget function simply stores a reference to the enemy and either schedules or unschedules the Shoot function as required. Notice how the fire_rate_ variable is used as the interval for the schedule function. Things are about to get exciting now as we define the Shoot, ShootBullet, and ShootLightning functions.

The Shoot function simply checks the value of the is_lightning_ flag and calls either ShootBullet or ShootLightning. So, we're heading straight into the action with the ShootBullet function of Tower.cpp:

void Tower::ShootBullet()
{
  float bullet_move_duration = ccpDistance(m_obPosition, 
    target_->getPosition()) / TILE_SIZE * BULLET_MOVE_DURATION;

  // damage the enemy
  CCActionInterval* damage_enemy = CCSequence::createWithTwoActions(
    CCDelayTime::create(bullet_move_duration), CCCallFuncO::create(
 target_, callfuncO_selector(Enemy::TakeDamage), this));
  target_->runAction(damage_enemy);

  // create the bullet
  CCSprite* bullet = CCSprite::create(bullet_name_);
  bullet->setScale(0.0f);
  bullet->setPosition(m_obPosition);
  game_world_->addChild(bullet, E_LAYER_TOWER - 1);

  // animate the bullet
  CCActionInterval* scale_up = CCScaleTo::create(0.05f, 1.0f);
  bullet->runAction(scale_up);
  
  // move the bullet then remove it
  CCActionInterval* move = CCSequence::create(
    CCMoveTo::create(bullet_move_duration, target_->getPosition()),
    CCRemoveSelf::create(true), NULL);
  bullet->runAction(move);
}

The ShootBullet function begins by calculating how long this particular bullet must travel to reach its target. The constant BULLET_MOVE_DURATION is defined in GameGlobals.h with a value of 0.15f and signifies the amount of time a bullet will take to travel a single tile.

Then, we inform the enemy that it must take some damage from this tower by creating a sequence of CCDelayTime and CCCallFuncO. We pass this tower's object as the CCObject pointer as we're using the callfuncO_selector selector type. The delay is to ensure that the enemy doesn't get hurt before the bullet has reached it.

We now create a new CCSprite for the bullet, using the bullet_name_ variable as input, then add it to GameWorld and run a simple move-remove sequence on it. Next up is the function that shoots the bolt of lightning. First, let's take a detour to create a Lightning class that we can use inside the ShootLightning function.

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

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