The Enemy class

The Enemy class inherits from CCSprite just like the Tower class and will contain all the properties of the struct named EnemyData. The Enemy class defines the functionalities to make the enemy walk along the path dictated by the level, do damage if it reaches the end of the path, take damage from a tower, and die. Let's take a look at the class declaration in the Enemy.h file:

class Enemy: public CCSprite
{
public:
  Enemy();
  virtual ~Enemy();
 
  static Enemy* create(GameWorld* game_world, int type);
 
  virtual bool init(GameWorld* game_world, int type);
  // copy data within the enemy library inside GameGlobals
  virtual void SetEnemyProperties();
 
  // create & update the progress bar showing health left
  void CreateHealthBar();
  void UpdateHealthBar();
 
  // basic enemy behaviour functions
  void StartWalking();
  void FinishWalking();
  void DoDamage();
  void TakeDamage(CCObject* object);
  void Die();
  void TakeSpeedDamage(float speed_damage, 
    float speed_damage_duration);
  void ResetSpeed(float dt);
 
  // accessors and mutators
  inline void SetAnimationName(const char* animation_name) {
    animation_name_ = animation_name; }
 
  inline void SetHealth(int health) { health_ = health; }
  inline int GetHealth() { return health_; }
 
  inline void SetArmor(int armor) { armor_ = armor; }
  inline int GetArmor() { return armor_; }
 
  inline void SetMagicResistance(int magic_resistance) { 
    magic_resistance_ = magic_resistance; }
  inline int GetMagicResistance() { return magic_resistance_; }
 
  inline void SetSpeed(float speed) { speed_ = speed; }
  inline float GetSpeed() { return speed_; }
 
  inline void SetDamage(int damage) { damage_ = damage; }
  inline int GetDamage() { return damage_; }
 
  inline void SetReward(int reward) { reward_ = reward; }
  inline int GetReward() { return reward_; }
 
  inline float GetRadius() { return radius_; }
  inline bool GetHasDied() { return has_died_; }
  inline bool GetIsSlowed() { return is_slowed_; }
 
  inline void SetWalkPoints(int num_walk_points, 
    vector<CCPoint> walk_points) { num_walk_points_ = num_walk_points; 
 walk_points_ = walk_points; }
 
protected:
  GameWorld* game_world_;
 
  // properties that define the enemy
  // these take values straight from the EnemyData struct
  int type_;
  const char* animation_name_;
  int health_;
  int armor_;
  int magic_resistance_;
  float speed_;
  int damage_;
  int reward_;
 
  // more properties that define the enemy
  float radius_;
  int health_left_;
  bool has_died_;
  bool is_slowed_;
  int num_walk_points_;
  int curr_walk_point_;
  vector<CCPoint> walk_points_;
  
  // the progress bar showing health left
  CCProgressTimer* health_bar_;
};

The first half of this class declares the functions that will describe the enemy's behavior. In the second half of the class, you will find the variables that are copied from the EnemyData structure.

You can also see a vector of CCPoint named walk_points_. This vector will copy all the positions stored inside the GameWorld class' enemy_walk_points_ vector we created while parsing the level's TMX file. We also have a CCProgressTimer object that we will use to show the enemy's health.

Without further ado, let's begin defining the enemy's behavior with the init function from Enemy.cpp:

bool Enemy::init(GameWorld* game_world, int type)
{
  if(!CCSprite::init())
  {
    return false;
  }
  // save reference to GameWorld & type of enemy
  game_world_ = game_world;
  type_ = type;
  // set the enemy's properties
  SetEnemyProperties();
  // fetch the first frame of animation so we know 
    the size of this enemy
  CCAnimation* animation = CCAnimationCache::sharedAnimationCache()->
    animationByName(animation_name_);
  radius_ = ((CCAnimationFrame*)animation->getFrames()->
    objectAtIndex(0))->getSpriteFrame()->getOriginalSize().width/2;
  // hide the enemy till it starts walking
  setVisible(false);

  CreateHealthBar();

  return true;
}

The init function is passed a reference to GameWorld and an integer variable named type. The wave feeds the value of the variable type into its enemy_list_ vector when GameWorld parses the level's XML file. We will use this type variable as an index while copying the properties from GameGlobals to the SetEnemyProperties function.

The init function also saves the size of this enemy into a radius_ variable to be used when being targeted by a tower. Since all enemies from all waves are added to the node graph right at the start of the level, we initially hide the enemies to avoid unnecessary rendering. We finally call the CreateHealthBar function to wind up the init function.

Since we haven't really covered the use of a CCProgressTimer object in previous chapters, let's take a look at the CreateHealthBar function of Enemy.cpp to see how this is done:

void Enemy::CreateHealthBar(){
  CCPoint position = ccp(radius_, radius_ * 1.75f);
  // sprite behind the progress bar
  CCSprite* red_bar = CCSprite::create("red_bar.png");
  red_bar->setPosition(position);
  addChild(red_bar);
  // create a horizontal progress bar
  health_bar_ = CCProgressTimer::create(CCSprite::create("green_bar.png"));
  health_bar_->setType(kCCProgressTimerTypeBar);
  // progress bar takes values from 0 to 100
  health_bar_->setPercentage( (float)health_left_ / (float)health_ * 100 );
  health_bar_->setMidpoint(ccp(0, 1));
  health_bar_->setBarChangeRate(ccp(1, 0));
  health_bar_->setPosition(position);
  addChild(health_bar_);
}

We begin the function by calculating the position where this progress timer will be added. We then go ahead and add a background sprite to give us an illustration of the progress timer filling or emptying.

We then create a CCProgressTimer object, passing in a CCSprite, position it, and add it to the enemy. We set the type of this progress timer as kCCProgressTimerTypeBar and set the percentage of the progress timer that must be filled, where 0 is empty, 50 is half full, and 100 is completely full.

The next two lines are very important to understand as they define the behavior of a progress timer. The bar change rate, in the context of a bar type progress timer, controls the direction in which the progress timer will expand. For a vertically expanding progress timer, you'd have to set the bar change rate as (0, 1), and for a horizontally expanding one as (1, 0).

Once the bar change rate has decided whether the progress timer expands vertically or horizontally, the midpoint decides whether it should expand up-to-down or down-to-up for a vertically expanding bar, and left-to-right or right-to-left for a horizontally expanding bar. So if you want the progress timer to expand down-to-up your midpoint would have to be (1, 0).

To summarize the way in which these variables control the behavior of a progress timer, take a look at the following table:

Behavior

Bar change rate

Midpoint

Vertical (down = 0 percent to up = 100 percent)

(0, 1)

(0, 1)

Vertical (up = 0 percent to down = 100 percent)

(0, 1)

(1, 0)

Horizontal (left = 0 percent to right = 100 percent)

(1, 0)

(0, 1)

Horizontal (right = 0 percent to left = 100 percent)

(1, 0)

(0, 1)

The CreateHealthBar function then positions the progress timer and adds it to the enemy. Let's now look at the movement logic of the enemy in the StartWalking function of Enemy.cpp:

void Enemy::StartWalking()
{
  // show the enemy when it starts walking
  setVisible(true);
  // position the enemy at the first walking point
  setPosition(walk_points_[curr_walk_point_]);
  // calculate duration in terms of time taken to walk a single tile
  float duration = speed_ * ENEMY_MOVE_DURATION * (ccpDistance(
    walk_points_[curr_walk_point_ + 1], 
 walk_points_[curr_walk_point_]) / TILE_SIZE);

  // walk to the subsequent walk point
  CCActionInterval* walk = CCMoveTo::create(duration, 
    walk_points_[curr_walk_point_ + 1]);
  CCActionInstant* finish_walking = CCCallFunc::create(
    this, callfunc_selector(Enemy::FinishWalking));
  CCActionInterval* walk_sequence = CCSequence::createWithTwoActions(
    walk, finish_walking);
  // create a speed action to control the walking speed
  CCAction* walk_action = CCSpeed::create(walk_sequence, 1.0f);
  walk_action->setTag(ENEMY_MOVE_ACTION_TAG);
  runAction(walk_action);

  if(getActionByTag(ENEMY_ANIMATE_ACTION_TAG) == NULL)
  {
    CCActionInterval* animation = CCAnimate::create(
   CCAnimationCache::sharedAnimationCache()->
   animationByName(animation_name_));
    animation->setTag(ENEMY_ANIMATE_ACTION_TAG);
    runAction(animation);
  }
}

Remember how we hid the enemy in the init function? We set its visibility to true and position it at the first walk point only when it is commanded to start walking. This vector of walk_points_ is filled when the enemy is spawned by GameWorld.

We must now calculate the amount of time the enemy will take, walking at its current speed (speed_), to reach the next walk point. We create a simple CCMoveTo action and sequence it with CCCallFunc to call the FinishWalking function.

Here is the interesting part: we use CCSpeed to control the execution speed of the walk_sequence action interval. Thus, we pass walk_sequence into the CCSpeed::create function along with the default speed at which we want this action to run. Now, by calling the setSpeed function of the CCSpeed class, we can slow down or speed up the action passed to it. We also set the tag of this CCSpeed action to ENEMY_MOVE_ACTION_TAG so that we can fetch it later. Why would we need to control the speed of the enemy's movement? Well, because we have towers that can slow down enemies!

As you can see, we don't run really run the walk_sequence action (of type CCSequence) that we created, but instead run walk_action action (of type CCSpeed). This is because the CCSpeed action will internally step whatever action it is created with. Consequently, the CCSpeed action will end when its inner action ends.

With the movement logic figured out, we simply run a CCAnimate to animate this enemy and finish the StartWalking function. We shall now move to the function that gets called when the enemy reaches the destination walk point, that is, the FinishWalking function of Enemy.cpp:

void Enemy::FinishWalking()
{
  // can't stop walking if already dead
  if(has_died_)
  {
    return;
  }

  // move to the subsequent walk point
  ++ curr_walk_point_;
  if(curr_walk_point_ < num_walk_points_ - 1)
  {
    StartWalking();
  }
  // enemy has reached the pumpkin
  else
  {
    DoDamage();
  }
}

We first check if the enemy is still alive and return from the function if the enemy has died while walking from one walk point to the next. We then increment the current_walk_point_ counter and check whether there are any more walk points left.

If there is a subsequent walk point, we simply call the StartWalking function so that the enemy starts moving towards the next walk point. If there are no walk points left, it means the enemy has reached the pumpkin and we call the DoDamage function.

We have completed the movement logic for the Enemy class. Now we need to define the way in which the enemy damages the pumpkin, and the way in which the enemy takes damage from the towers. So let's begin with the DoDamage function of Enemy.cpp:

void Enemy::DoDamage()
{
  // inform GameWorld that damage must be done
  game_world_->EnemyAtTheGates(this);

  stopAllActions();
  // hide the enemy
  setVisible(false);
}

This is a very lazy enemy indeed. It uses CCMoveTo to walk and makes GameWorld do all the work. The enemy simply informs GameWorld that it has reached the pumpkin and passes in a reference to itself. It then hides itself, so no further processing is wasted on it.

Now, an enemy is damaged whenever a tower shoots a bullet or lightning bolt at it. This happens in the ShootBullet and ShootLightning functions (as we saw when defining the Tower class). These functions run a CCCallFuncO action to call the TakeDamage function of the Enemy class. So let's look at the way in which an enemy takes damage inside the TakeDamage function of Enemy.cpp:

void Enemy::TakeDamage(CCObject* object)
{
  // sometimes a dead enemy might get shot
  if(has_died_)
  {
    return;
  }

  Tower* tower = (Tower*)object;
  // calculate total damage taken by this enemy from a given tower
  float physical_damage = tower->GetPhysicalDamage() - armor_;
  float magical_damage = tower->GetMagicalDamage() - magic_resistance_;
  float total_damage = (physical_damage > 0 ? physical_damage : 0) + 
    (magical_damage > 0 ? magical_damage : 0);
  health_left_ -= total_damage;
  
  // slow the enemy if not already being slowed & 
    if the tower has speed damage
  if(is_slowed_ == false && tower->GetSpeedDamage() < 1.0f)
  {
    TakeSpeedDamage(tower->GetSpeedDamage(), 
   tower->GetSpeedDamageDuration());    
  }
  
  // check if enemy should die
  if(health_left_ <= 0)
  {
    Die();
  }

  // update the health bar
  health_bar_->setPercentage( (float)health_left_ / 
    (float)health_ * 100 );
}

This enemy cannot be damaged if it has already been killed. There may be times when multiple towers are simultaneously targeting this particular enemy. Then, the enemy might die by the first tower alone, and subsequent attacks must be ignored.

We now extract the Tower object passed to this function with the CCCallFuncO action that was run on this enemy object. We can then use this Tower object to calculate the total damage that this enemy must suffer. The total damage is the sum of all physical and magical damage levied upon this enemy, with the quantities of armor and magic resistance subtracted. We subtract this total damage from the enemy's remaining health.

Now, if the tower has the capability to slow the enemy, we must call the TakeSpeedDamage function, passing in the quantity of speed damage and the duration for which the enemy must be slowed. If the enemy has no health left, we must call the Die function. Finally, we update the progress timer used to display the enemy's health.

We saw two new functions in the TakeDamage function: TakeSpeedDamage and Die. Let's see how the enemy dies in the Die function of Enemy.cpp:

void Enemy::Die()
{
  // inform GameWorld that an enemy has died
  has_died_ = true;
  game_world_->EnemyDown(this);

  stopAllActions();
  runAction(CCSequence::createWithTwoActions(
    CCEaseBackIn::create(CCScaleTo::create(0.2f, 0.0f)), 
 CCHide::create()));
}

We first set the has_died_ flag and then inform the GameWorld that an enemy has died, passing in a pointer to this enemy's object. Then we must stop all actions running on this enemy and run one last action to animate this enemy's death and finally hide it.

Moving on to the TakeSpeedDamage function of Enemy.cpp:

void Enemy::TakeSpeedDamage(float speed_damage, 
  float speed_damage_duration)
{
  // reduce the walking speed
  is_slowed_ = true;
  CCSpeed* walk_action = (CCSpeed*)getActionByTag(
    ENEMY_MOVE_ACTION_TAG);
  if(walk_action != NULL)
  {
    walk_action->setSpeed(speed_damage);
    // walking speed must return back to normal after 
   certain duration
    scheduleOnce(schedule_selector(Enemy::ResetSpeed), 
   speed_damage_duration);
  }  
}

We begin by setting the is_slowed_ variable to true so that the enemy isn't slowed more than once at a time. We then fetch the CCSpeed action and call its setSpeed function. This manipulates the speed of its inner action, which in our case is the enemy's walking sequence. We also schedule a function to return the enemy's walking sequence to normal speed after the specified duration in the ResetSpeed function.

So far, we have defined the movement, take damage, and do damage behaviors of our enemy—thereby completing the Enemy class. We still have a decent amount of work left. We need to stitch everything together in our GameWorld class, implement a simple gesture recognition mechanism to spawn our towers, and discuss a special feature at the end!

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

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