Helios Lyons 201239214

Dependencies:   mbed

Brief

My aim for this project was to create a FRDM K64F adapted version of the classic Space Invaders by Tomohiro Nishikado. The game itself has a number of clear features to implement;

  • Left to right movement for the player 'canon'
  • A fixed amount of player lives
  • Firing mechanics for both canon and invaders (hence collision systems)
  • Random firing from remaining invaders
  • Wave based combat

My own addition to these established ideas was Boss waves, featuring a single, larger sprite which fires at a faster interval than previous waves. The addition of a movement system using a basic for loop, as opposed to a velocity based system, will enhance the nostalgic feel of the game.

https://os.mbed.com/media/uploads/helioslyons/screenshot_2020-05-27_at_06.12.00.png

Gameplay

Movement is controlled with the joystick, moving the canon left or right. Fire by pressing A. Invaders spawn at set positions, but randomly fire at a set interval, which is higher for boss waves. Time is taken during each wave, and displayed at wave intervals, and if the play wins.

Controls are shown on the Gamepad below: (attribution: Craig A. Evans, ELEC2645 University of Leeds)

https://os.mbed.com/media/uploads/helioslyons/screenshot_2020-05-27_at_06.20.18.png

Committer:
helioslyons
Date:
Wed May 27 15:40:47 2020 +0000
Revision:
13:1472c1637bfc
Parent:
11:1fd8fca23375
Final Submission. I have read and agreed with Statement of Academic Integrity.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
eencae 0:b7f1f47bb26a 1 /*
eencae 0:b7f1f47bb26a 2 ELEC2645 Embedded Systems Project
eencae 0:b7f1f47bb26a 3 School of Electronic & Electrical Engineering
eencae 0:b7f1f47bb26a 4 University of Leeds
eencae 0:b7f1f47bb26a 5 2019/20
eencae 0:b7f1f47bb26a 6
helioslyons 1:a3f43007030e 7 Name: Helios Ael Lyons
helioslyons 1:a3f43007030e 8 Username: mc18hal
helioslyons 1:a3f43007030e 9 Student ID Number: 201239214
helioslyons 1:a3f43007030e 10 Date: 24th March 2020
helioslyons 1:a3f43007030e 11 */
helioslyons 1:a3f43007030e 12
helioslyons 1:a3f43007030e 13 /** Main
helioslyons 13:1472c1637bfc 14 * @brief Start screen, menu, game loop, and time tracking. The start project Pong was used for the initial structuring and velocity systems it provided.
helioslyons 1:a3f43007030e 15 * @author Helios A. Lyons
helioslyons 1:a3f43007030e 16 * @date March, 2020
eencae 0:b7f1f47bb26a 17 */
eencae 0:b7f1f47bb26a 18
helioslyons 13:1472c1637bfc 19 // NOTE: the Battle.h API docs are not being recognised by Doxygen, though
helioslyons 13:1472c1637bfc 20 // I have included the requisite code. Additionally, code was used from Mozilla
helioslyons 13:1472c1637bfc 21 // to implement the rectangle based collision system in Battle.h (link below):
helioslyons 13:1472c1637bfc 22 // https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
helioslyons 13:1472c1637bfc 23
helioslyons 13:1472c1637bfc 24 // KNOWN BUGS: On first play through, or when waves are played too quickly, the
helioslyons 13:1472c1637bfc 25 // game recognises that the wave has ended, but does not load into the
helioslyons 13:1472c1637bfc 26 // next. I am in the process of identifying the source of this.
helioslyons 13:1472c1637bfc 27
helioslyons 13:1472c1637bfc 28 // pre-processor directives
eencae 0:b7f1f47bb26a 29 #include "mbed.h"
eencae 0:b7f1f47bb26a 30 #include "Gamepad.h"
eencae 0:b7f1f47bb26a 31 #include "N5110.h"
helioslyons 11:1fd8fca23375 32 #include "Bitmap.h"
helioslyons 10:19b250c7de5e 33 #include "Battle.h"
helioslyons 10:19b250c7de5e 34
helioslyons 10:19b250c7de5e 35 // structs
helioslyons 10:19b250c7de5e 36 struct UserInput {
helioslyons 10:19b250c7de5e 37 Direction d;
helioslyons 10:19b250c7de5e 38 float mag;
helioslyons 10:19b250c7de5e 39 };
helioslyons 11:1fd8fca23375 40
helioslyons 10:19b250c7de5e 41 // objects
helioslyons 10:19b250c7de5e 42 N5110 lcd;
helioslyons 10:19b250c7de5e 43 Gamepad pad;
helioslyons 10:19b250c7de5e 44 Battle battle;
helioslyons 11:1fd8fca23375 45 Canon player;
helioslyons 11:1fd8fca23375 46
helioslyons 11:1fd8fca23375 47 Timer playTime;
helioslyons 10:19b250c7de5e 48
helioslyons 10:19b250c7de5e 49 // prototypes
helioslyons 10:19b250c7de5e 50 void init();
helioslyons 10:19b250c7de5e 51 void update_game(UserInput input);
helioslyons 10:19b250c7de5e 52 void render();
helioslyons 10:19b250c7de5e 53 void welcome();
helioslyons 13:1472c1637bfc 54 void game(Gamepad &pad);
helioslyons 11:1fd8fca23375 55 void wave(int n);
helioslyons 13:1472c1637bfc 56 void waveState(int n, Gamepad &pad);
helioslyons 11:1fd8fca23375 57 void menu(Gamepad &pad);
helioslyons 13:1472c1637bfc 58 void instructions(Gamepad &pad);
helioslyons 10:19b250c7de5e 59
helioslyons 10:19b250c7de5e 60 // functions
helioslyons 10:19b250c7de5e 61 int main()
helioslyons 11:1fd8fca23375 62 {
helioslyons 11:1fd8fca23375 63 //lcd.clear();
helioslyons 11:1fd8fca23375 64 init(); // initialise LCD and gamepad
helioslyons 11:1fd8fca23375 65
helioslyons 11:1fd8fca23375 66 welcome(); // display welcome screen
helioslyons 11:1fd8fca23375 67
helioslyons 11:1fd8fca23375 68 Menu_Point: // goto point after victory/defeat
helioslyons 11:1fd8fca23375 69 menu(pad);
helioslyons 11:1fd8fca23375 70
helioslyons 11:1fd8fca23375 71 battle.reset_life(); // start player lives at 3
helioslyons 11:1fd8fca23375 72 int n = 0;
helioslyons 10:19b250c7de5e 73
helioslyons 11:1fd8fca23375 74 while (n < 9) {
helioslyons 11:1fd8fca23375 75 n++;
helioslyons 11:1fd8fca23375 76 if (battle.life() < 1) { break; } // break condition for when player dies
helioslyons 11:1fd8fca23375 77 switch(n) {
helioslyons 11:1fd8fca23375 78 case 1: // initialise entities for the wave
helioslyons 11:1fd8fca23375 79 battle.init(1,3,4,3,0,3); // row,column,speed,firing interval,boss,bossNum
helioslyons 13:1472c1637bfc 80 waveState(n, pad);
helioslyons 11:1fd8fca23375 81 break;
helioslyons 11:1fd8fca23375 82
helioslyons 11:1fd8fca23375 83 case 2:
helioslyons 11:1fd8fca23375 84 battle.init(3,3,4,3,0,3); // init function is used this way to provide
helioslyons 13:1472c1637bfc 85 waveState(n, pad); // easy options to vary invader formation and number
helioslyons 13:1472c1637bfc 86 break; // in each wave, creating streamlined level design
helioslyons 11:1fd8fca23375 87
helioslyons 11:1fd8fca23375 88 case 3:
helioslyons 13:1472c1637bfc 89 battle.init(1,1,3,2,1,1); // in redesign, would likely implement a 2D vector
helioslyons 13:1472c1637bfc 90 waveState(n, pad); // which stores each wave's information, and is run
helioslyons 13:1472c1637bfc 91 break; // through using a for loop to fetch
helioslyons 11:1fd8fca23375 92
helioslyons 11:1fd8fca23375 93 case 4:
helioslyons 11:1fd8fca23375 94 battle.init(2,3,3,3,0,1);
helioslyons 13:1472c1637bfc 95 waveState(n, pad);
helioslyons 11:1fd8fca23375 96 break;
helioslyons 11:1fd8fca23375 97
helioslyons 11:1fd8fca23375 98 case 5:
helioslyons 11:1fd8fca23375 99 battle.init(3,3,3,3,0,1);
helioslyons 13:1472c1637bfc 100 waveState(n, pad);
helioslyons 11:1fd8fca23375 101 break;
helioslyons 11:1fd8fca23375 102
helioslyons 11:1fd8fca23375 103 case 6:
helioslyons 11:1fd8fca23375 104 battle.init(1,1,2,1,1,2);
helioslyons 13:1472c1637bfc 105 waveState(n, pad);
helioslyons 11:1fd8fca23375 106 break;
helioslyons 11:1fd8fca23375 107
helioslyons 11:1fd8fca23375 108 case 7:
helioslyons 11:1fd8fca23375 109 battle.init(3,4,2,3,0,2);
helioslyons 13:1472c1637bfc 110 waveState(n, pad);
helioslyons 11:1fd8fca23375 111 break;
helioslyons 11:1fd8fca23375 112
helioslyons 11:1fd8fca23375 113 case 8:
helioslyons 11:1fd8fca23375 114 battle.init(3,6,3,2,0,2);
helioslyons 13:1472c1637bfc 115 waveState(n, pad);
helioslyons 11:1fd8fca23375 116 break;
helioslyons 11:1fd8fca23375 117
helioslyons 11:1fd8fca23375 118 case 9:
helioslyons 11:1fd8fca23375 119 battle.init(1,1,1,1,1,3);
helioslyons 13:1472c1637bfc 120 waveState(n, pad);
helioslyons 11:1fd8fca23375 121 break;
helioslyons 13:1472c1637bfc 122 }
helioslyons 11:1fd8fca23375 123 }
helioslyons 11:1fd8fca23375 124
helioslyons 11:1fd8fca23375 125 if (battle.life() < 1) { // if player failed display game over
helioslyons 11:1fd8fca23375 126 lcd.printString("Game Over",16,2);
helioslyons 11:1fd8fca23375 127 lcd.refresh();
helioslyons 11:1fd8fca23375 128 wait(8.0);
helioslyons 11:1fd8fca23375 129 goto Menu_Point; // return to main menu after wait
helioslyons 11:1fd8fca23375 130 }
helioslyons 11:1fd8fca23375 131 else { // if waves are completed display time and victory screen
helioslyons 11:1fd8fca23375 132 lcd.printString("You won!",16,2);
helioslyons 11:1fd8fca23375 133 char buffer4[14];
helioslyons 11:1fd8fca23375 134 float time = sprintf(buffer4,"Time:%.2f",playTime.read());
helioslyons 11:1fd8fca23375 135 lcd.printString(buffer4,16,3);
helioslyons 11:1fd8fca23375 136 lcd.refresh();
helioslyons 11:1fd8fca23375 137 wait(8.0);
helioslyons 11:1fd8fca23375 138 goto Menu_Point; // return to main menu
helioslyons 11:1fd8fca23375 139 }
helioslyons 10:19b250c7de5e 140 }
eencae 0:b7f1f47bb26a 141
helioslyons 11:1fd8fca23375 142 void init() // initialises LCD and Gamepad
eencae 0:b7f1f47bb26a 143 {
helioslyons 10:19b250c7de5e 144 lcd.init();
helioslyons 10:19b250c7de5e 145 pad.init();
eencae 0:b7f1f47bb26a 146 }
eencae 0:b7f1f47bb26a 147
helioslyons 11:1fd8fca23375 148 void render() { // clears screen and re-draws content (+ current lives left)
helioslyons 11:1fd8fca23375 149 lcd.clear(); // called in the main game() loop
helioslyons 11:1fd8fca23375 150 battle.draw(lcd);
helioslyons 11:1fd8fca23375 151 lcd.refresh();
helioslyons 11:1fd8fca23375 152
helioslyons 11:1fd8fca23375 153 char buffer[4];
helioslyons 11:1fd8fca23375 154 int lives = sprintf(buffer,"%2d",battle.life());
helioslyons 11:1fd8fca23375 155 lcd.printString(buffer,0,5);
helioslyons 10:19b250c7de5e 156 lcd.refresh();
helioslyons 10:19b250c7de5e 157 }
helioslyons 10:19b250c7de5e 158
helioslyons 11:1fd8fca23375 159 void game(Gamepad &pad) { // main game loop, reads input and updates bullet / canon positions
helioslyons 11:1fd8fca23375 160 while (1) {
helioslyons 11:1fd8fca23375 161 int fps = 6;
helioslyons 11:1fd8fca23375 162 battle.read_input(pad);
helioslyons 11:1fd8fca23375 163 battle.update(pad);
helioslyons 11:1fd8fca23375 164 render();
helioslyons 11:1fd8fca23375 165 wait(1.0f/fps);
helioslyons 13:1472c1637bfc 166 if (battle.end() || battle.life() < 1 ) { break; } // end condition based on
helioslyons 13:1472c1637bfc 167 } // remaining invaders or number
helioslyons 13:1472c1637bfc 168 lcd.clear(); // of lives left
helioslyons 11:1fd8fca23375 169 }
helioslyons 11:1fd8fca23375 170
helioslyons 11:1fd8fca23375 171 void wave(int n) { // prints wave number and time taken so far
helioslyons 11:1fd8fca23375 172 lcd.clear();
helioslyons 13:1472c1637bfc 173
helioslyons 11:1fd8fca23375 174 char buffer[14];
helioslyons 11:1fd8fca23375 175 char buffer3[3];
helioslyons 11:1fd8fca23375 176 int number = sprintf(buffer,"Wave%2d",n);
helioslyons 11:1fd8fca23375 177 float time = sprintf(buffer3,"%.2f",playTime.read());
helioslyons 11:1fd8fca23375 178 lcd.printString(buffer,24,1);
helioslyons 11:1fd8fca23375 179 lcd.printString(buffer3,27,2);
helioslyons 11:1fd8fca23375 180
helioslyons 11:1fd8fca23375 181 lcd.refresh();
helioslyons 11:1fd8fca23375 182 wait(3.0);
helioslyons 11:1fd8fca23375 183 }
helioslyons 13:1472c1637bfc 184
helioslyons 13:1472c1637bfc 185 void waveState(int n, Gamepad &pad) {
helioslyons 13:1472c1637bfc 186 battle.clock(pad); // start the invader firing pattern
helioslyons 13:1472c1637bfc 187 wave(n); // print wave #
helioslyons 13:1472c1637bfc 188 playTime.start(); // start timer before game starts
helioslyons 13:1472c1637bfc 189 game(pad); // start the main game loop
helioslyons 13:1472c1637bfc 190 playTime.stop(); // stop timer
helioslyons 13:1472c1637bfc 191 }
helioslyons 11:1fd8fca23375 192
helioslyons 11:1fd8fca23375 193 void welcome() { // bitmap logo and title for start screen
helioslyons 11:1fd8fca23375 194 static int logo[] = {
helioslyons 11:1fd8fca23375 195 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,
helioslyons 11:1fd8fca23375 196 0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,
helioslyons 11:1fd8fca23375 197 0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,
helioslyons 11:1fd8fca23375 198 0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,
helioslyons 11:1fd8fca23375 199 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,
helioslyons 11:1fd8fca23375 200 0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,
helioslyons 11:1fd8fca23375 201 0,0,1,1,1,1,0,0,1,1,1,1,1,1,0,0,1,1,1,1,0,0,
helioslyons 11:1fd8fca23375 202 0,0,1,1,1,1,0,0,1,1,1,1,1,1,0,0,1,1,1,1,0,0,
helioslyons 11:1fd8fca23375 203 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
helioslyons 11:1fd8fca23375 204 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
helioslyons 11:1fd8fca23375 205 1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,
helioslyons 11:1fd8fca23375 206 1,1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,1,1,
helioslyons 11:1fd8fca23375 207 1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,
helioslyons 11:1fd8fca23375 208 1,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,1,
helioslyons 11:1fd8fca23375 209 0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,
helioslyons 11:1fd8fca23375 210 0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0
helioslyons 11:1fd8fca23375 211 };
helioslyons 10:19b250c7de5e 212
helioslyons 11:1fd8fca23375 213 Bitmap canon_sprite(logo, 16, 22);
helioslyons 11:1fd8fca23375 214 canon_sprite.render(lcd,30,4);
helioslyons 11:1fd8fca23375 215
helioslyons 11:1fd8fca23375 216 lcd.printString("Space Invaders",0,3);
helioslyons 11:1fd8fca23375 217 lcd.printString("Press Start",8,4);
helioslyons 10:19b250c7de5e 218 lcd.refresh();
helioslyons 11:1fd8fca23375 219
helioslyons 11:1fd8fca23375 220 while (pad.start_pressed() == false) { // LEDs flash until start is pressed
helioslyons 11:1fd8fca23375 221 lcd.setContrast(pad.read_pot1());
helioslyons 10:19b250c7de5e 222 pad.leds_on();
helioslyons 10:19b250c7de5e 223 wait(0.1);
helioslyons 10:19b250c7de5e 224 pad.leds_off();
helioslyons 10:19b250c7de5e 225 wait(0.1);
helioslyons 10:19b250c7de5e 226 }
helioslyons 11:1fd8fca23375 227 }
helioslyons 11:1fd8fca23375 228
helioslyons 11:1fd8fca23375 229 void menu(Gamepad &pad) {
helioslyons 11:1fd8fca23375 230 while(1) {
helioslyons 11:1fd8fca23375 231 pad.reset_buttons();
helioslyons 11:1fd8fca23375 232 lcd.clear();
helioslyons 11:1fd8fca23375 233 lcd.printString("(A) Play",0,1);
helioslyons 11:1fd8fca23375 234 lcd.printString("(X) How To",0,2);
helioslyons 11:1fd8fca23375 235 lcd.printString("(B) Credits",0,3);
helioslyons 11:1fd8fca23375 236 lcd.refresh();
helioslyons 11:1fd8fca23375 237
helioslyons 11:1fd8fca23375 238 if (pad.X_pressed()) {
helioslyons 13:1472c1637bfc 239 instructions(pad);
helioslyons 11:1fd8fca23375 240 }
helioslyons 11:1fd8fca23375 241 else if (pad.A_pressed()) {
helioslyons 11:1fd8fca23375 242 return;
helioslyons 11:1fd8fca23375 243 }
helioslyons 11:1fd8fca23375 244 else if (pad.B_pressed()) {
helioslyons 11:1fd8fca23375 245 lcd.clear();
helioslyons 11:1fd8fca23375 246 lcd.printString("Game by",0,2);
helioslyons 11:1fd8fca23375 247 lcd.printString("H.A. Lyons",0,3);
helioslyons 11:1fd8fca23375 248 lcd.refresh();
helioslyons 11:1fd8fca23375 249 wait(5.0);
helioslyons 11:1fd8fca23375 250 }
helioslyons 11:1fd8fca23375 251 }
helioslyons 13:1472c1637bfc 252 }
helioslyons 13:1472c1637bfc 253
helioslyons 13:1472c1637bfc 254 void instructions(Gamepad &pad) {
helioslyons 13:1472c1637bfc 255 lcd.clear();
helioslyons 13:1472c1637bfc 256 lcd.printString("Press A",0,2);
helioslyons 13:1472c1637bfc 257 lcd.printString("to shoot",0,3);
helioslyons 13:1472c1637bfc 258 lcd.refresh();
helioslyons 13:1472c1637bfc 259 wait(5.0);
helioslyons 13:1472c1637bfc 260
helioslyons 13:1472c1637bfc 261 lcd.clear();
helioslyons 13:1472c1637bfc 262 lcd.printString("Use the",0,1);
helioslyons 13:1472c1637bfc 263 lcd.printString("joystick to",0,2);
helioslyons 13:1472c1637bfc 264 lcd.printString("move left",0,3);
helioslyons 13:1472c1637bfc 265 lcd.printString("or right",0,4);
helioslyons 13:1472c1637bfc 266 lcd.refresh();
helioslyons 13:1472c1637bfc 267 wait(5.0);
helioslyons 13:1472c1637bfc 268
helioslyons 13:1472c1637bfc 269 lcd.clear();
helioslyons 13:1472c1637bfc 270 lcd.printString("Remaining ",0,1);
helioslyons 13:1472c1637bfc 271 lcd.printString("lives are on",0,2);
helioslyons 13:1472c1637bfc 272 lcd.printString("the bottom",0,3);
helioslyons 13:1472c1637bfc 273 lcd.printString("left",0,4);
helioslyons 13:1472c1637bfc 274 lcd.refresh();
helioslyons 13:1472c1637bfc 275 wait(5.0);
helioslyons 13:1472c1637bfc 276
helioslyons 13:1472c1637bfc 277 lcd.clear();
helioslyons 13:1472c1637bfc 278 lcd.printString("Elapsed time",0,1);
helioslyons 13:1472c1637bfc 279 lcd.printString("is shown at",0,2);
helioslyons 13:1472c1637bfc 280 lcd.printString("each wave",0,3);
helioslyons 13:1472c1637bfc 281 lcd.printString("interval",0,4);
helioslyons 13:1472c1637bfc 282 lcd.refresh();
helioslyons 13:1472c1637bfc 283 wait(5.0);
helioslyons 13:1472c1637bfc 284 }