Andreas Garmannslund / Mbed 2 deprecated SimplePlatformGame

Dependencies:   N5110 PinDetect PowerControl mbed

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers Game.cpp Source File

Game.cpp

Go to the documentation of this file.
00001 #include "Game.h "
00002 
00003 /// @file Game.cpp
00004 
00005 Game::~Game()
00006 {
00007     // Free allocated memory from bullets
00008     for (std::vector<Point*>::iterator it = bullets.begin(); it != bullets.end(); ++it)
00009         delete *it;
00010     
00011     bullets.clear();
00012     
00013     // Free allocated memory from enemies
00014     for (std::vector<Enemy*>::iterator it = enemies.begin(); it != enemies.end(); ++it)
00015         delete *it;
00016     
00017     enemies.clear();
00018 }
00019 
00020 void Game::init()
00021 {   
00022     Global::score = 0;
00023     paused = false;
00024     livesLeft = 2; 
00025     
00026     // Set initial values for the player
00027     player.width = player.height = 5; // important that this is correct, see size of Image::Player in Resources.h
00028     player.onGround = false;
00029     respawnPlayer();
00030     
00031     spawnRate = 2; // Probability for spawning a new enemy in percent
00032     spawnEnemy();
00033 }
00034 
00035 // temporary move to game
00036 const int Game::spawnPoints[3][2] = {{27,20}, {2,29}, {24,45}};
00037 
00038 void Game::spawnEnemy()
00039 {
00040     // Get random spawn point
00041     int r = rand() % 3;
00042     int x = Game::spawnPoints[r][0];
00043     int y = Game::spawnPoints[r][1];
00044     
00045     // Spawn random enemy
00046     int randPercent = (rand() % 100);
00047     Enemy::Type type;
00048         
00049     if (randPercent >= 40)      // 60% probability
00050         type = Enemy::JUMPER;
00051     else if (randPercent >= 15) // 25% probablitiy
00052         type = Enemy::SIMPLE;
00053     else                        // 15 % probability
00054         type = Enemy::RUNNER;
00055         
00056     // Create enemy
00057     Enemy *enemy = new Enemy(x, y, true, type);
00058     enemies.push_back(enemy);
00059 }
00060 
00061 // Functions
00062 void Game::update(float dt)
00063 {    
00064     // Pause button input
00065     if (input->read(Input::ButtonC))
00066     {
00067         if (releasedBtnC)
00068         {
00069             paused = !paused;
00070             releasedBtnC = false;
00071         }
00072     }
00073     else
00074         releasedBtnC = true;
00075         
00076     // Skip the rest if paused
00077     if (paused) return;   
00078     
00079     if ((rand() % 100) < spawnRate )
00080         spawnEnemy();
00081         
00082     // Handle input, should be its own function
00083     switch(input->joystick->getDirection())
00084     {
00085         case LEFT:
00086         case UP_LEFT:
00087         case DOWN_LEFT:
00088             player.vx = -2;
00089             player.facingLeft = true;
00090         break;
00091         
00092         case RIGHT:
00093         case UP_RIGHT:
00094         case DOWN_RIGHT:
00095             player.vx = 2;
00096             player.facingLeft = false;
00097         break;
00098         
00099         case CENTER:
00100             player.vx = 0;
00101         break;
00102     }
00103     
00104     
00105     // MOVE: Random enemies
00106     
00107     // Gravity
00108     player.vy += 1;
00109     
00110     // Check if player is trying to jump. Player can only jump if it's on the ground
00111     if (input->read(Input::ButtonA) && player.onGround)
00112     {
00113         player.vy = -4;
00114         player.onGround = false;
00115         sound->playNote(SFX::PLAYER_JUMP);
00116     }
00117     
00118     // Terminal velocity 3 px/update
00119     if (player.vy > TERMINAL_VELOCITY) player.vy = TERMINAL_VELOCITY;
00120     
00121     if (!player.dead)
00122         moveWithCollisionTest(&player, map);
00123     else // move without testing collision agains the map
00124     {
00125         player.x += player.vx;
00126         player.y += player.vy;
00127     }
00128     
00129     
00130     moveEnemies();
00131     
00132     // Check if bullet should be fired
00133     if (input->read(Input::ButtonB) && releasedBtnB)
00134     {
00135         // Create a new bullet and give it initial values
00136         Point* bullet = new Point;
00137         bullet->x = (int)(player.x + (player.width / 2)); //(player.facingLeft) ? (player.x-1) : (player.x + player.width);
00138         bullet->y = player.y + 2;
00139         bullet->vx = (player.facingLeft) ? -4 : 4;
00140         bullet->vy = 0;
00141         
00142         bullets.push_back(bullet);
00143         releasedBtnB = false;
00144         
00145         // Play sound
00146         sound->playNote(SFX::BULLET_FIRED);
00147     }
00148     else if (!input->read(Input::ButtonB))
00149         releasedBtnB = true;
00150     
00151     // Loop through bullets and move them + collision test
00152     for (std::vector<Point*>::iterator it = bullets.begin(); it != bullets.end();)
00153     {
00154         Point* bullet = *it;
00155         
00156         int x0; // left border of collision rect
00157         int x1; // right border of collision rect
00158         
00159         int oldX = bullet->x;
00160         int newX = bullet->x + bullet->vx;
00161         
00162         x0 = min(oldX, newX);
00163         x1 = max(oldX, newX);
00164             
00165         // Collision rect for bullet in this time step
00166         Rectangle bulletColRect(x0, bullet->y, (x1-x0)+1, 1);
00167         
00168         bool col = false;
00169         // Delete if outside screen
00170         if (newX < 0 || newX > WIDTH || bulletHitMap(bulletColRect, map)) // if outside screen
00171         {
00172             col = true;
00173         }
00174         else
00175         {
00176             // loop through all enemies
00177             for (std::vector<Enemy*>::iterator ite = enemies.begin(); ite != enemies.end(); ++ite)
00178             {
00179                 Enemy *enemy = *ite;
00180                 
00181                 // If bullet hits enemy
00182                 //if (!enemy->dead && bullet->x >= enemy->x && bullet->x <= enemy->getRight() && bullet->y >= enemy->y && bullet->y <= enemy->getBottom()) 
00183                 
00184                 Rectangle enemyColRect(enemy->x, enemy->y, enemy->width, enemy->height); // collision rectangle for enemy
00185                 
00186                 if (!enemy->dead && hitTestRect(bulletColRect, enemyColRect))
00187                 {
00188                     col = true;
00189                     
00190                     enemy->dead = true;
00191                     enemy->vx = bullet->vx / 2; // sends the dead enemy in the same direction as the incoming bullet
00192                     enemy->vy = -3;             // sends the dead enemy upwards in the air, because of impact
00193                     
00194                     Global::score += 5 * enemy->difficulty; // increase the score
00195                     
00196                     sound->playNote(SFX::ENEMY_DEAD);
00197                 }
00198             }
00199         }
00200         
00201         if (!col)
00202         {
00203             ++it;   // go to next element
00204             bullet->x += bullet->vx; // update position
00205         }
00206         else
00207         {
00208             delete bullet;
00209             it = bullets.erase(it); // go to next element
00210         }
00211     }
00212     
00213     // Check if player hits enemy
00214     Rectangle playerRect(player.x, player.y, player.width, player.height);
00215     for (std::vector<Enemy*>::iterator it = enemies.begin(); it != enemies.end(); ++it)
00216     {
00217         Enemy *enemy = *it;
00218         
00219         if (enemy->dead) continue; // only test against living enemies
00220         
00221         Rectangle enemyRect(enemy->x, enemy->y, enemy->width, enemy->height);
00222         
00223         if (hitTestRect(playerRect, enemyRect))
00224         {
00225             player.dead = true;
00226             player.vx = 0;
00227             player.vy = -4;
00228             player.onGround = false;
00229             --livesLeft;
00230             
00231             sound->playNote(SFX::PLAYER_DEAD);
00232             break;
00233         }
00234     }
00235     
00236     if (player.dead)
00237     {
00238         // remove all enemies (let them fall off)
00239         for (std::vector<Enemy*>::iterator it = enemies.begin(); it != enemies.end(); ++it)
00240         {
00241             Enemy *enemy = *it;
00242             enemy->dead = true;
00243         }
00244         
00245         if (player.y >= HEIGHT && !enemies.size()) // when all enemies are removed
00246         {
00247             if (livesLeft)
00248             {
00249                 respawnPlayer(); // Respawn player if it still have lives left
00250                 spawnEnemy();   // Spawn an enemy right away
00251             }
00252             else
00253             {
00254                 if (Global::score > Global::highscores[2].score) // If new high score
00255                     requestStateChange(SUBMIT_HIGHSCORE);
00256                 else
00257                     requestStateChange(GAME_OVER);
00258             }
00259         }
00260     }
00261 }
00262 
00263 void Game::render()
00264 {
00265     
00266     if (!player.dead)
00267     {
00268         // Draw map
00269         drawImage(map);
00270     }
00271     else
00272     {
00273         // Print lives left
00274         std::stringstream ss;
00275         ss << "Lives left: " << livesLeft;
00276         lcd->printString(ss.str().c_str(), 4, 2);
00277     }
00278     
00279     // Draw player
00280     drawImage(Image::Player, player.x, player.y, false, !player.facingLeft, player.dead);
00281     
00282     
00283     // Draw enemies
00284     for (std::vector<Enemy*>::iterator it = enemies.begin(); it != enemies.end(); ++it)
00285     {
00286         Enemy *enemy = *it;
00287         
00288         switch (enemy->type)
00289         {
00290             case Enemy::SIMPLE:
00291                 drawImage(Image::EnemySimple, enemy->x, enemy->y, false, !enemy->facingLeft, enemy->dead);    
00292             break;
00293             
00294             case Enemy::JUMPER:
00295                 drawImage(Image::EnemyJumper, enemy->x, enemy->y, false, !enemy->facingLeft, enemy->dead);
00296             break;
00297             
00298             case Enemy::RUNNER:
00299                 drawImage(Image::EnemyRunner, enemy->x, enemy->y, false, !enemy->facingLeft, enemy->dead);
00300             break;
00301             
00302             default:
00303                 ; // should not happen, don't render
00304         }
00305         
00306         
00307     }    
00308 
00309     // Render bullets
00310     for (std::vector<Point*>::iterator it = bullets.begin(); it != bullets.end(); ++it)
00311     {
00312         int x, y;
00313         x = (*it)->x;
00314         y = (*it)->y;
00315         
00316         if (x >= 0 && x < WIDTH && y >= 0 && y < HEIGHT) // Boundary check
00317             lcd->setPixel(x,y);
00318     }
00319     
00320     // Draw pause
00321     if (paused)
00322     {
00323         lcd->drawRect(24, 13, 40, 13, 0); // outline
00324         lcd->drawRect(25, 14, 38, 11, 2); // white fill
00325         lcd->printString("Paused", 27, 2); // text
00326     }
00327     
00328     // GUI
00329     renderScore();
00330 }
00331 
00332 // Collision test between entites and map
00333 void Game::moveWithCollisionTest(Entity* entity, const int map[HEIGHT][WIDTH])
00334 {   
00335     int x = entity->x;
00336     int y = entity->y;
00337     int steps = abs(entity->vx); // how many units (pixels) the entity should move in said direction
00338     bool collision; // true if colliding
00339     
00340     // Check x-axis
00341     if (entity->vx > 0) // moving right
00342     {
00343         int entityRight = x + entity->width - 1; // Need to check right border of entity, since it is moving right
00344         
00345         while(steps--) // While it still have more movement left
00346         {
00347             collision = false;
00348             
00349             // Wrapping
00350             if (entityRight+1 >= WIDTH)
00351                 entityRight = -1;// wants entityRight = -1, so next check is entityRight 0*/
00352             
00353             for (int i = 0; i < entity->height; ++i) // Loop through all vertical points on the right hand side of the entity (y+i)
00354             {
00355                 if (map[y+i][entityRight+1]) // If moving to the right leads to collision for given y+i
00356                 {
00357                     // Slope + allows player to climb to top of platform by going right if it hits close to top of wall.
00358                     if (!map[y+i-1][entityRight+1] && entity->onGround)
00359                     {
00360                         entity->vy = -1;
00361                     }
00362                     else
00363                     {
00364                         collision = true; // Then collision is true
00365                         break;            // Skip the for loop, no need for further testing
00366                     }
00367                         
00368                 }
00369             }
00370             
00371             if (collision) // If collision
00372                 break;     // skip the while loop, entity can not move further, even though its velocity is higher
00373             else
00374                 ++entityRight;  // Move entity one px to the right
00375         }
00376         
00377         // If wrap didn't work, make sure entity is on the correct side of the map
00378         if (entityRight < 0)
00379             entityRight = WIDTH-1;
00380             
00381         entity->x = entityRight - (entity->width - 1); // Update entity's position. Need to set upper-left pixel.
00382     }
00383     else // moving left
00384     {
00385         while(steps--) // While still movement left
00386         {
00387             collision = false;
00388             
00389             // Wrap around map
00390             if (x-1 < 0)
00391                 x = WIDTH; // causes x-1 in the next check to be WIDTH - 1
00392             
00393             // Check for all y-positions
00394             for (int i = 0; i < entity->height; ++i)
00395             {
00396                 
00397                 if (map[y+i][x-1])                  // If solid block
00398                 {
00399                     if (!map[y+i-1][x-1] && entity->onGround) // If slope or close to top of wall (=> can climb by going left).
00400                     {
00401                         entity->vy = -1;   
00402                     }
00403                     else
00404                     {
00405                         collision = true;
00406                         break;                          // Collision detected, no further testing required
00407                     }
00408                 }
00409             }
00410             
00411             if (collision)
00412                 break;
00413             else
00414                 --x;    // Move to the left if no collision is detected
00415         }
00416         
00417         x %= WIDTH; // In case wrapping caused entity to crash with wall on other side, x should be 0 instead of WIDTH (invalid).
00418         
00419         entity->x = x; // update position
00420     }
00421     
00422     // Check collision with map in y-direction - works the same way as the x-axis, except for other axis
00423     x = entity->x;
00424     y = entity->y;
00425     steps = abs(entity->vy);
00426     
00427     if (entity->vy > 0) // downwards
00428     {
00429         int entityBottom = y + entity->height - 1; // Need to check if bottom part collides
00430         while(steps--)  // Still movement left
00431         {
00432             collision = false;
00433 
00434             for (int i = 0; i < entity->width; ++i)  // Loop through all x-position on lower part of entity
00435             {   
00436                 if (map[(entityBottom+1) % HEIGHT][x+i])       // If moving the entity one step down for a given (x+i)-position gives a collision
00437                 {
00438                     collision = true;
00439                     break;                          // No further testing required
00440                 }
00441             }
00442             
00443             if (collision)                          // If collision
00444             {
00445                 entity->vy = 0;                      // Set vertical velocity to 0 (playe
00446                 entity->onGround = true;             // entity has hit ground
00447                 break;                               // Skip the while loop as the entity can not move further downwards
00448             }
00449             else                // Can safely move entity without collision
00450             {
00451                 ++entityBottom; // Move entity one step down
00452                 entity->onGround = false;
00453             }
00454         }
00455         
00456         // Wrapping
00457         y = (entityBottom - (entity->height - 1));
00458         if (y >= HEIGHT)            // if completely outside map
00459             y = -entity->height;    // wrap to top of map
00460             
00461         entity->y = y; // (entityBottom - (entity->height - 1));      // Update position when done moving, remember that entity.y refers to upper part of the entity
00462     }
00463     else // moving up, check collision from top
00464     {
00465         while(steps--)  // Still movement left
00466         {
00467             collision = false;
00468                             
00469             for (int i = 0; i < entity->width; ++i) // Check for all x-positions
00470             {
00471                 int y1 = ((y-1) + HEIGHT) % HEIGHT; // In case negative, because of wrapping
00472                 
00473                 if (map[y1][x+i])                  // If moving upwards gives collision for a given x+i
00474                 {
00475                     collision = true;               // Then we have a collision
00476                     break;                          // No further testing needed, skip for loop
00477                 }
00478             }
00479             
00480             if (collision)  // If collision was detected
00481             {
00482                 entity->vy = 0;  // Set vertical velocity to zero
00483                 break;          // Skip while loop as entity can not move further up
00484             }
00485             else            // If safe to move for all x-values
00486                 --y;        // Move entity one step up
00487         }
00488         
00489         // Wrapping
00490         if (y + (entity->height - 1) < 0)          // completely outside map (bottom of entity over top of map)
00491             y = HEIGHT-1 - entity->height - 1;     // Sets the altitude.
00492             
00493         entity->y = y;       // Update vertical position of entity
00494     }    
00495 }
00496 
00497 bool Game::hitTestRect(Rectangle r1, Rectangle r2)
00498 {
00499     return ((r1.x + r1.width > r2.x)       // r1's right edge to the right of r2's left edge
00500         &&  (r1.x < r2.x + r2.width)       // r1's left edge to the left of r2's right edge
00501         &&  (r1.y + r2.height > r2.y)      // r1's bottom lower than r2's top
00502         &&  (r1.y < r2.y + r2.height));    // r1's top higher than r2's bottom
00503         
00504     // Note: Right border: r1.x + r1.width - 1, but we don't need to subtract 1 as we use > instead of >=
00505 }
00506 
00507 bool Game::bulletHitMap(Rectangle &bulletColRect, const int map[HEIGHT][WIDTH])
00508 {
00509     for (int j = 0; j < bulletColRect.width; ++j)
00510     {
00511         if (map[bulletColRect.y][bulletColRect.x + j])
00512             return true;
00513     }
00514     
00515     return false;
00516 }
00517 
00518 void Game::moveEnemies()
00519 {
00520     for (std::vector<Enemy*>::iterator it = enemies.begin(); it != enemies.end(); )
00521     {   
00522         Enemy *enemy = *it;
00523         
00524         // Gravity
00525         enemy->vy += 1;
00526         
00527         if (!enemy->dead)
00528         {
00529             // Random movement for enemies
00530             if (enemy->onGround && (rand() % 100) < enemy->jumpRate)
00531             {
00532                 // jump
00533                 enemy->vy = -3;
00534                 enemy->onGround = false;
00535             }
00536             else if ((rand() % 100) > 98) // 2% chance
00537             {
00538                 // switch direction
00539                 enemy->vx *= -1;
00540                 enemy->facingLeft = (enemy->vx < 0);
00541             }
00542             
00543             moveWithCollisionTest(enemy, map);
00544             
00545             // Enemy AI
00546             if (enemy->y >= 0)
00547             {
00548                 int nextRight = enemy->getRight() + 1;   // Next position of right edge if enemy moves to the right
00549                 nextRight %= WIDTH; // wrapping
00550                 for (int i = 0; i < enemy->height; ++i)  // Check for all heighs
00551                 {
00552                     // Check if crashing if moving right or left. Bounds should already be limited by moveWithCollisionTest!
00553                     if (map[enemy->y + i][nextRight] || map[enemy->y + i][enemy->x - 1])
00554                     {
00555                         enemy->vx *= -1; // move in opposite direction
00556                         enemy->facingLeft = !enemy->facingLeft; // toggle direction
00557                         break;          // no further testing required
00558                     }
00559                 }
00560             }
00561             
00562             ++it;   // go to next enemy
00563         }
00564         else    // if enemy is dead
00565         {
00566             enemy->y += enemy->vy;
00567             enemy->x += enemy->vx;
00568             
00569             if (enemy->y >= HEIGHT) // if outside map (and dead)
00570             {
00571                 delete enemy;
00572                 it = enemies.erase(it); // remove and go to next enemy
00573             }
00574             else
00575                 ++it;   // go to next enemy
00576         }
00577     }   
00578 }
00579 
00580 void Game::renderScore()
00581 {
00582     int s = (Global::score < 100000) ? Global::score : 99999; // Max possible score is 99999.
00583     
00584     // Read digits
00585     int digits[5]; // max five
00586     // Count the number of digits in the score
00587     int numDigits = 0;
00588     do
00589     {
00590         digits[numDigits] = s % 10;
00591         s /= 10;
00592         ++numDigits;
00593     } while (s != 0 && numDigits < 5);
00594     
00595     
00596     // Draw score
00597     int xStart = 79;
00598     int xStep = 4; // width + 1
00599     int y = 2;
00600     int x;
00601     
00602     for (int i = 0; i < numDigits; ++i)
00603     {
00604         x = xStart - i * xStep;
00605         
00606         switch (digits[i])
00607         {
00608             case 1:
00609                 drawImage(Number::One, x, y);
00610             break;
00611             
00612             case 2:
00613                 drawImage(Number::Two, x, y);
00614             break;
00615             
00616             case 3:
00617                 drawImage(Number::Three, x, y);
00618             break;
00619             
00620             case 4:
00621                 drawImage(Number::Four, x, y);
00622             break;
00623             
00624             case 5:
00625                 drawImage(Number::Five, x, y);
00626             break;
00627             
00628             case 6:
00629                 drawImage(Number::Six, x, y);
00630             break;
00631             
00632             case 7:
00633                 drawImage(Number::Seven, x, y);
00634             break;
00635             
00636             case 8:
00637                 drawImage(Number::Eight, x, y);
00638             break;
00639             
00640             case 9:
00641                 drawImage(Number::Nine, x, y);
00642             break;
00643             
00644             case 0:
00645             default:
00646                 drawImage(Number::Zero, x, y);
00647             break;
00648         }
00649                 
00650     }
00651 }
00652 
00653 void Game::respawnPlayer()
00654 {
00655     player.x = 74;
00656     player.y = 31;
00657     player.vx = player.vy = 0;
00658     player.facingLeft = true;   
00659     player.dead = false;
00660 }