The Penguin class

The Penguin class publicly inherits from CCSprite and it is similar in terms of structure to the GameObject class you saw in the previous chapter. It possesses a b2Body object along with functions that update its position based on its body. The body of the penguin will have a circle-shaped fixture applied to it. We will skip all of that boilerplate code and discuss the behavior of the penguin.

Penguin behavior

Description

Awake

The penguin will be asleep at the start of the game. The player will have to touch the screen to wake him up.

Diving

This is true as long as the player is tapping the screen. A downward force is constantly applied to the penguin so long as he is diving.

Flying

This is true when the penguin is airborne and false when he is sliding on the surface of the hill.

Perfect slide

When the penguin launches off a slope above a certain velocity, it's a perfect slide.

Fever

The penguin enters into a trance like fever when he does three perfect slides in a row and leaves the fever mode when he has a bad landing.

Leaping

The penguin enters this state when he reaches the end of a particular hill.

It leaves this state when it approaches the start of the next hill.

Let's now write the functions that define the behavior of the penguin based on the preceding states, starting with the SetIsAwake function from the Penguin.cpp file:

void Penguin::SetIsAwake(bool is_awake)
{
  // activate the body & apply impulse in the top-right direction
  if(is_awake)
  {
    is_awake_ = true;
    body_->SetActive(true);
    body_->ApplyLinearImpulse(b2Vec2(0.1f, 0.2f), 
      body_->GetWorldCenter());
  }
  // deactivate the body
  else
  {
    is_awake_ = false;
    body_->SetActive(false);
  }
}

As you can read from the table, the penguin is initially asleep and hence the is_awake_ flag is initialized to false. However, when the player touches the screen for the first time, this function is called with a value of true passed in. In that case, we activate the physics body and apply an impulse such that the penguin is pushed in the upright direction. Bodies that are not active have no physics processing; so the ApplyImpulse function would have been without effect if we had not activated the penguin's body.

Conversely, when SetIsAwake is called with the false argument, we merely deactivate the physics body. Let's now look at the SetIsDiving function of Penguin.cpp:

void Penguin::SetIsDiving(bool is_diving)
{
  if(is_diving_ == is_diving)
    return;

  is_diving_ = is_diving;

  // set sprite for the respective state
  if(is_diving_)
  {
    initWithFile("penguin_3.png");
  }
  else
  {
    initWithFile("penguin_2.png");
  }
}

This function just toggles the is_diving_ flag and resets the sprite for the penguin. However, the real magic happens in the update loop, but more on that later. For now, we'll proceed to the SetIsFlying function of Penguin.cpp:

void Penguin::SetIsFlying(bool is_flying)
{
  if(is_flying_ == is_flying)
    return;

  is_flying_ = is_flying;

  // if penguin has taken off, check for a perfect slide
  if(is_flying_ && !is_leaping_)
  {
    // if take off velocity is above certain threshold, 
      its a perfect slide
    if(body_->GetLinearVelocity().Length() >= 
      PERFECT_TAKE_OFF_VELOCITY)
    {
      OnPerfectSlide();
    }
  }
}

This function is called every tick and the parameter passed in, and it informs us whether the penguin is currently on the ground or airborne based on whether or not a contact exists between the penguin and the terrain.

The sole purpose of this function is to determine whether the penguin has managed to pull off a perfect slide. To do that, we simply check what the magnitude of the velocity vector is when the penguin enters the flying stage, that is, when it has just taken off. If this value is greater than a predetermined threshold (PERFECT_TAKE_OFF_VELOCITY), we can safely say that the slide was carried out with perfection.

Let's now define the way in which we can reward the player for a perfect slide in the OnPerfectSlide function of Penguin.cpp:

void Penguin::OnPerfectSlide()
{
  // increment counters
  ++ num_perfect_slides_;
  ++ total_perfect_slides_;

  // 3 slides in a row activate fever mode
  if(num_perfect_slides_ == 3)
  {
    StartFever();
  }

  // a perfect slide deserves some extra time
  int time = 3 * (is_feverish_ ? 2 : 1);
  game_world_->AddTime(time);

  // inform player of the perfect slide
  char buf[16] = {0};
  sprintf(buf, "Perfect +%ds", time);
  game_world_->ShowMessage(buf);
}

At the start of the function, we increment the num_perfect_slides_ and total_perfect_slides_ counters that stand for the number of consecutive perfect slides and the total number of perfect slides respectively. The next if condition simply turns the fever mode on. The fever mode is visually represented by a trail of particles behind the penguin.

The code after that deals with adding time to the gameplay. Where did this concept of time-keeping come from? In this game, players have a limited amount of time (in seconds) to get the penguin as far as they can. The only way to increase this playing time is to perform perfect slides and to leap off from one hill to the next. On a perfect slide, players are rewarded 3 seconds and on leaping off from one hill to the next, players are rewarded 5 seconds. The best thing about fever mode is that the player is awarded with double time as long as the penguin is in fever mode.

The last bit of code calls the ShowMessage function from the GameWorld class that accepts a char array as input and displays the text within the char array with some gratuitous animations for entry and exit. We're almost done with the behavior of our little penguin, except for leaping, which we will define in the StartLeap and FinishLeap functions of Penguin.cpp:

void Penguin::StartLeap()
{
  if(is_leaping_)
    return;

  // successfully conquered a hill...reward player with extra time
  is_leaping_ = true;
  game_world_->AddTime(5 * (is_feverish_ ? 2 : 1));
  // no gravity so the penguin can be transported weightlessly to the next hill
  body_->SetGravityScale(0.0f);
}

void Penguin::FinishLeap()
{
  if(!is_leaping_)
    return;

  is_leaping_ = false;
  // next hill has arrived, turn gravity back on
  body_->SetGravityScale(1.0f);
  is_diving_ = false;
}

In the StartLeap function, we simply tell GameWorld to award the player 5 seconds of time and to double that if the penguin is feverish. Along with that, we also set the gravity scale on the penguin's body to 0. If you remember from the previous chapter, this simply means that the said physics body will not be affected by gravity. The reason why we're doing this here is so that we can effortlessly lift the penguin high up so that the player is ready to take on the next hill. You will see that code in action when we define the updating functions of the Penguin class. Conversely, in the FinishLeap function, we finish a leap when the penguin is close enough to the next set of hills. Hence, we set the gravity scale to its default value.

Updating the penguin

Now that we've defined most of the penguin's behavior, it's time to tie it all together with the update logic. We will split the update logic into two main methods: UpdatePhysics and UpdateNode. Here is the code for the UpdatePhysics function of Penguin.cpp:

void Penguin::UpdatePhysics()
{
  if(is_diving_)
  {
    // if penguin is asleep, awaken him and cancel the dive
    if(!is_awake_)
    {
      SetIsAwake(true);
      is_diving_ = false;
    }
    // else apply a downward force provided penguin isn't leaping
    else if(!is_leaping_)
    {
      body_->ApplyForceToCenter(b2Vec2(0.5f, -2.0f));
    }
  }

  // restrict velocity between minimum & maximum
  b2Vec2 vel = body_->GetLinearVelocity();
  vel.x = vel.x < MIN_PENGUIN_VELOCITY_X ? MIN_PENGUIN_VELOCITY_X : 
    (vel.x > MAX_PENGUIN_VELOCITY_X ? MAX_PENGUIN_VELOCITY_X : vel.x);
  vel.y = vel.y < MIN_PENGUIN_VELOCITY_Y ? MIN_PENGUIN_VELOCITY_Y : 
    (vel.y > MAX_PENGUIN_VELOCITY_Y ? MAX_PENGUIN_VELOCITY_Y : vel.y);
  body_->SetLinearVelocity(vel);

  UpdateLeap();
}

This function begins by checking if the player wants the penguin to dive. Remember that this flag is set in the SetIsDiving function when the player taps the screen. Within this condition, we must first check if the penguin is awake or not. At the start of the game, the penguin is asleep, so we wake him up and reset the is_diving_ flag. In the else if condition, we check if the penguin is not currently leaping. Then, we apply a downward force causing the penguin to lunge towards the hill.

Outside the conditional, we ensure that the velocity of the penguin stays within the limits defined by the four variables, that is, MIN_PENGUIN_VELOCITY_X, MIN_PENGUIN_VELOCITY_Y, MAX_PENGUIN_VELOCITY_X, and MAX_PENGUIN_VELOCITY_Y. You can find these variables defined in GameGlobals.h.

To finish off this function, we make a call to the UpdateLeap function of Penguin.cpp:

void Penguin::UpdateLeap()
{
  if(!is_leaping_)
    return;

  b2Vec2 vel = body_->GetLinearVelocity();
  b2Vec2 new_vel = vel;
  // increase the velocity
  new_vel.x += (MAX_PENGUIN_VELOCITY_X - new_vel.x) / 15.0f;
  new_vel.y += (MAX_PENGUIN_VELOCITY_Y - new_vel.y) / 15.0f;
  // ensure velocity doesn't exceed maximum
  new_vel.x = (new_vel.x > MAX_PENGUIN_VELOCITY_X
    ) ? MAX_PENGUIN_VELOCITY_X : new_vel.x;
  new_vel.y = (new_vel.y > MAX_PENGUIN_VELOCITY_Y
    ) ? MAX_PENGUIN_VELOCITY_Y : new_vel.y;
  body_->SetLinearVelocity(new_vel);
}

The UpdateLeap function must ensure that the penguin moves from one hill to the next. To do this, we increase the x and y components of the penguin's linear velocity every time this function is called. We also ensure that we stay within the velocity limits. This will simply cause the penguin to soar high above the hills, thereby ensuring it doesn't fall off the cliff and into the terrible abyss. Why don't you see what's below the hills?

We will now discuss the UpdateNode function of Penguin.cpp:

void Penguin::UpdateNode()
{
  // set node position based on body
  CCPoint previous_position = m_obPosition;
  setPositionX(WORLD_TO_SCREEN(body_->GetPosition().x));
  setPositionY(WORLD_TO_SCREEN(body_->GetPosition().y));
  
  // set rotation based on body
  float angle = CC_RADIANS_TO_DEGREES(-1 * ccpToAngle(
    ccpSub(m_obPosition, previous_position)));
  setRotation(angle);

  // fetch list of contacts
  b2Contact* contact = game_world_->GetWorld()->GetContactList();
  // if contact exists, penguin has landed
  if(contact)
  {
    SetIsFlying(false);
  }
  // else penguin is airborne
  else
  {
    SetIsFlying(true);
  }

  // update the trail if penguin is feverish
  if(is_feverish_)
  {
    trail_->setPosition(m_obPosition);
  }
}

You must recognize the first steps within the function where we simply set the position and rotation for the penguin's node according to its body. What follows next is another way to check for collisions, where we simply iterate over the list of the contacts that the Box2D world has recorded. Since we have just one dynamic body (the penguin) and one static body (the hill), it is safe for us to assume that the penguin has landed on the hill whenever we find a contact in the list.

Now, I'm sure you're wondering what's wrong with using contact listeners like we did in the previous chapter. The BeginContact and EndContact callbacks are called very frequently, owing to the curved shape of the surface of the hill. To see exactly what I mean, you should implement the BeginContact and EndContact functions in GameWorld.cpp. Put in a call to the CCLOG function in both the callbacks to see just how often they're fired.

Once we've detected a collision, we react to it by calling the SetIsFlying function. The last bit of code there is to reset the position of the trail so long as the penguin is feverish. At this point, we have defined the two major entities of the game: the Terrain class and the Penguin class.

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

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