The update
function for Inverse Universe will be similar to most other games where we update all the game elements and check for collisions. The code looks like this:
void GameWorld::update(float dt) { // don't process if player is dying if(player_->is_dying_) return; // update each enemy CCObject* object = NULL; CCARRAY_FOREACH(enemies_, object) { Enemy* enemy = (Enemy*)object; if(enemy) { enemy->Update(player_->getPosition(), player_->GetShield() == NULL); } } // update each power-up object = NULL; CCARRAY_FOREACH(powerups_, object) { PowerUp* powerup = (PowerUp*)object; if(powerup) { powerup->Update(); } } CheckCollisions(); CheckRemovals(); }
We skip processing anything if the player's death animation is playing. We then iterate over the enemies_
and powerups_
arrays and update each object they contain. Finally, we check for collisions and for objects that must be removed. We will skip discussing the CheckRemovals
function but to give you a gist, it basically checks the state of the must_be_removed_
flag for Enemy
, PowerUp
, Blast
, and Missile
and removes them if the flag is enabled.
Let's now look at the CheckCollisions
function. This function will basically use circular collision detection by using the CIRCLE_INTERSECTS_CIRCLE
function defined in GameGlobals.h
. All you need to do is provide the center and radius of the two circles and the function returns true
if a collision is found.
Let's take a look at the following code:
void GameWorld::CheckCollisions() { // save player position & radius CCPoint player_position = player_->getPosition(); float player_radius = player_->getRadius(); // iterate through all enemies CCObject* object = NULL; CCARRAY_FOREACH(enemies_, object) { Enemy* enemy = (Enemy*)object; if(enemy) { CCPoint enemy_position = enemy->getPosition(); // check with Player if(CIRCLE_INTERSECTS_CIRCLE(player_position, player_radius, enemy_position, ENEMY_RADIUS)) { // if shield is enabled, kill enemy if(player_->GetShield()) { enemy->Die(); EnemyKilled(); } // else kill player...but only if enemy has finished spawning else if(!enemy->getIsSpawning()) player_->Die(); }
A lot of the collision detection revolves around the enemies. So, we first check for collisions of each enemy with the player. If a collision is found, we either kill the player or the enemy based on the shield being enabled or disabled. Another thing to consider is how we check whether the enemy is spawning and skip killing the player. We do this on purpose, or else the player will die before the enemy's spawning is complete—thereby leaving the user bewildered about the player's death.
Let's take a look at the following code:
// check with all blasts CCObject* object2 = NULL; CCARRAY_FOREACH(blasts_, object2) { Blast* blast = (Blast*)object2; if(blast) { if(CIRCLE_INTERSECTS_CIRCLE(blast->getPosition(), blast->getRadius(), enemy_position, ENEMY_RADIUS*1.5f)) { enemy->Die(); EnemyKilled(); } } } // check with all missiles object2 = NULL; CCARRAY_FOREACH(missiles_, object2) { Missile* missile = (Missile*)object2; if(missile) { if(CIRCLE_INTERSECTS_CIRCLE(missile->getPosition(), MISSILE_RADIUS, enemy_position, ENEMY_RADIUS*1.5f)) { missile->Explode(); } } } } }
We then proceed to check collisions of the enemy with the blasts and kill any enemy coming in contact with the blast. We also explode any missile that has come in contact with any enemy.
Let's take a look at the following code:
// check if player collides with any of the power-ups // activate the power-up if collision is found object = NULL; CCARRAY_FOREACH(powerups_, object) { PowerUp* powerup = (PowerUp*)object; if(powerup && !powerup->getIsActive()) { if(CIRCLE_INTERSECTS_CIRCLE(player_position, player_radius, powerup->getPosition(), POWERUP_ICON_OUTER_RADIUS)) { powerup->Activate(); } } } }
Finally, we check for collisions between the player and the power-ups and activate the power-up on contact with the player. That wraps up the CheckCollisions
function but before we move to the Tick
function, let's take a look at the EnemyKilled
function:
void GameWorld::EnemyKilled() { // increment counters ++ enemies_killed_total_; ++ enemies_killed_combo_; // reset combo time combo_timer_ = COMBO_TIME; // add score & update the label score_ += 7; char buf[16] = {0}; sprintf(buf, "Score: %d", score_); score_label_->setString(buf); }
Quite simply, we increment the total number of enemies killed as well as the number of enemies killed in the current combo. We can then use the number of enemies killed in the current combo to reward the player with some extra points when the combo timer elapses. We also reset the combo timer, increment the score, and update the HUD. Next up is the Tick
function:
void GameWorld::Tick(float dt) { // don't tick if player is dying if(player_->is_dying_) return; ++ seconds_; -- combo_timer_; // show the combo achieved if time is up if(combo_timer_ < 0) combo_timer_ = 0; else if(combo_timer_ == 0) ComboTimeUp(); // Tick each enemy CCObject* object = NULL; CCARRAY_FOREACH(enemies_, object) { Enemy* enemy = (Enemy*)object; if(enemy) { enemy->Tick(); } } // Tick each power-up object = NULL; CCARRAY_FOREACH(powerups_, object) { PowerUp* powerup = (PowerUp*)object; if(powerup) { powerup->Tick(); } } // add an enemy formation every 5 seconds if(seconds_ % 5 == 0) AddEnemyFormation(); // add a powerup formation every 4 seconds if(seconds_ % 4 == 0) AddPowerUp(); }
The first thing to do is to increment the seconds_
counter. The use of this variable will become clear in the last section when we use it to make the game progressively more difficult. Next, we decrement the combo_timer_
and call the ComboTimeUp
function that displays the number of enemies killed by the player in the last 3 seconds.
Then, we call the Tick
function for the Enemy
and PowerUp
objects. Finally, we add a new formation of enemies every 5 seconds and a new power-up every 4 seconds.
3.149.249.252