By now, we have a fully functioning game but we still haven't ensured the game is challenging and addictive for the user. We must engage the player by increasing the intensity and difficulty of the game progressively. In our previous game, we had the option of designing levels that get more difficult, but this game isn't level based and is different every time.
With that in mind, a bit of spice is added by creating a variety of formations in which the enemies will spawn on screen. These formations will be increasingly difficult with the difficulty completely based on how long the user has managed to survive. We define a few enums and constants in GameGlobals.h
before writing the functions that will add progression to our game:
enum ESkillTimer { E_SKILL1 = 10, E_SKILL2 = 30, E_SKILL3 = 45, E_SKILL4 = 60, E_SKILL5 = 90, E_SKILL6 = 120, };
First up, we have an enum called ESkillTimer
that represents the skill level in terms of the number of seconds the user has survived the game. Next, we have enum EEnemyFormation
defined as follows:
enum EEnemyFormation { E_FORMATION_RANDOM_EASY = 0, E_FORMATION_VERTICAL_EASY, E_FORMATION_HORIZONTAL_EASY, E_FORMATION_POLYGON_EASY, E_FORMATION_RANDOM_MEDIUM, E_FORMATION_VERTICAL_MEDIUM, E_FORMATION_HORIZONTAL_MEDIUM, E_FORMATION_POLYGON_MEDIUM, E_FORMATION_RANDOM_HARD, E_FORMATION_VERTICAL_HARD, E_FORMATION_HORIZONTAL_HARD, E_FORMATION_POLYGON_HARD, E_FORMATION_MAX //12 };
EEnemyFormation
is an enum representing the various types of formations the enemies will be positioned in when they are added to the GameWorld
. Next, we have a few arrays pre-defined as follows:
const int GameGlobals::skill1_formations[] = {0, 4}; const int GameGlobals::skill2_formations[] = {4, 4, 4, 4, 1, 1, 1, 2, 2, 2}; const int GameGlobals::skill3_formations[] = {4, 4, 4, 8, 8, 1, 1, 2, 2, 5, 5, 5, 6, 6, 6, 3, 3}; const int GameGlobals::skill4_formations[] = {4, 4, 8, 8, 8, 5, 5, 5, 6, 6, 6, 3, 3, 3, 7, 7, 7}; const int GameGlobals::skill5_formations[] = {8, 8, 8, 3, 3, 3, 5, 5, 6, 6, 9, 9, 10, 10, 7, 7, 7}; const int GameGlobals::skill6_formations[] = {8, 8, 8, 5, 5, 6, 6, 9, 9, 10, 10, 7, 7, 7, 11, 11, 11};
We also have arrays that specify the frequency of the various formations for a specific skill level. We will stitch these different bits of information together in the GetEnemyFormationType
function:
EEnemyFormation GameWorld::GetEnemyFormationType() { // return a formation type from a list of formation types, based on time user has been playing // the longer the user has survived, the more difficult the formations will be if(seconds_ > E_SKILL6) { int random_index = CCRANDOM_0_1() * GameGlobals::skill6_formations_size; return (EEnemyFormation)(GameGlobals::skill6_formations[random_index]); } else if(seconds_ > E_SKILL5) { int random_index = CCRANDOM_0_1() * GameGlobals::skill5_formations_size; return (EEnemyFormation)(GameGlobals::skill5_formations[random_index]); } else if(seconds_ > E_SKILL4) { int random_index = CCRANDOM_0_1() * GameGlobals::skill4_formations_size; return (EEnemyFormation)(GameGlobals::skill4_formations[random_index]); } else if(seconds_ > E_SKILL3) { int random_index = CCRANDOM_0_1() * GameGlobals::skill3_formations_size; return (EEnemyFormation)(GameGlobals::skill3_formations[random_index]); } else if(seconds_ > E_SKILL2) { int random_index = CCRANDOM_0_1() * GameGlobals::skill2_formations_size; return (EEnemyFormation)(GameGlobals::skill2_formations[random_index]); } else if(seconds_ > E_SKILL1) { int random_index = CCRANDOM_0_1() * GameGlobals::skill1_formations_size; return (EEnemyFormation)(GameGlobals::skill1_formations[random_index]); } else { return E_FORMATION_RANDOM_EASY; } }
This function first checks up to which skill level the user has managed to survive and then returns an appropriate formation for the respective skill level. Even though the formations are chosen randomly, we still have control over how often a given formation can pop up for a given difficulty level. All we need to do is increase or decrease the number of occurrences of a formation type within the array for a skill level.
Now that we have the type of formation based on difficulty, we can proceed by actually adding the enemies to the game in the AddEnemyFormation
function:
void GameWorld::AddEnemyFormation() { // fetch an enemy formation EEnemyFormation type = GetEnemyFormationType(); // fetch a list of positions for the given formation vector<CCPoint> formation = GameGlobals::GetEnemyFormation( type, boundary_rect_, player_->getPosition()); int num_enemies_to_create = formation.size(); int num_enemies_on_screen = enemies_->count(); // limit the total number of enemies to MAX_ENEMIES if(num_enemies_on_screen + num_enemies_to_create >= MAX_ENEMIES) { num_enemies_to_create = MAX_ENEMIES - num_enemies_on_screen; } // create, add & position enemies based on the formation for(int i = 0; i < num_enemies_to_create; ++i) { Enemy* enemy = Enemy::create(this); enemy->setPosition(formation[i]); enemy->Spawn(i * ENEMY_SPAWN_DELAY); addChild(enemy, E_LAYER_ENEMIES); enemies_->addObject(enemy); } }
This function begins by fetching the type of formation to create and passes it into the GetEnemyFormation
function of the GameGlobals
class. The GetEnemyFormation
function will return a vector of CCPoint
that we will use to position the enemies. At any given point, the total number of enemies on screen should not exceed 250, which in this case is held by constant MAX_ENEMIES
defined in GameGlobals.h
. We ensure the preceding condition is met and then run a loop where we create an Enemy
object, set its position, call its Spawn
function, and finally add it to the screen as well as the enemies_ CCARRAY
.
By adding progression to Inverse Universe, we complete our fourth game. I have skipped discussing some of the functionality in the chapter, such as the BackgroundManager
class, the way enemy formations are created and some of the helper functions. You will find the code for such features and more in the source bundle. Rest assured the comments will be enough to explain what's happening.
18.226.34.197