The Hero class

The Hero class will inherit from GameObject and will be responsible for handling the state machine of the hero and collision response, among a few other things. The constructor for the Hero class looks something like this:

Hero::Hero()
{
  type_ = E_GAME_OBJECT_HERO;
  state_ = E_HERO_STATE_NONE;
  is_on_ground_ = false;
  current_ground_height_ = 0.0f;
  platform_below_ = NULL;
}

We start off by setting the type_ of this object to E_GAME_OBJECT_HERO. We then initialize the member variables. The state_ variable (like you saw in Chapter 5, Let's Get Physical!) is the key to maintaining the state machine for the hero's behavior. We also have an is_on_ground_ flag that indicates when the hero is standing on a surface, which could be either a brick or a moving platform. So, we can set the appropriate state accordingly. We also have a current_ground_height_ variable that measures the height the hero has managed to reach and is at currently. This increases as the hero moves higher from one platform to another and reduces when he falls down. Lastly, we maintain a reference to a Platform object.

The various states that the player can have are enumerated by enum EHeroState in GameGlobals.h as follows:

enum EHeroState
{
 E_HERO_STATE_NONE = 0,
 E_HERO_STATE_IDLE,
 E_HERO_STATE_WALKING,
 E_HERO_STATE_JUMPING,
 E_HERO_STATE_SWINGING,
 E_HERO_STATE_DYING,
 E_HERO_STATE_WINNING,
};

The state machine in this class is identical to the Clown class you saw in Chapter 5, Let's Get Physical!. So, I will skip going over the details of the SetState function and each subsequent state function. We saw how the collision detection works in the previous sections and left the collision response for later. Now, it is time to define how the hero will respond to collisions. So, let's define the CollisionResponse function in Hero.cpp:

void Hero::CollisionResponse(int tile_col, int tile_row, 
  ECollisionType collision_type)
{
 switch(collision_type)
 {
 case E_COLLISION_TOP:
  // stop moving
  speed_.y = 0;
  // collision occured above AABB...reposition the AABB 
    one pixel below the collided tile
  aabb_.origin.y = GET_Y_FOR_ROW(tile_row + 1, 
    game_world_->GetRows()) - aabb_.size.height - 1;
  // tell the game world to remove this brick
  game_world_->RemoveBrick(tile_col, tile_row);
  break;
 case E_COLLISION_BOTTOM:
  // stop moving
  speed_.y = 0;
  // collision occured below AABB...reposition the 
    AABB one pixel above the collided tile
  aabb_.origin.y = GET_Y_FOR_ROW(
    tile_row, game_world_->GetRows()) + 1;
  // hero has landed on a brick
  SetIsOnGround(true);
  break;
 case E_COLLISION_LEFT:
  // stop moving
  speed_.x = 0;
  // collision occured to the left AABB...reposition 
    the AABB one pixel after the collided tile
  aabb_.origin.x = GET_X_FOR_COL(tile_col + 1) + 1;
  break;
 case E_COLLISION_RIGHT:
  // stop moving
  speed_.x = 0;
  // collision occured to the right AABB...reposition 
    the AABB one pixel before the collided tile
  aabb_.origin.x = GET_X_FOR_COL(tile_col) - 
    aabb_.size.width - 1;
  break;
 }
}

As you read earlier, the collision detection passes in the column and row of the collision tile, and also the type of collision that was detected. Based on this collision type, we have a little switch case that quite simply handles each type of collision separately. This comes in handy when you'd want to have different animations for when your character hits his head against the ceiling (E_COLLISION_TOP) and when it hits the chest against the wall (E_COLLISION_RIGHT).

For each type of collision, the first thing to do is to prevent the hero from continuing in the same state of motion, and that is why we set the speed in the respective direction to 0. We then proceed to correct the position of the AABB so that it is just one pixel away from the collision tile. This is done irrespective of the type of collision, because we would never want the hero to overlap any of the bricks.

An interesting thing is done for collisions that occur above the hero. We tell GameWorld to remove the brick at the given column and row. For collisions below the player, we call the SetIsOnGround function with true as the parameter.

The hero in Iceman seems like an elderly chap, but don't be fooled because he still has a lot of speed. Let's see how he works so energetically in the UpdateSpeed function of Hero.cpp:

void Hero::UpdateSpeed(bool must_go_left, 
  bool must_go_right, bool must_jump)
{
 // add gravity & clamp vertical velocity
 speed_.y += GRAVITY;
 speed_.y = (speed_.y < MAX_VELOCITY_Y) ? 
   MAX_VELOCITY_Y : speed_.y;

 // is the hero above a platform
 if(platform_below_)
 {
  // stop falling due to gravity
  speed_.y = 0;
  // move the hero along with the platform he's standing on
  aabb_.origin.x += platform_below_->GetSpeed().x;
  aabb_.origin.y = platform_below_->GetAABB().getMaxY();
 }

 // set speed accordingly if the hero must jump
 if(must_jump && is_on_ground_)
 {
  speed_.y = TILE_SIZE * 0.75f;
 }

 // increase/decrease the horizontal speed based on the 
   button pressed
 if(must_go_left)
 {
  speed_.x -= HERO_MOVEMENT_FORCE;
  speed_.x = (speed_.x < -MAX_VELOCITY_X) ? 
    -MAX_VELOCITY_X : speed_.x;
  setFlipX(true);
 }
 if(must_go_right)
 {
  speed_.x += HERO_MOVEMENT_FORCE;
  speed_.x = (speed_.x > MAX_VELOCITY_X) ? 
    MAX_VELOCITY_X : speed_.x;
  setFlipX(false);
 }

 // gradually come to a halt if no button is pressed
 if(!must_go_left && !must_go_right)
 {
  speed_.x -= speed_.x / 5;
 }

 // change from idle to walking & vice versa based on 
   horizontal velocity
 if(fabs(speed_.x) > 0.5f)
 {
  SetState(E_HERO_STATE_WALKING);
 }
 else if(state_ == E_HERO_STATE_WALKING)
 {
  SetState(E_HERO_STATE_IDLE);
 }
}

This function is called from the Update function of the same class and is passed the flags that indicate whether the player has pressed the left, right, or jump buttons on the screen. We begin this function by first adding the force of gravity to the y component of speed_ and clamp it to a maximum value.

Before we react to the user input flags, we must first check to see whether the hero is standing above a platform. If he is, then most certainly he shouldn't be falling. Also, he should now move with the platform beneath him so we add the platform's horizontal speed to aabb_. We also set the y component of the aabb_ to be right above the platform.

With that out of the way, we can react to the respective user input flags. If jump is pressed, we simply add a predefined value to the y component of speed_, which will send the hero shooting upwards. Similarly, if the left or right buttons are pressed, we subtract or add a predefined force to the x component of speed_. Finally, we flip the hero so that he is facing the same direction as he is moving towards.

We also have a condition when the player releases the buttons. The hero must now come to a stop and that is exactly what the next condition does. Notice how the x component is gradually reduced; this is to avoid the hero coming to a sudden halt. The last condition checks the x component of speed_ to set the appropriate walking or idle state.

Let's now focus our attention on the Update function of the Hero.cpp file:

void Hero::Update(bool must_go_left, bool must_go_right, 
  bool must_jump, bool must_swing)
{
 // let the hero die in peace
 if(state_ == E_HERO_STATE_DYING)
  return;

 // update speed based on user input
 UpdateSpeed(must_go_left, must_go_right, must_jump);

 // this enables the hero to leave from the left edge and 
   reappear from the right edge
 if(aabb_.origin.x < TILE_SIZE)
 {
  aabb_.origin.x = SCREEN_SIZE.width - 
    TILE_SIZE - aabb_.size.width;
 }
 if(aabb_.origin.x + aabb_.size.width > 
   SCREEN_SIZE.width - TILE_SIZE)
 {
  aabb_.origin.x = TILE_SIZE;
 }

 // update the AABB
 GameObject::Update();
 // check for collisions
 game_world_->CheckCollisions(this);
 game_world_->CheckHeroEnemyCollisions();
 game_world_->CheckHeroPlatformCollisions();

 setPosition(ccp(aabb_.origin.x + aabb_.size.width * 0.5f, 
   aabb_.origin.y + m_obContentSize.height * 0.5f));

 // check if the hero should swing
 if(must_swing)
 {
  SetState(E_HERO_STATE_SWINGING);
 }
 else if(state_ == E_HERO_STATE_SWINGING)
 {
  SetState(E_HERO_STATE_IDLE);
 }

 // check if the hero could be falling
 if(fabs(speed_.y) > 0.1f)
 {
  SetIsOnGround(false);
 }
}

The hero's Update function is called from the main Update loop of GameWorld and is passed four flags that basically indicate which onscreen buttons the player has currently pressed. We begin this function by checking whether the hero is in the dying state and quietly return from there if this condition is met. We then call the UpdateSpeed function, followed by a conditional that ensures the hero reappears at one end of the screen on exiting from the other end.

We now call the Update function of GameObject, which simply updates the aabb_ with the speed_. With this updated aabb_, we now call the CheckCollisions function from GameWorld, which internally calls the CheckVerticalCollisions and CheckHorizontalCollisions functions that you saw earlier. In addition to this, we also ask GameWorld to check for collisions with the hero and the enemies and the platforms.

Once the collision detection and response has completed, we set the corrected position of the hero. Now, if the user has pressed the swing button, we set the appropriate swinging state. Finally, we check to see if the vertical speed is greater than a minimum value. This indicates that the player is airborne so we call the SetIsOnGround function, passing in false as the parameter.

Now that we have the collision response and the Update functions out of the way, all we have left is the SetIsOnGround function and we're done coding our zesty old chap.

Let's look at the following code:

void Hero::SetIsOnGround(bool is_on_ground)
{
 /// only accept a change
 if(is_on_ground_ == is_on_ground)
  return;

 is_on_ground_ = is_on_ground;

 if(is_on_ground_)
 {
  // save the height the hero is at currently
  current_ground_height_ = aabb_.origin.y;
  SetState(E_HERO_STATE_IDLE);
 }
 else
 {
  // going up means jumping...for now
  if(speed_.y > 0)
  {
   SetState(E_HERO_STATE_JUMPING);
  }
 }
}

In the SetIsOnGround function, we proceed only if there is a change in this information. If the hero has indeed landed on ground, we update the current_ground_height_ variable to whatever height the bottom edge of AABB is at currently before setting the hero to idle. However, if the hero is not on the ground and the y component of speed_ is positive, it means that the hero is jumping and we must set the appropriate state. With that, we are done with class Hero. Let's now quickly define the Enemy class.

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

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