In this episode we will add the explosion animations for the Invaders. I did mention we’d add in the scoring as well but on reflection I’m going to leave that for a later episode because I do not want to put two different items in this article and scoring would be better suited when we have more game elements.
As usual the full code is below (expand and copy as required).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 |
#include <Adafruit_SSD1306.h> #include <Adafruit_GFX.h> // DISPLAY SETTINGS #define OLED_ADDRESS 0x3C #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 // Input settings #define FIRE_BUT 6 #define RIGHT_BUT 5 #define LEFT_BUT 4 // Alien Settings #define NUM_ALIEN_COLUMNS 7 #define NUM_ALIEN_ROWS 3 #define X_START_OFFSET 6 #define SPACE_BETWEEN_ALIEN_COLUMNS 5 #define LARGEST_ALIEN_WIDTH 11 #define SPACE_BETWEEN_ROWS 9 #define INVADERS_DROP_BY 4 // pixel amount that invaders move down by #define INVADERS_SPEED 12 // speed of movement, lower=faster. #define INVADER_HEIGHT 8 #define EXPLOSION_GFX_TIME 7 // How long an ExplosionGfx remains on screen before dissapearing // Player settingsc #define TANKGFX_WIDTH 13 #define TANKGFX_HEIGHT 8 #define PLAYER_X_MOVE_AMOUNT 2 #define PLAYER_Y_START 56 #define PLAYER_X_START 0 #define MISSILE_HEIGHT 4 #define MISSILE_WIDTH 1 #define MISSILE_SPEED 4 // Status of a game object constants #define ACTIVE 0 #define EXPLODING 1 #define DESTROYED 2 // graphics // aliens const unsigned char InvaderTopGfx [] PROGMEM = { B00011000, B00111100, B01111110, B11011011, B11111111, B00100100, B01011010, B10100101 }; const unsigned char InvaderTopGfx2 [] PROGMEM = { B00011000, B00111100, B01111110, B11011011, B11111111, B01011010, B10000001, B01000010 }; const unsigned char PROGMEM InvaderMiddleGfx []= { B00100000,B10000000, B00010001,B00000000, B00111111,B10000000, B01101110,B11000000, B11111111,B11100000, B10111111,B10100000, B10100000,B10100000, B00011011,B00000000 }; const unsigned char PROGMEM InvaderMiddleGfx2 [] = { B00100000,B10000000, B00010001,B00000000, B10111111,B10100000, B10101110,B10100000, B11111111,B11100000, B00111111,B10000000, B00100000,B10000000, B01000000,B01000000 }; const unsigned char PROGMEM InvaderBottomGfx [] = { B00001111,B00000000, B01111111,B11100000, B11111111,B11110000, B11100110,B01110000, B11111111,B11110000, B00111001,B11000000, B01100110,B01100000, B00110000,B11000000 }; const unsigned char PROGMEM InvaderBottomGfx2 [] = { B00001111,B00000000, B01111111,B11100000, B11111111,B11110000, B11100110,B01110000, B11111111,B11110000, B00111001,B11000000, B01000110,B00100000, B10000000,B00010000 }; static const unsigned char PROGMEM ExplosionGfx [] = { B00001000,B10000000, B01000101,B00010000, B00100000,B00100000, B00010000,B01000000, B11000000,B00011000, B00010000,B01000000, B00100101,B00100000, B01001000,B10010000 }; // Player grafix const unsigned char PROGMEM TankGfx [] = { B00000010,B00000000, B00000111,B00000000, B00000111,B00000000, B01111111,B11110000, B11111111,B11111000, B11111111,B11111000, B11111111,B11111000, B11111111,B11111000, }; static const unsigned char PROGMEM MissileGfx [] = { B10000000, B10000000, B10000000, B10000000 }; // Game structures struct GameObjectStruct { // base object which most other objects will include signed int X; signed int Y; unsigned char Status; //0 active, 1 exploding, 2 destroyed }; struct AlienStruct { GameObjectStruct Ord; unsigned char ExplosionGfxCounter; // how long we want the ExplosionGfx to last }; struct PlayerStruct { GameObjectStruct Ord; }; // general global variables Adafruit_SSD1306 display(1); //alien global vars //The array of aliens across the screen AlienStruct Alien[NUM_ALIEN_COLUMNS][NUM_ALIEN_ROWS]; // widths of aliens // as aliens are the same type per row we do not need to store their graphic width per alien in the structure above // that would take a byte per alien rather than just three entries here, 1 per row, saving significnt memory byte AlienWidth[]={8,11,12}; // top, middle ,bottom widths char AlienXMoveAmount=2; signed char InvadersMoveCounter; // counts down, when 0 move invaders, set according to how many aliens on screen bool AnimationFrame=false; // two frames of animation, if true show one if false show the other // Player global variables PlayerStruct Player; GameObjectStruct Missile; void setup() { display.begin(SSD1306_SWITCHCAPVCC,OLED_ADDRESS); InitAliens(0); InitPlayer(); pinMode(RIGHT_BUT, INPUT_PULLUP); pinMode(LEFT_BUT, INPUT_PULLUP); pinMode(FIRE_BUT, INPUT_PULLUP); } void loop() { Physics(); UpdateDisplay(); } void Physics() { AlienControl(); PlayerControl(); MissileControl(); CheckCollisions(); } void PlayerControl() { // user input checks if((digitalRead(RIGHT_BUT)==0)&(Player.Ord.X+TANKGFX_WIDTH<SCREEN_WIDTH)) Player.Ord.X+=PLAYER_X_MOVE_AMOUNT; if((digitalRead(LEFT_BUT)==0)&(Player.Ord.X>0)) Player.Ord.X-=PLAYER_X_MOVE_AMOUNT; if((digitalRead(FIRE_BUT)==0)&(Missile.Status!=ACTIVE)) { Missile.X=Player.Ord.X+(TANKGFX_WIDTH/2); Missile.Y=PLAYER_Y_START; Missile.Status=ACTIVE; } } void MissileControl() { if(Missile.Status==ACTIVE) { Missile.Y-=MISSILE_SPEED; if(Missile.Y+MISSILE_HEIGHT<0) // If off top of screen destroy so can be used again Missile.Status=DESTROYED; } } void AlienControl() { if((InvadersMoveCounter--)<0) { bool Dropped=false; if((RightMostPos()+AlienXMoveAmount>=SCREEN_WIDTH) |(LeftMostPos()+AlienXMoveAmount<0)) // at edge of screen { AlienXMoveAmount=-AlienXMoveAmount; // reverse direction Dropped=true; // and indicate we are dropping } // update the alien postions for(int Across=0;Across<NUM_ALIEN_COLUMNS;Across++) { for(int Down=0;Down<3;Down++) { if(Alien[Across][Down].Ord.Status==ACTIVE) { if(Dropped==false) Alien[Across][Down].Ord.X+=AlienXMoveAmount; else Alien[Across][Down].Ord.Y+=INVADERS_DROP_BY; } } } InvadersMoveCounter=INVADERS_SPEED; AnimationFrame=!AnimationFrame; ///swap to other frame } } void CheckCollisions() { MissileAndAlienCollisions(); } void MissileAndAlienCollisions() { for(int across=0;across<NUM_ALIEN_COLUMNS;across++) { for(int down=0;down<NUM_ALIEN_ROWS;down++) { if(Alien[across][down].Ord.Status==ACTIVE) { if(Missile.Status==ACTIVE) { if(Collision(Missile,MISSILE_WIDTH,MISSILE_HEIGHT,Alien[across][down].Ord,AlienWidth[down],INVADER_HEIGHT)) { // missile hit Alien[across][down].Ord.Status=EXPLODING; Missile.Status=DESTROYED; } } } } } } bool Collision(GameObjectStruct Obj1,unsigned char Width1,unsigned char Height1,GameObjectStruct Obj2,unsigned char Width2,unsigned char Height2) { return ((Obj1.X+Width1>Obj2.X)&(Obj1.X<Obj2.X+Width2)&(Obj1.Y+Height1>Obj2.Y)&(Obj1.Y<Obj2.Y+Height2)); } int RightMostPos() { //returns x pos of right most alien int Across=NUM_ALIEN_COLUMNS-1; int Down; int Largest=0; int RightPos; while(Across>=0){ Down=0; while(Down<NUM_ALIEN_ROWS){ if(Alien[Across][Down].Ord.Status==ACTIVE) { // different aliens have different widths, add to x pos to get rightpos RightPos= Alien[Across][Down].Ord.X+AlienWidth[Down]; if(RightPos>Largest) Largest=RightPos; } Down++; } if(Largest>0) // we have found largest for this coloum return Largest; Across--; } return 0; // should never get this far } int LeftMostPos() { //returns x pos of left most alien int Across=0; int Down; int Smallest=SCREEN_WIDTH*2; while(Across<NUM_ALIEN_COLUMNS){ Down=0; while(Down<3){ if(Alien[Across][Down].Ord.Status==ACTIVE) if(Alien[Across][Down].Ord.X<Smallest) Smallest=Alien[Across][Down].Ord.X; Down++; } if(Smallest<SCREEN_WIDTH*2) // we have found smalest for this coloum return Smallest; Across++; } return 0; // should nevr get this far } void UpdateDisplay() { display.clearDisplay(); for(int across=0;across<NUM_ALIEN_COLUMNS;across++) { for(int down=0;down<NUM_ALIEN_ROWS;down++) { if(Alien[across][down].Ord.Status==ACTIVE){ switch(down) { case 0: if(AnimationFrame) display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, InvaderTopGfx, AlienWidth[down],INVADER_HEIGHT,WHITE); else display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, InvaderTopGfx2, AlienWidth[down],INVADER_HEIGHT,WHITE); break; case 1: if(AnimationFrame) display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, InvaderMiddleGfx, AlienWidth[down],INVADER_HEIGHT,WHITE); else display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, InvaderMiddleGfx2, AlienWidth[down],INVADER_HEIGHT,WHITE); break; default: if(AnimationFrame) display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, InvaderBottomGfx, AlienWidth[down],INVADER_HEIGHT,WHITE); else display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, InvaderBottomGfx2, AlienWidth[down],INVADER_HEIGHT,WHITE); } } else { if(Alien[across][down].Ord.Status==EXPLODING){ Alien[across][down].ExplosionGfxCounter--; if(Alien[across][down].ExplosionGfxCounter>0) { display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, ExplosionGfx, 13, 8,WHITE); } else Alien[across][down].Ord.Status=DESTROYED; } } } } // player display.drawBitmap(Player.Ord.X, Player.Ord.Y, TankGfx, TANKGFX_WIDTH, TANKGFX_HEIGHT,WHITE); //missile if(Missile.Status==ACTIVE) display.drawBitmap(Missile.X, Missile.Y, MissileGfx, MISSILE_WIDTH, MISSILE_HEIGHT,WHITE); display.display(); } void InitPlayer() { Player.Ord.Y=PLAYER_Y_START; Player.Ord.X=PLAYER_X_START; Missile.Status=DESTROYED; } void InitAliens(int YStart) { for(int across=0;across<NUM_ALIEN_COLUMNS;across++) { for(int down=0;down<3;down++) { // we add down to centralise the aliens, just happens to be the right value we need per row! // we need to adjust a little as row zero should be 2, row 1 should be 1 and bottom row 0 Alien[across][down].Ord.X=X_START_OFFSET+(across*(LARGEST_ALIEN_WIDTH+SPACE_BETWEEN_ALIEN_COLUMNS))-(AlienWidth[down]/2); Alien[across][down].Ord.Y=YStart+(down*SPACE_BETWEEN_ROWS); Alien[across][down].Ord.Status=ACTIVE; Alien[across][down].ExplosionGfxCounter=EXPLOSION_GFX_TIME; } } } |
The length of an explosion
The amount of time the explosion graphic is on screen is determined by this constant:
24 |
#define EXPLOSION_GFX_TIME 7 // How long an ExplosionGfx remains on screen before dissapearing |
The higher the number the longer the graphic remains on screen.
A new object status
An object’s status could be either ACTIVE or DESTROYED but now (if you look below) we have introduced a new status called EXPLODING
38 39 40 |
#define ACTIVE 0 #define EXPLODING 1 #define DESTROYED 2 |
This code allows the UpdateDisplay routine to know when it should be displaying the explosion graphic for that object rather than its normal graphic.
The explosion graphic
The definition for the explosion starts at line 112 and will not be discussed further as graphics were covered in a earlier episode.
The Alien’s structure
153 154 155 156 |
struct AlienStruct { GameObjectStruct Ord; unsigned char ExplosionGfxCounter; // how long we want the ExplosionGfx to last }; |
We have added the variable to time how long the explosion remains on this screen for this Alien. Initially when it is hit it will be filled with the EXPLOSION_GFX_TIME constant value and then decremented for every time the code loops round its main loop. We’ll discuss this further shortly.
Missile Collisions
Previously within the routine MissileAndAlienCollisions we had a line that read
Alien[across][down].Ord.Status=DESTROYED;
Now (on line 288) we have changed this to
Alien[across][down].Ord.Status=EXPLODING;
To indicate that this Alien is exploding – not yet destroyed.
Displaying the explosions
In the UpdateDisplay routine we have added these lines at line 375
375 376 377 378 379 380 381 382 383 384 |
else { if(Alien[across][down].Ord.Status==EXPLODING){ Alien[across][down].ExplosionGfxCounter--; if(Alien[across][down].ExplosionGfxCounter>0) { display.drawBitmap(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, ExplosionGfx, 13, 8,WHITE); } else Alien[across][down].Ord.Status=DESTROYED; } } |
This code is executed if the Invader is not active (decided on line 354 – not shown here but in main code at the start of the article). Looking above we can see that if the Invader has the status of EXPLODING then we go on to decrement the explosion counter. Then we decide whether to display the explosion or mark the Invader as destroyed. If the explosion counter (ExplosionGfxCounter) is more than 0 we display the explosion else we mark the Invader as DESTROYED and then nothing else will happen for this Invader for this wave of Invaders.
Invader Initialisation
We need to add a couple of lines to the initialisation code:
404 405 406 407 408 409 410 411 412 413 414 415 |
void InitAliens(int YStart) { for(int across=0;across<NUM_ALIEN_COLUMNS;across++) { for(int down=0;down<3;down++) { // we add down to centralise the aliens, just happens to be the right value we need per row! // we need to adjust a little as row zero should be 2, row 1 should be 1 and bottom row 0 Alien[across][down].Ord.X=X_START_OFFSET+(across*(LARGEST_ALIEN_WIDTH+SPACE_BETWEEN_ALIEN_COLUMNS))-(AlienWidth[down]/2); Alien[across][down].Ord.Y=YStart+(down*SPACE_BETWEEN_ROWS); Alien[across][down].Ord.Status=ACTIVE; Alien[across][down].ExplosionGfxCounter=EXPLOSION_GFX_TIME; } } } |
Lines 411-412 are the new lines showing that we are setting the Invader Status to ACTIVE (this should have been included prior to this episode to be fair) and we also set the explosion counter to its starting point ready for an explosion to happen.
That’s it for now, next time we’ll look at adding the mothership that goes across the screen.
Enjoy and Learn 🙂