On to the game world

We'll start by looking at the CreateGame function that is called from the init function when GameWorld is created:

void GameWorld::CreateGame()
{
  // player starts off with 30 seconds
  time_left_ = 30;

  // create & add the first set of hills at 0
  terrain_ = Terrain::create(world_, 0);
  addChild(terrain_, E_LAYER_FOREGROUND);
  
  // create & add the sky
  sky_ = Sky::create();
  addChild(sky_, E_LAYER_BACKGROUND);

  // create & add the penguin to the hills
  penguin_ = Penguin::create(this, "penguin_1.png");
  terrain_->addChild(penguin_, E_LAYER_FOREGROUND);

  CreateHUD();

  // enable touch and schedule two selectors; the seconds 
    ticker and the update
  setTouchEnabled(true);
  schedule(schedule_selector(GameWorld::Tick), 1.0f);
  scheduleUpdate();
}

We begin this function by initializing a variable called time_left_ to 30. This means that the player starts off with 30 seconds to take the penguin as far as possible. We then create the hills, sky, and penguin. Notice how the penguin is added to terrain_ instead of this (GameWorld). By doing this, we only need to move and scale the terrain_ object to simulate the movement of a camera following the penguin as he glides across the hill. The rest is boilerplate code that you've seen before. Let's look at the update function from GameWorld:

void GameWorld::update(float dt)
{
  // update penguin physics
  penguin_->UpdatePhysics();

  // update the world
  // slow it down...otherwise the game is too fast to enjoy!
  world_->Step(dt * 0.5f, 8, 3);

  // update penguin node
  penguin_->UpdateNode();

  // update the hills and sky
  terrain_->Update(penguin_->getPosition());
  sky_->Update(penguin_->getPosition(), terrain_->GetOffsetX(), 
    terrain_->getScale());

  // if penguin has gone beyond the second last key point, 
    its time to take a leap
  if(penguin_->getPositionX() > terrain_->GetCliffKeyPoint().x && 
    !penguin_->GetIsLeaping())
  {
    penguin_->StartLeap();
  }
  // check if the penguin is leaping first
  if(penguin_->GetIsLeaping())
  {
    // if the next hill's foot is close enough, its time to stop 
      leaping & time to start falling
    if(terrain_->GetFootKeyPoint().x > penguin_->getPositionX() &&
      terrain_->GetFootKeyPoint().x - penguin_->getPositionX() <= 
      SCREEN_SIZE.width * 1.75f)
    {
      penguin_->FinishLeap();
    }
  }
  // update the distance counter & label
  int new_distance_travelled = penguin_->getPositionX();
  if(new_distance_travelled > distance_travelled_)
  {
    char buf[16] = {0};
    sprintf(buf, "%dm", (int)(new_distance_travelled / 2));
    distance_label_->setString(buf);
    distance_travelled_ = new_distance_travelled;
  }
}

We start this function off by calling the UpdatePhysics function on the penguin. Then, we step into the Box2D world and call the penguin's UpdateNode function, followed by the Update functions of the Terrain and Sky classes respectively. Notice how we've halved the delta time there. This is to slow down the game as it is just too fast to play. Try it and see for yourself.

The next bit of code is important since it is here that we toggle the penguin's leaping state. First we check if the penguin has surpassed the terrain's cliff by calling the GetCliffKeyPoint function on terrain_. Then, we check whether the penguin isn't already leaping. As its name suggests, this function GetCliffKeyPoint returns to us the key point of the cliff, which is nothing but the second last key point in the array hill_key_points_ from the Terrain class.

Now that the penguin is leaping, we monitor the time when the leaping should be stopped. To know when to stop leaping, we need to know the location of the next foothill. That is exactly what GetFootKeyPoint of Terrain returns to us. Thus, our if condition basically checks if there is the appropriate distance between the penguin and the next foothill before calling the FinishLeap function of Penguin. The last bit of code you see there is similar to what we saw in the previous chapter, and it is used to update the HUD to track the distance the penguin has travelled thus far.

We'll now take a look at the PreSolve function, which is one of the callbacks b2ContactListener provides to us. The PreSolve function is called for a given contact before that contact goes into Box2D's solver. Here is the PreSolve function from GameWorld.cpp:

void GameWorld::PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
{
  // get the world manifold for this contact
  b2WorldManifold world_manifold;
  contact->GetWorldManifold(&world_manifold);
  // get velocity of the penguin
  b2Vec2 velocity = penguin_->GetBody()->GetLinearVelocity();

  // get angles for the velocity & normal vectors
  float velocity_angle = atan2f(velocity.y, velocity.x);
  float normal_angle = atan2f(world_manifold.normal.y, world_manifold.normal.x);

  // it is a bad landing if the difference in angles is above a certain threshold
  if (normal_angle - velocity_angle > BAD_LANDING_ANGLE_DIFF)
  {
    penguin_->OnBadLanding();
  }
}

First, we get the b2WorldManifold object for the current contact. The b2WorldManifold structure provides a normal for the collision and an array containing the points where two fixtures have collided. Next, we record the linear velocity of the penguin's body. We then calculate two angles: the angle of the penguin's velocity vector and the angle of the contact's normal vector. Then, we specify that if the difference between these two angles is greater than a predetermined threshold (BAD_LANDING_ANGLE_DIFF), the player has made a bad landing. BAD_LANDING_ANGLE_DIFF holds a value in radians and can be found in GameGlobals.h.

We're almost done with the gameplay. While defining the Penguin class, there was some talk of time being added for a perfect slide and a leap and we also initialized the variable time_left_ in the CreateGame function. It means that we must keep track of the time somewhere. This is done by the Tick function of GameWorld.cpp:

void GameWorld::Tick(float dt)
{
  // tick only after the game has started till before it ends
  if(!has_game_begun_ || has_game_ended_)
    return;

  // decrement the time counter
  -- time_left_;

  // update the label
  char buf[16] = {0};
  sprintf(buf, "%ds", time_left_);
  time_label_->setString(buf);

  // no more time...game over
  if(time_left_ <= 0)
  {
    GameOver();
  }

  // when there 5 seconds or less, start animating the label & 
    playing a sound
  if(time_left_ <= 5)
  {
    if(time_label_->numberOfRunningActions() <= 0)
    {
      CCActionInterval* scale_up_down = 
        CCSequence::createWithTwoActions(
        CCScaleTo::create(0.2f, 1.2f), 
        CCScaleTo::create(0.2f, 1.0f));
      time_label_->runAction(CCRepeatForever::create(scale_up_down));

      CCActionInterval* shake = 
        CCSequence::createWithTwoActions(
        CCRotateBy::create(0.05f, 20.0f), 
        CCRotateBy::create(0.05f, -20.0f));
      time_label_->setRotation(-10.0f);
      time_label_->runAction(CCRepeatForever::create(shake));
    }
  }
}

We want this function to be processed only after the game has begun and before it has ended; hence, the if condition. We then decrement the time_left_ variable, which keeps track of the amount of time the player has remaining. Then, we update the HUD to reflect the time left and call GameOver when the player has run out of time. Also, to alert the user, we animate the time label when time left is 5 seconds or less.

This winds up the Tick function and also completes our sixth and most challenging game yet. I have skipped discussing some things, including the Sky class and touch handling. I know that by now, these things shouldn't cause you to scratch your head in confusion. So, go have some fun and revel in the satisfaction of what you've accomplished in this chapter!

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

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