Creating and reusing collectibles

While creating games, sometimes you may come across some of your game elements repeating themselves over and over as the level progresses. Consider you have a side-scrolling platform where your character is supposed to collect things such as coins, stars, health packs, and so on. Now, you might want to create these elements just before they enter the screen and delete them when the character collects them or when they exit the screen. However, this may not be the most efficient approach.

So, I will show you a commonly used technique of pooling or caching your game's elements so that they can be reused. What this means is that you will create a predefined maximum number of elements and place them in a container when the game or level loads. When required, elements will be removed from this container and merely added to the game world. Conversely, after their task is finished, they will be removed from the game world and added back to the container. This way, you don't waste any time creating and deleting objects.

For our game, we will create two CCArray containers named pool_collectibles_ and active_collectibles_ to maintain references to the collectibles in the pool and the ones added to the GameWorld object, respectively. We will also maintain a counter named num_collectibles_active_ to keep track of the collectibles currently added to GameWorld. Let's look at the CreateCollectibles function from GameWorld.cpp:

void void GameWorld::CreateCollectibles()
{
  // create the pool and active containers
  pool_collectibles_ = CCArray::createWithCapacity(MAX_COLLECTIBLES);
  pool_collectibles_->retain();
  active_collectibles_ = CCArray::createWithCapacity(MAX_COLLECTIBLES);
  active_collectibles_->retain();

  // all collectibles will be static bodies
  b2BodyDef body_def;
  body_def.type = b2_staticBody;
  body_def.position.Set(SCREEN_TO_WORLD(-1 * WALL_WIDTH), 
    SCREEN_TO_WORLD(-1 * WALL_WIDTH));

  for(int i = 0; i < MAX_COLLECTIBLES; ++i)
  {
    // ensure there is one balloon and one rocket
    EGameObjectType type = (i == 1) ? E_GAME_OBJECT_BALLOON : ( 
      (i == 0) ? E_GAME_OBJECT_ROCKET : E_GAME_OBJECT_COLLECTIBLE );
    // create collectible, set physics body & add to the pool
    Collectible* collectible = Collectible::create(this, type);
    collectible->SetBody(world_->CreateBody(&body_def));
    pool_collectibles_->addObject(collectible);
  }
}

We start the function by creating two new CCArray objects with a predefined capacity and retain them. Then, we define a b2BodyDef class for the collectible bodies and position them outside the boundary of the game.

In a loop, we create new Collectible objects, set their type and body, and finally add them to the pool_collectibles_ array. We also ensure that there is one rocket collectible and one balloon collectible in the array. Now that we have everything ready to add collectibles on to the screen whenever required, we can define the AddCollectible function from GameWorld.cpp:

void GameWorld::AddCollectible(bool special)
{
  // do not exceed the maximum
  if(num_collectibles_active_ >= MAX_COLLECTIBLES)
    return;

  // loop through the pool of collectibles
  Collectible* collectible = NULL;
  int num_pool_collectibles = pool_collectibles_->count();
  for(int i = 0; i < num_pool_collectibles; ++i)
  {
    // if a special collectible is required, return one if available
    collectible = (Collectible*)pool_collectibles_->objectAtIndex(i);
    if(special && (collectible->GetType() == E_GAME_OBJECT_ROCKET || 
      collectible->GetType() == E_GAME_OBJECT_BALLOON))
      break;
    else if(!special && collectible->GetType() != E_GAME_OBJECT_ROCKET 
      && collectible->GetType() != E_GAME_OBJECT_BALLOON)
      break;
  }

  // add the collectible to the batch node
  sprite_batch_node_->addChild(collectible, E_LAYER_COLLECTIBLES);

  // remove the collectible from the pool and add it to the active list
  pool_collectibles_->removeObject(collectible);
  active_collectibles_->addObject(collectible);
  ++ num_collectibles_active_;

  // position the collectible & then initialise it
  b2Vec2 position;
  position.x = SCREEN_TO_WORLD(WALL_WIDTH * 1.5f) + 
    CCRANDOM_0_1() * SCREEN_TO_WORLD(SCREEN_SIZE.width - 
    WALL_WIDTH * 3);
  position.y = distance_travelled_ + SCREEN_TO_WORLD(
    SCREEN_SIZE.height * 2);
  collectible->Init(position);
}

The first thing we do is check to see whether we've already added all the collectibles we have and return if so. We then loop through the pool_collectibles_ container and pick out a Collectible object. While doing this, we use the special flag passed to this function to decide between a simple collectible and a rocket or balloon.

Once the required collectible is found, we add it to the game's batch node. We must now update the respective containers so we simply remove the object from pool_collectibles_ and add it to active_collectibles_, incrementing the num_collectibles_active_ counter along the way.

The last couple of things left to do are to appropriately position the collectible and inform it that it has been added to the game world. We can now define the other half of this feature with the RemoveCollectible function from GameWorld.cpp:

void GameWorld::RemoveCollectible(Collectible* collectible)
{
  if(num_collectibles_active_ <= 0)
    return;

  -- num_collectibles_active_;
  // remove the collectible from the active list and add it back to the pool
  active_collectibles_->removeObject(collectible);
  pool_collectibles_->addObject(collectible);
  // reset the collectible so it is ready for reuse
  collectible->Reset();
}

The RemoveCollectible function first checks to see if there are any collectibles to remove in the first place. Then, as you may have guessed, the collectible object is removed from the list of active collectibles and placed back into pool. The counter is decremented and the collectible's state is reset so that it is ready to be used again. Remember that this function is called by the Collectible class when it has been collected (in the Collectible::AfterCollision function) or when it has exited the screen (in the Collectible::Update function).

Congratulations, you have learned how to implement the simplest technique to reuse your game's elements. I urge you to try out this technique to pool the hundreds of enemies we created in the previous game, Inverse Universe. Resource pooling is ideal for a game like Inverse Universe. With most of the elements created in our current game, the most important task still remains: the update loop.

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

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