Important changes to repositories hosted on mbed.com
Mbed hosted mercurial repositories are deprecated and are due to be permanently deleted in July 2026.
To keep a copy of this software download the repository Zip archive or clone locally using Mercurial.
It is also possible to export all your personal repositories from the account settings page.
Dependencies: N5110 PinDetect PowerControl mbed
Game.cpp
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 }
Generated on Tue Jul 12 2022 21:59:47 by
1.7.2