Nano_Vaders
Space Invaders for the Nano Every using a 2.7" OLED Display
Components and supplies
1
2.7 Inch 128x64 White OLED display
1
Audio Transducer
1
Vero Board (Strip Board)
1
Push Buttons
1
Arduino Nano Every
Tools and machines
1
Soldering iron (generic)
1
Solder Wire, Lead Free
Apps and platforms
1
Arduino IDE
Project description
Code
Nano Vaders
arduino
Sketch for my Nano Vaders game.
1// Space Invaders game based on the work of 2// http://www.xtronical.com/projects/space-invaders/ 3// and Ricardo Moreno (check out youtube video) 4// 5// Uses SSD1325 SPI OLED display 128x64 6// 7// MEGA/LEONARDO/NANO EVERY Only. Needs the Extra Ram 8 9 10#include <SPI.h> 11#include <U8g2lib.h> 12#include <EEPROM.h> 13 14// DISPLAY SETTINGS 15#define SCREEN_WIDTH 128 16#define SCREEN_HEIGHT 64 17 18#define OLED_CLK 13 19#define OLED_MOSI 11 20#define OLED_CS 3 21#define OLED_DC 2 22#define OLED_RST 8 23 24 25U8G2_SSD1325_NHD_128X64_F_4W_HW_SPI display(U8G2_R0, OLED_CS, OLED_DC, OLED_RST); // 2.7 inch White/Black 26 27 28 29// Input settings 30#define FIRE_BUT 6 31#define RIGHT_BUT 5 32#define LEFT_BUT 4 33 34// Mothership 35#define MOTHERSHIP_HEIGHT 3 36#define MOTHERSHIP_WIDTH 7 37#define MOTHERSHIP_SPEED 1 38#define MOTHERSHIP_SPAWN_CHANCE 150 // Was 150 39#define DISPLAY_MOTHERSHIP_BONUS_TIME 20 // how long bonus stays on screen for displaying mothership 40 41// Alien Settings 42#define ALIEN_HEIGHT 3 // Was 8 43#define NUM_ALIEN_COLUMNS 11 // Was 7 44#define NUM_ALIEN_ROWS 5 45#define X_START_OFFSET 26 46#define SPACE_BETWEEN_ALIEN_COLUMNS 2 47#define LARGEST_ALIEN_WIDTH 5 48#define SPACE_BETWEEN_ROWS 6 49#define INVADERS_DROP_BY 2 // Was 4 50#define INVADERS_SPEED 10 // Was 6 51#define INVADER_HEIGHT 3 // Was 8 52#define EXPLOSION_GFX_TIME 10 // Was 7 53#define AMOUNT_TO_DROP_BY_PER_LEVEL 4 // NEW How much farther down aliens start per new level 54#define LEVEL_TO_RESET_TO_START_HEIGHT 4 // EVERY MULTIPLE OF THIS LEVEL THE ALIEN y START POSITION WILL RESET TO TOP 55#define ALIEN_X_MOVE_AMOUNT 1 // Was 1 56#define CHANCEOFBOMBDROPPING 40 // Was 40 57#define BOMB_HEIGHT 4 58#define BOMB_WIDTH 2 59#define MAXBOMBS 3 // Was 3 60#define CHANCE_OF_BOMB_DAMAGE_TO_LEFT_OR_RIGHT 20 // higher more chance 61#define CHANCE_OF_BOMB_PENETRATING_DOWN 1 // higher more chance 62 63// Player settingsc 64#define TANKGFX_WIDTH 5 65#define TANKGFX_HEIGHT 3 66#define PLAYER_X_MOVE_AMOUNT 2 // Was 5 67#define LIVES 3 // NEW 68#define PLAYER_EXPLOSION_TIME 10 // How long an ExplosionGfx remains on screen before dissapearing 69#define PLAYER_Y_START 61 70#define PLAYER_X_START 20 71#define BASE_WIDTH 8 72#define BASE_HEIGHT 6 73#define BASE_Y 53 74#define NUM_BASES 4 75 76#define MISSILE_HEIGHT 4 77#define MISSILE_WIDTH 1 78#define MISSILE_SPEED 3 // Was 5 79 80// Status of a game object constants 81#define ACTIVE 0 82#define EXPLODING 1 83#define DESTROYED 2 84 85unsigned long lastMillis; 86unsigned long frameCount; 87unsigned int framesPerSecond; 88 89 90// background dah dah dah sound setting 91#define NOTELENGTH 1 // larger means play note longer 92 93 94 95// graphics 96// aliens 97 98 const unsigned char MotherShipGfx [] PROGMEM = { 99 B01111100, 100 B10111010, 101 B11101110 102}; 103 104const unsigned char InvaderTopGfx [] PROGMEM = { 105 B00000010, 106 B00000111, 107 B00000101, 108}; 109 110const unsigned char InvaderTopGfx2 [] PROGMEM = { 111 B00000101, 112 B00000111, 113 B00000010, 114}; 115 116const unsigned char PROGMEM InvaderMiddleGfx []= 117{ 118 B00001010, 119 B00011111, 120 B00010101, 121}; 122 123const unsigned char PROGMEM InvaderMiddleGfx2 [] = { 124 B00010101, 125 B00011111, 126 B00001010, 127}; 128 129const unsigned char PROGMEM InvaderBottomGfx [] = { 130 B00001010, 131 B00010101, 132 B00011011, 133}; 134 135const unsigned char PROGMEM InvaderBottomGfx2 [] = { 136 B00011011, 137 B00010101, 138 B00001110, 139}; 140 141static const unsigned char PROGMEM ExplosionGfx [] = { 142 B00010101, 143 B00001010, 144 B00010101, 145}; 146 147// Player grafix 148const unsigned char PROGMEM TankGfx [] = { 149 B00000100, 150 B00011111, 151 B00011111, 152}; 153 154static const unsigned char PROGMEM MissileGfx [] = { 155 B00000001, 156 B00000001, 157 B00000001, 158 B00000001, 159}; 160 161static const unsigned char PROGMEM AlienBombGfx [] = { 162 B00000010, 163 B00000001, 164 B00000010, 165 B00000001, 166}; 167 168static const unsigned char PROGMEM BaseGfx [] = { 169 B00111100, 170 B01111110, 171 B11111111, 172 B11111111, 173 B11100111, 174 B11000011, 175}; 176 177 178// Game structures 179 180struct GameObjectStruct { 181 // base object which most other objects will include 182 signed int X; 183 signed int Y; 184 unsigned char Status; //0 active, 1 exploding, 2 destroyed 185}; 186 187 188struct BaseStruct { 189 GameObjectStruct Ord; 190 unsigned char Gfx[6]; 191}; 192 193 194struct AlienStruct { 195 GameObjectStruct Ord; 196 unsigned char ExplosionGfxCounter; // how long we want the ExplosionGfx to last 197}; 198 199struct PlayerStruct { 200 GameObjectStruct Ord; 201 unsigned int Score; 202 unsigned char Lives; 203 unsigned char Level; 204 unsigned char AliensDestroyed; // count of how many killed so far 205 unsigned char AlienSpeed; // higher the number slower they go, calculated when ever alien destroyed 206 unsigned char ExplosionGfxCounter; // how long we want the ExplosionGfx to last 207}; 208 209 210// general global variables 211 212 213//alien global vars 214//The array of aliens across the screen 215 216AlienStruct Alien[NUM_ALIEN_COLUMNS][NUM_ALIEN_ROWS]; 217AlienStruct MotherShip; 218GameObjectStruct AlienBomb[MAXBOMBS]; 219BaseStruct Base[NUM_BASES]; 220 221static const int TOTAL_ALIENS=NUM_ALIEN_COLUMNS*NUM_ALIEN_ROWS; //NEW 222 223 224// widths of aliens 225// as aliens are the same type per row we do not need to store their graphic width per alien in the structure above 226// that would take a byte per alien rather than just three entries here, 1 per row, saving significnt memory 227byte AlienWidth[]={5,5,5,5,5}; // top, middle ,bottom widths 228 229 230char AlienXMoveAmount=2; 231signed char InvadersMoveCounter; // counts down, when 0 move invaders, set according to how many aliens on screen 232bool AnimationFrame=false; // two frames of animation, if true show one if false show the other 233 234// Mothership 235signed char MotherShipSpeed; 236unsigned int MotherShipBonus; 237signed int MotherShipBonusXPos; // pos to display bonus at 238unsigned char MotherShipBonusCounter; // how long bonus amount left on screen 239 240 241// Player global variables 242PlayerStruct Player; 243GameObjectStruct Missile; 244 245// game variables 246unsigned int HiScore; 247bool GameInPlay=false; 248 249// Sound settings and variables 250 251// music (dah dah dah sound) control, these are the "pitch" of the four basic notes 252const unsigned char Music[] = { 253 160,100,80,62 254}; 255 256unsigned char MusicIndex; // index pointer to next note to play 257unsigned MusicCounter; // how long note plays for countdown timer, is set to 258 // NOTELENGTH define above 259bool ShootCompleted=true; // stops music when this is false, so we can here shoot sound 260 261 262void setup() 263{ 264 display.begin(); 265 display.clear(); 266 display.setBitmapMode(1); 267 268 InitAliens(0); 269 InitPlayer(); 270 271 pinMode(RIGHT_BUT, INPUT_PULLUP); 272 pinMode(LEFT_BUT, INPUT_PULLUP); 273 pinMode(FIRE_BUT, INPUT_PULLUP); 274 275 display.setFont(u8g2_font_t0_11b_tf); 276 display.setDrawColor(1); //set the color 277 278 EEPROM.get(0,HiScore); 279 if(HiScore==65535) // new unit never written to 280 { 281 HiScore=0; 282 EEPROM.put(0,HiScore); 283 } 284} 285 286void loop() 287{ 288 if(GameInPlay) //NEW 289 { 290 Physics(); 291 UpdateDisplay(); 292 } 293 else 294 AttractScreen(); 295} 296 297 298// 3x5 pixel 7 Seg font 299const uint8_t digit[10][5] PROGMEM = { 300 B00000111, B00000101, B00000101, B00000101, B00000111, 301 B00000100, B00000100, B00000100, B00000100, B00000100, 302 B00000111, B00000100, B00000111, B00000001, B00000111, 303 B00000111, B00000100, B00000111, B00000100, B00000111, 304 B00000101, B00000101, B00000111, B00000100, B00000100, 305 B00000111, B00000001, B00000111, B00000100, B00000111, 306 B00000111, B00000001, B00000111, B00000101, B00000111, 307 B00000111, B00000100, B00000100, B00000100, B00000100, 308 B00000111, B00000101, B00000111, B00000101, B00000111, 309 B00000111, B00000101, B00000111, B00000100, B00000111, 310 }; 311 312 313void draw_digit(int16_t x, int16_t y, uint8_t n) { 314display.drawXBMP(x, y, 3, 5, digit[n % 10] ); 315} 316 317void print_int(int16_t x, int16_t y, uint16_t s, uint8_t digit) { 318 x += 4 * (digit - 1); 319 for (; digit > 0; digit--) { 320 draw_digit(x, y, s % 10); 321 x -= 4; 322 s /= 10; 323 } 324} 325 326 327void AttractScreen() 328{ 329 display.clearBuffer(); 330 331 CentreText("Play",8); 332 CentreText("Space Invaders",20); 333 CentreText("Press Fire to start",32); 334 CentreText("Hi Score ",44); 335 display.setCursor(80,44); 336 display.print(HiScore); 337 338 display.sendBuffer(); 339 340 if(digitalRead(FIRE_BUT)==0){ 341 GameInPlay=true; 342 NewGame(); 343 } 344 345 // CHECK FOR HIGH SCORE RESET, PLAYER MUST HOLD LEFT AND RIGHT TOGETHER 346 if((digitalRead(LEFT_BUT)==0)&(digitalRead(RIGHT_BUT)==0)) 347 { 348 // Reset high score, don't need to worry about debounce as will ony write to EEPROM if changed, so writes a 0 then won't write again 349 // if hiscore still 0 which it will be 350 HiScore=0; 351 EEPROM.put(0,HiScore); 352 } 353} 354 355void Physics() { 356 if(Player.Ord.Status==ACTIVE) { 357 AlienControl(); 358 MotherShipPhysics(); 359 PlayerControl(); 360 MissileControl(); 361 CheckCollisions(); 362 } 363} 364 365unsigned char GetScoreForAlien(int RowNumber) 366{ 367 // returns value for killing an alien at the row indicated 368 switch (RowNumber) 369 { 370 case 0:return 30; 371 case 1:return 20; 372 case 2:return 10; 373 case 3:return 5; 374 case 4:return 2; 375 376 } 377} 378 379void MotherShipPhysics() { 380 if(MotherShip.Ord.Status==ACTIVE) { // spawned, move it 381 tone(10, ( MotherShip.Ord.X % 8)*500,200); 382 MotherShip.Ord.X+=MotherShipSpeed; 383 if(MotherShipSpeed>0) // going left to right , check if off right hand side 384 { 385 if(MotherShip.Ord.X>=(SCREEN_WIDTH-26)) 386 { 387 MotherShip.Ord.Status=DESTROYED; 388 noTone(10); 389 } 390 } 391 else // going right to left , check if off left hand side 392 { 393 if(MotherShip.Ord.X+MOTHERSHIP_WIDTH<28) 394 { 395 MotherShip.Ord.Status=DESTROYED; 396 noTone(10); 397 } 398 } 399 400 } 401 else { 402 // try to spawn mothership 403 if(random(MOTHERSHIP_SPAWN_CHANCE)==1) 404 { 405 MotherShip.Ord.Status=ACTIVE; // Spawn a mother ship, starts just off screen at top 406 if(random(2)==1) // need to set direction 407 { 408 MotherShip.Ord.X=(SCREEN_WIDTH-20-MOTHERSHIP_WIDTH); 409 MotherShipSpeed=-MOTHERSHIP_SPEED; // if we go in here swaps to right to left 410 } 411 else 412 { 413 MotherShip.Ord.X=20; // -MOTHERSHIP_WIDTH; 414 MotherShipSpeed=MOTHERSHIP_SPEED; // set to go left ot right 415 } 416 } 417 } 418} 419 420void PlayerControl() { 421 // user input checks 422 if((digitalRead(RIGHT_BUT)==0)&(Player.Ord.X+TANKGFX_WIDTH<(SCREEN_WIDTH-20))) 423 Player.Ord.X+=PLAYER_X_MOVE_AMOUNT; 424 if((digitalRead(LEFT_BUT)==0)&(Player.Ord.X>PLAYER_X_START)) 425 Player.Ord.X-=PLAYER_X_MOVE_AMOUNT; 426 if((digitalRead(FIRE_BUT)==0)&(Missile.Status!=ACTIVE)) 427 { 428 tone(10, 1000,100); 429 Missile.X=Player.Ord.X+(TANKGFX_WIDTH/2); 430 Missile.Y=PLAYER_Y_START; 431 Missile.Status=ACTIVE; 432 } 433} 434 435void MissileControl() 436{ 437 if(Missile.Status==ACTIVE) 438 { 439 Missile.Y-=MISSILE_SPEED; 440 if(Missile.Y+MISSILE_HEIGHT<0) // If off top of screen destroy so can be used again 441 Missile.Status=DESTROYED; 442 } 443} 444 445 446void AlienControl() 447{ 448 if((InvadersMoveCounter--)<0) 449 { 450 bool Dropped=false; 451 if((RightMostPos()+AlienXMoveAmount>=(SCREEN_WIDTH-18)) |(LeftMostPos()+AlienXMoveAmount<21)) // at edge of screen 452 { 453 AlienXMoveAmount=-AlienXMoveAmount; // reverse direction 454 Dropped=true; // and indicate we are dropping 455 } 456 457 // play background music note if other higher priority sounds not playing 458 if((ShootCompleted)&(MotherShip.Ord.Status!=ACTIVE)) { 459 tone(10, Music[MusicIndex],100); 460 MusicIndex++; 461 if(MusicIndex==sizeof(Music)) 462 MusicIndex=0; 463 MusicCounter=NOTELENGTH; 464 } 465 // update the alien postions 466 for(int Across=0;Across<NUM_ALIEN_COLUMNS;Across++) 467 { 468 for(int Down=0;Down<NUM_ALIEN_ROWS;Down++) 469 { 470 if(Alien[Across][Down].Ord.Status==ACTIVE) 471 { 472 if(Dropped==false) 473 Alien[Across][Down].Ord.X+=AlienXMoveAmount; 474 else 475 Alien[Across][Down].Ord.Y+=INVADERS_DROP_BY; 476 } 477 } 478 } 479 InvadersMoveCounter=Player.AlienSpeed; 480 AnimationFrame=!AnimationFrame; //swap to other frame 481 } 482 // should the alien drop a bomb 483 if(random(CHANCEOFBOMBDROPPING)==1) 484 DropBomb(); 485 MoveBombs(); 486} 487 488 489void MoveBombs(){ 490 for(int i=0;i<MAXBOMBS;i++){ 491 if(AlienBomb[i].Status==ACTIVE) 492 AlienBomb[i].Y+=1; 493 } 494} 495 496 497void DropBomb() { 498 // if there is a free bomb slot then drop a bomb else nothing happens 499 bool Free=false; 500 unsigned char ActiveCols[NUM_ALIEN_COLUMNS]; 501 unsigned char BombIdx=0; 502 // find a free bomb slot 503 while((Free==false)&(BombIdx<MAXBOMBS)){ 504 if(AlienBomb[BombIdx].Status==DESTROYED) 505 Free=true; 506 else 507 BombIdx++; 508 } 509 if(Free) { 510 unsigned char Columns=0; 511 // now pick and alien at random to drop the bomb 512 // we first pick a column, obviously some columns may not exist, so we count number of remaining cols 513 // first, this adds time but then also picking columns randomly that don't exist may take time also 514 unsigned char ActiveColCount=0; 515 signed char Row; 516 unsigned char ChosenColumn; 517 518 while(Columns<NUM_ALIEN_COLUMNS){ 519 Row=(NUM_ALIEN_ROWS-1); 520 while(Row>=0) { 521 if(Alien[Columns][Row].Ord.Status==ACTIVE) 522 { 523 ActiveCols[ActiveColCount]=Columns; 524 ActiveColCount++; 525 break; 526 } 527 Row--; 528 } 529 Columns++; 530 } 531 // we have ActiveCols array filled with the column numbers of the active cols and we have how many 532 // in ActiveColCount, now choose a column at random 533 ChosenColumn=random(ActiveColCount); // random number between 0 and the amount of columns 534 // we now find the first available alien in this column to drop the bomb from 535 Row=(NUM_ALIEN_ROWS-1); 536 while(Row>=0) { 537 if(Alien[ActiveCols[ChosenColumn]][Row].Ord.Status==ACTIVE) { 538 // Set the bomb from this alien 539 AlienBomb[BombIdx].Status=ACTIVE; 540 AlienBomb[BombIdx].X=Alien[ActiveCols[ChosenColumn]][Row].Ord.X+int(AlienWidth[Row]/2); 541 // above sets bomb to drop around invaders centre, here we add a litrle randomness around this pos 542 AlienBomb[BombIdx].X=(AlienBomb[BombIdx].X-2)+random(0,4); 543 AlienBomb[BombIdx].Y=Alien[ActiveCols[ChosenColumn]][Row].Ord.Y+4; 544 break; 545 } 546 Row--; 547 } 548 } 549} 550 551 552 553void BombCollisions() 554{ 555 //check bombs collisions 556 for(int i=0;i<MAXBOMBS;i++) 557 { 558 if(AlienBomb[i].Status==ACTIVE) 559 { 560 if(AlienBomb[i].Y>64) // gone off bottom of screen 561 AlienBomb[i].Status=DESTROYED; 562 else 563 { 564 // HAS IT HIT PLAYERS missile 565 if(Collision(AlienBomb[i],BOMB_WIDTH,BOMB_HEIGHT,Missile,MISSILE_WIDTH,MISSILE_HEIGHT)) 566 { 567 // destroy missile and bomb 568 AlienBomb[i].Status=EXPLODING; 569 Missile.Status=DESTROYED; 570 } 571 else 572 { 573 // has it hit players ship 574 if(Collision(AlienBomb[i],BOMB_WIDTH,BOMB_HEIGHT,Player.Ord,TANKGFX_WIDTH,TANKGFX_HEIGHT)) 575 { 576 PlayerHit(); 577 AlienBomb[i].Status=DESTROYED; 578 } 579 else 580 BombAndBasesCollision(&AlienBomb[i]); 581 } 582 } 583 } 584 } 585} 586 587void BombAndBasesCollision(GameObjectStruct *Bomb) { // check and handle any bomb and base collision 588 589 for(int i=0;i<NUM_BASES;i++) 590 { 591 if(Collision(*Bomb,BOMB_WIDTH,BOMB_HEIGHT,Base[i].Ord,BASE_WIDTH,BASE_HEIGHT)) 592 { 593 unsigned char X=Bomb->X-Base[i].Ord.X; 594 signed char Bomb_Y=(Bomb->Y+BOMB_HEIGHT)-Base[i].Ord.Y; 595 unsigned char Base_Y=0; 596 597 while((Base_Y <= Bomb_Y) & (Base_Y<BASE_HEIGHT) & (Bomb->Status==ACTIVE)) 598 { 599 unsigned char Idx=(Base_Y); // this gets the index for the byte in question from the gfx array 600 unsigned char TheByte=Base[i].Gfx[Idx]; // we now have the byte to inspect, but need to only look at the 2 bits where the bomb is colliding 601 unsigned char Mask=B00000011; 602 if (X>6) {X=6; }; 603 Mask=(Mask<<(X)); 604 TheByte=TheByte & Mask; 605 606 if(TheByte>0) 607 { 608 Mask=~Mask; 609 Base[i].Gfx[Idx]=Base[i].Gfx[Idx] & Mask; 610 if (random(CHANCE_OF_BOMB_PENETRATING_DOWN) == false) // if false BOMB EXPLODES else carries on destroying more 611 Bomb->Status = EXPLODING; 612 } 613 else 614 Base_Y++; 615 } 616 } 617 } 618 } 619 620void MissileAndBasesCollisions() { 621 for(int i=0;i<NUM_BASES;i++) 622 { 623 if(Collision(Missile,MISSILE_WIDTH,MISSILE_HEIGHT,Base[i].Ord,BASE_WIDTH,BASE_HEIGHT)) 624 { 625 unsigned char X=Missile.X-Base[i].Ord.X; 626 signed char Missile_Y=Missile.Y-Base[i].Ord.Y; 627 signed char Base_Y=BASE_HEIGHT-1; 628 while((Base_Y>=Missile_Y)&(Base_Y>=0)&(Missile.Status==ACTIVE)) 629 { 630 unsigned char Idx=(Base_Y); 631 unsigned char TheByte=Base[i].Gfx[Idx]; 632 unsigned char Mask = B00000011; 633 if (X>6) {X=6; } 634 Mask=(Mask<<(X)); 635 TheByte=TheByte & Mask; 636 637 if(TheByte>0) 638 { 639 Mask=~Mask; 640 Base[i].Gfx[Idx]=Base[i].Gfx[Idx] & Mask; 641 if(random(CHANCE_OF_BOMB_PENETRATING_DOWN)==false) // if false BOMB EXPLODES else carries on destroying more 642 Missile.Status=EXPLODING; 643 } 644 else 645 Base_Y--; 646 } 647 } 648 } 649} 650 651void PlayerHit() { 652 Player.Ord.Status=EXPLODING; 653 Player.ExplosionGfxCounter=PLAYER_EXPLOSION_TIME; 654 Missile.Status=DESTROYED; 655} 656 657void CheckCollisions() 658{ 659 MissileAndAlienCollisions(); 660 MotherShipCollisions(); 661 MissileAndBasesCollisions(); 662 BombCollisions(); 663 AlienAndBaseCollisions(); 664} 665 666char GetAlienBaseCollisionMask(int AlienX, int AlienWidth,int BaseX) 667{ 668 signed int DamageWidth; 669 unsigned char LeftMask,RightMask,FullMask; 670 671 672 // this routine uses a 1 to mean remove bit and 0 to preserve, this is kind of opposite of what we would 673 // normally think of, but it's done this way as when we perform bit shifting to show which bits are preserved 674 // it will shift in 0's in (as that's just was the C shift operater ">>" and "<<" does 675 // at the end we will flip all the bits 0 becomes 1, 1 becomes 0 etc. so that the mask then works correctly 676 // with the calling code 677 678 LeftMask=B11111111; // initially set to remove all 679 RightMask=B11111111; // unless change in code below 680 // if Alien X more than base x then some start bits are unaffected 681 if(AlienX>BaseX) 682 { 683 // we shift the bits above to the right by the amount unaffected, thus putting 0's into the bits 684 // not to delete 685 DamageWidth=AlienX-BaseX; 686 LeftMask>>=DamageWidth; 687 } 688 689 // now work out how much of remaining byte is affected 690 691 // if right hand side of alien is less than BaseX right hand side then some preserved at the right hand end 692 if(AlienX+AlienWidth<BaseX+(BASE_WIDTH/2)) 693 { 694 DamageWidth=(BaseX+(BASE_WIDTH/2))-(AlienX+AlienWidth); 695 RightMask<<=DamageWidth; 696 697 } 698 // we now have two masks, one showing which bits to preserve on left of the byte, the other the right hand side, 699 // we need to combine them to one mask, the code in the brackets does this combining 700 701 // at the moment a 0 means keep, 1 destroy, but this is actually makes it difficult to implement later on, so we flip 702 // the bits to be a more logical 1= keep bit and 0 remove bit (pixel) and then return the mask 703 // the tilde (~) flips the bits that resulted from combining the two masks 704 705 return ~(LeftMask & RightMask); 706} 707 708void DestroyBase(GameObjectStruct *Alien,BaseStruct *Base,char Mask,int BaseByteOffset) 709{ 710 signed char Y; 711 // go down "removing" bits to the depth that the alien is down into the base 712 Y=(Alien->Y+ALIEN_HEIGHT)-Base->Ord.Y; 713 if(Y>BASE_HEIGHT-1) Y=BASE_HEIGHT-1; 714 for(;Y>=0;Y--){ 715 Base->Gfx[(Y)+BaseByteOffset]=Base->Gfx[(Y)+BaseByteOffset] & Mask; 716 } 717} 718 719void AlienAndBaseCollisions() 720{ 721 unsigned char Mask; 722 // checks if aliens are in collision with the tank 723 // start at bottom row as they are most likely to be in contact or not and if not then none further up are either 724 for(int row=(NUM_ALIEN_ROWS-1);row>=0;row--) 725 { 726 for(int column=0;column<NUM_ALIEN_COLUMNS;column++) 727 { 728 if(Alien[column][row].Ord.Status==ACTIVE) 729 { 730 // now scan for a collision with each base in turn 731 for(int BaseIdx=0;BaseIdx<NUM_BASES;BaseIdx++) 732 { 733 if(Collision(Alien[column][row].Ord,AlienWidth[row],ALIEN_HEIGHT,Base[BaseIdx].Ord,BASE_WIDTH,BASE_HEIGHT)) 734 { 735 // WE HAVE A COLLSISION, REMOVE BITS OF BUILDING 736 // process left half (first byte) of base first 737 Mask=GetAlienBaseCollisionMask(Alien[column][row].Ord.X,AlienWidth[row],Base[BaseIdx].Ord.X); 738 DestroyBase(&Alien[column][row].Ord,&Base[BaseIdx],Mask,0); 739 } 740 } 741 } 742 } 743 } 744} 745 746void MotherShipCollisions() 747{ 748 if((Missile.Status==ACTIVE)&(MotherShip.Ord.Status==ACTIVE)) 749 { 750 if(Collision(Missile,MISSILE_WIDTH,MISSILE_HEIGHT,MotherShip.Ord,MOTHERSHIP_WIDTH,MOTHERSHIP_HEIGHT)) 751 { 752 MotherShip.Ord.Status=EXPLODING; 753 MotherShip.ExplosionGfxCounter=EXPLOSION_GFX_TIME; 754 Missile.Status=DESTROYED; 755 // generate the score for the mothership hit, note in the real arcade space invaders the score was not random but 756 // just appeared so, a player could infulence its value with clever play, but we'll keep it a little simpler 757 MotherShipBonus=random(4); // a random number between 0 and 3 758 switch(MotherShipBonus) 759 { 760 case 0:MotherShipBonus=50;break; 761 case 1:MotherShipBonus=100;break; 762 case 2:MotherShipBonus=150;break; 763 case 3:MotherShipBonus=300;break; 764 } 765 Player.Score+=MotherShipBonus; 766 MotherShipBonusXPos=MotherShip.Ord.X; 767 if(MotherShipBonusXPos>100) // to ensure isn't half off right hand side of screen 768 MotherShipBonusXPos=100; 769 if(MotherShipBonusXPos<0) // to ensure isn't half off right hand side of screen 770 MotherShipBonusXPos=0; 771 MotherShipBonusCounter=DISPLAY_MOTHERSHIP_BONUS_TIME; 772 } 773 } 774} 775 776 777void MissileAndAlienCollisions() 778{ 779 for(int across=0;across<NUM_ALIEN_COLUMNS;across++) 780 { 781 for(int down=0;down<NUM_ALIEN_ROWS;down++) 782 { 783 if(Alien[across][down].Ord.Status==ACTIVE) 784 { 785 if(Missile.Status==ACTIVE) 786 { 787 if(Collision(Missile,MISSILE_WIDTH,MISSILE_HEIGHT,Alien[across][down].Ord,AlienWidth[down],INVADER_HEIGHT)) 788 { 789 // missile hit 790 Alien[across][down].Ord.Status=EXPLODING; 791 tone(10, 700,100); 792 Missile.Status=DESTROYED; 793 Player.Score+=GetScoreForAlien(down); 794 Player.AliensDestroyed++; 795 // calc new speed of aliens, note (float) must be before TOTAL_ALIENS to force float calc else 796 // you will get an incorrect result 797 Player.AlienSpeed=((1-(Player.AliensDestroyed/(float)TOTAL_ALIENS))*INVADERS_SPEED); 798 // for very last alien make to fast! 799 if(Player.AliensDestroyed==TOTAL_ALIENS-2) 800 if(AlienXMoveAmount>0) 801 AlienXMoveAmount=ALIEN_X_MOVE_AMOUNT*2; 802 else 803 AlienXMoveAmount=-(ALIEN_X_MOVE_AMOUNT*2); 804 // for very last alien make to super fast! 805 if(Player.AliensDestroyed==TOTAL_ALIENS-1) 806 if(AlienXMoveAmount>0) 807 AlienXMoveAmount=ALIEN_X_MOVE_AMOUNT*4; 808 else 809 AlienXMoveAmount=-(ALIEN_X_MOVE_AMOUNT*4); 810 811 if(Player.AliensDestroyed==TOTAL_ALIENS) 812 NextLevel(&Player); 813 } 814 } 815 if(Alien[across][down].Ord.Status==ACTIVE) // double check still active afer above code 816 { 817 // check if this alien is in contact with TankGfx 818 if(Collision(Player.Ord,TANKGFX_WIDTH,TANKGFX_HEIGHT,Alien[across][down].Ord,AlienWidth[down],ALIEN_HEIGHT)) 819 PlayerHit(); 820 else 821 { 822 // check if alien is below bottom of screen 823 if(Alien[across][down].Ord.Y+8>SCREEN_HEIGHT) 824 PlayerHit(); 825 } 826 } 827 } 828 } 829 } 830} 831 832bool Collision(GameObjectStruct Obj1,unsigned char Width1,unsigned char Height1,GameObjectStruct Obj2,unsigned char Width2,unsigned char Height2) 833{ 834 return ((Obj1.X+Width1>Obj2.X)&(Obj1.X<Obj2.X+Width2)&(Obj1.Y+Height1>Obj2.Y)&(Obj1.Y<Obj2.Y+Height2)); 835} 836 837int RightMostPos() { 838 //returns x pos of right most alien 839 int Across=NUM_ALIEN_COLUMNS-1; 840 int Down; 841 int Largest=0; 842 int RightPos; 843 while(Across>=0){ 844 Down=0; 845 while(Down<NUM_ALIEN_ROWS){ 846 if(Alien[Across][Down].Ord.Status==ACTIVE) 847 { 848 // different aliens have different widths, add to x pos to get rightpos 849 RightPos= Alien[Across][Down].Ord.X+AlienWidth[Down]; 850 if(RightPos>Largest) 851 Largest=RightPos; 852 } 853 Down++; 854 } 855 if(Largest>0) // we have found largest for this coloum 856 return Largest; 857 Across--; 858 } 859 return 0; // should never get this far 860} 861 862int LeftMostPos() { 863 //returns x pos of left most alien 864 int Across=0; 865 int Down; 866 int Smallest=SCREEN_WIDTH*2; 867 while(Across<NUM_ALIEN_COLUMNS){ 868 Down=0; 869 while(Down<NUM_ALIEN_ROWS){ 870 if(Alien[Across][Down].Ord.Status==ACTIVE) 871 if(Alien[Across][Down].Ord.X<Smallest) 872 Smallest=Alien[Across][Down].Ord.X; 873 Down++; 874 } 875 if(Smallest<SCREEN_WIDTH*2) // we have found smalest for this coloum 876 return Smallest; 877 Across++; 878 } 879 return 0; // should nevr get this far 880} 881 882void UpdateDisplay() 883{ 884 int i; 885 886 display.clearBuffer(); 887 888 // Mothership bonus display if required 889 if(MotherShipBonusCounter>0) 890 { 891 print_int(MotherShipBonusXPos, 0, MotherShipBonus, 3); 892 MotherShipBonusCounter--; 893 } 894 895 print_int(0, 0, Player.Score, 5); // draw score and lives, anything else can go above them 896 print_int(0, 20, HiScore, 5); 897 print_int(125, 0, Player.Lives, 1); 898 display.drawXBMP(115, 2, TANKGFX_WIDTH, TANKGFX_HEIGHT, TankGfx ); 899 display.drawVLine(20,0,64 ); 900 display.drawVLine(109,0,64 ); 901 902 print_int(0, 59, framesPerSecond, 3); 903 904 frameCount ++; 905 if ((millis() - lastMillis) > (1000)) { 906 framesPerSecond = (frameCount ); 907 frameCount = 0; 908 lastMillis = millis(); 909 } 910 911 //BOMBS 912 // draw bombs next as aliens have priority of overlapping them 913 for(i=0;i<MAXBOMBS;i++) { 914 if(AlienBomb[i].Status==ACTIVE) 915 display.drawXBMP(AlienBomb[i].X, AlienBomb[i].Y, 2, 4 , AlienBombGfx); 916 else {// must be destroyed 917 if(AlienBomb[i].Status==EXPLODING) 918 display.drawXBMP(AlienBomb[i].X-4, AlienBomb[i].Y, 5, 3, ExplosionGfx ); 919 // Ensure on next draw that ExplosionGfx dissapears 920 AlienBomb[i].Status=DESTROYED; 921 } 922 } 923 924 //Invaders 925 for(int across=0;across<NUM_ALIEN_COLUMNS;across++) 926 { 927 for(int down=0;down<NUM_ALIEN_ROWS;down++) 928 { 929 if(Alien[across][down].Ord.Status==ACTIVE){ 930 931 switch(down) { 932 case 0: 933 if(AnimationFrame) 934 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, AlienWidth[down],INVADER_HEIGHT, InvaderTopGfx ); 935 else 936 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, AlienWidth[down],INVADER_HEIGHT, InvaderTopGfx2 ); 937 break; 938 case 1: 939 if(AnimationFrame) 940 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, AlienWidth[down],INVADER_HEIGHT, InvaderMiddleGfx ); 941 else 942 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, AlienWidth[down],INVADER_HEIGHT, InvaderMiddleGfx2 ); 943 break; 944 case 2: 945 if(AnimationFrame) 946 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, AlienWidth[down],INVADER_HEIGHT, InvaderMiddleGfx ); 947 else 948 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, AlienWidth[down],INVADER_HEIGHT, InvaderMiddleGfx2); 949 break; 950 case 3: 951 if(AnimationFrame) 952 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, AlienWidth[down],INVADER_HEIGHT, InvaderBottomGfx ); 953 else 954 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, AlienWidth[down],INVADER_HEIGHT, InvaderBottomGfx2 ); 955 case 4: 956 if(AnimationFrame) 957 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, AlienWidth[down],INVADER_HEIGHT, InvaderBottomGfx ); 958 else 959 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, AlienWidth[down],INVADER_HEIGHT, InvaderBottomGfx2 ); 960 } 961 } 962 else { 963 if(Alien[across][down].Ord.Status==EXPLODING){ 964 Alien[across][down].ExplosionGfxCounter--; 965 if(Alien[across][down].ExplosionGfxCounter>0) { 966 tone(10, Alien[across][down].ExplosionGfxCounter*100,100); 967 display.drawXBMP(Alien[across][down].Ord.X, Alien[across][down].Ord.Y, 5, 3, ExplosionGfx ); 968 } 969 else 970 Alien[across][down].Ord.Status=DESTROYED; 971 } 972 } 973 } 974 } 975 976 // player 977 if(Player.Ord.Status==ACTIVE) 978 display.drawXBMP(Player.Ord.X, Player.Ord.Y, TANKGFX_WIDTH, TANKGFX_HEIGHT, TankGfx ); 979 else { 980 if(Player.Ord.Status==EXPLODING) { 981 display.drawXBMP(Player.Ord.X, Player.Ord.Y, 5, 3, ExplosionGfx ); 982 tone(10, Player.ExplosionGfxCounter*250,50); 983 Player.ExplosionGfxCounter--; 984 if(Player.ExplosionGfxCounter==0) { 985 Player.Ord.Status=DESTROYED; 986 LoseLife(); 987 } 988 } 989 } 990 //missile 991 if(Missile.Status==ACTIVE) 992 display.drawXBMP(Missile.X, Missile.Y, MISSILE_WIDTH, MISSILE_HEIGHT , MissileGfx); 993 994 // mothership (not bonus if hit) 995 if(MotherShip.Ord.Status==ACTIVE) 996 display.drawXBMP(MotherShip.Ord.X, MotherShip.Ord.Y, MOTHERSHIP_WIDTH, MOTHERSHIP_HEIGHT, MotherShipGfx ); 997 else 998 { 999 if(MotherShip.Ord.Status==EXPLODING) 1000 { 1001 tone(10, MotherShip.ExplosionGfxCounter*250,50); 1002 MotherShip.ExplosionGfxCounter--; 1003 if(MotherShip.ExplosionGfxCounter==0) { 1004 MotherShip.Ord.Status=DESTROYED; 1005 } 1006 } 1007 } 1008 1009 // plot bases 1010 1011 for(i=0; i <NUM_BASES; i++) { 1012 if (Base[i].Ord.Status == ACTIVE) 1013 display.drawXBM(Base[i].Ord.X, Base[i].Ord.Y, BASE_WIDTH, BASE_HEIGHT, Base[i].Gfx ); 1014 } 1015 display.sendBuffer(); 1016} 1017 1018void LoseLife() { 1019 Player.Lives--; 1020 if(Player.Lives>0) { 1021 DisplayPlayerAndLives(&Player); 1022 // clear alien missiles 1023 for(int i=0;i<MAXBOMBS;i++) { 1024 AlienBomb[i].Status=DESTROYED; 1025 AlienBomb[i].Y=0; 1026 } 1027 // ENABLE PLAYER 1028 Player.Ord.Status=ACTIVE; 1029 Player.Ord.X=PLAYER_X_START; 1030 } 1031 else { 1032 GameOver(); 1033 } 1034} 1035 1036 1037void GameOver() { 1038 GameInPlay=false; 1039 display.clearBuffer(); 1040 CentreText("Player 1",8); 1041 CentreText("Game Over",20); 1042 CentreText("Score ",32); 1043 display.setCursor(72,32); 1044 display.print(Player.Score); 1045 if(Player.Score>HiScore) 1046 { 1047 CentreText("NEW HIGH SCORE!!!",44); 1048 CentreText("**CONGRATULATIONS**",56); 1049 } 1050 display.sendBuffer(); 1051 if(Player.Score>HiScore){ 1052 HiScore=Player.Score; 1053 EEPROM.put(0,HiScore); 1054 PlayRewardMusic(); 1055 } 1056 delay(2500); 1057} 1058 1059 1060void PlayRewardMusic() 1061{ 1062 unsigned char Notes[] = { 26, 20, 18, 22, 20, 0, 26, 0, 26 }; 1063 unsigned char NoteDurations[] = { 40, 20, 20, 40, 30, 50, 30, 10,30 }; 1064 for(int i=0;i<9;i++) 1065 { 1066 tone(10, Notes[i]*10); 1067 delay(NoteDurations[i]*10); // time note plays for 1068 noTone(10); // stop note 1069 delay(20); // small delay between notes 1070 } 1071 noTone(10); 1072} 1073 1074 1075void DisplayPlayerAndLives(PlayerStruct *Player) { 1076 display.clearBuffer(); 1077 CentreText(" Player 1",8); 1078 CentreText("Score ",20); 1079 display.print(Player->Score); 1080 CentreText("Lives ",32); 1081 display.print(Player->Lives); 1082 CentreText("Level ",44); 1083 display.print(Player->Level); 1084 display.sendBuffer(); 1085 delay(2000); 1086 Player->Ord.X=PLAYER_X_START; 1087} 1088 1089 1090void CentreText(const char *Text,unsigned char Y) { 1091 // centres text on screen 1092 display.setCursor(int((SCREEN_WIDTH - display.getStrWidth(Text)) / 2.0), Y); 1093 display.print(Text); 1094} 1095 1096 1097void InitPlayer() { 1098 Player.Ord.Y=PLAYER_Y_START; 1099 Player.Ord.X=PLAYER_X_START; 1100 Player.Ord.Status=ACTIVE; 1101 Player.Lives=LIVES; 1102 Player.Level=0; 1103 Missile.Status=DESTROYED; 1104 Player.Score=0; 1105} 1106 1107 1108void NextLevel(PlayerStruct *Player) { // reset any dropping bombs 1109 1110 int YStart; 1111 for(int i=0;i<MAXBOMBS;i++) 1112 AlienBomb[i].Status=DESTROYED; 1113 AnimationFrame=false; 1114 Player->Level++; 1115 YStart=(MOTHERSHIP_HEIGHT +2) + ((Player->Level-1) % LEVEL_TO_RESET_TO_START_HEIGHT)*AMOUNT_TO_DROP_BY_PER_LEVEL; 1116 InitAliens(YStart); 1117 AlienXMoveAmount=ALIEN_X_MOVE_AMOUNT; 1118 Player->AlienSpeed=INVADERS_SPEED; 1119 Player->AliensDestroyed=0; 1120 MotherShip.Ord.X=-MOTHERSHIP_WIDTH; 1121 MotherShip.Ord.Status=DESTROYED; 1122 Missile.Status=DESTROYED; 1123 randomSeed(100); 1124 InitBases(); 1125 DisplayPlayerAndLives(Player); 1126 MusicIndex=0; 1127 MusicCounter=NOTELENGTH; 1128} 1129 1130 1131void InitBases() { // Bases need to be re-built! 1132 1133 byte TheByte; 1134 int Spacing=((SCREEN_WIDTH-32)-(NUM_BASES*BASE_WIDTH))/NUM_BASES; 1135 for(int i=0;i<NUM_BASES;i++) 1136 { 1137 for(int DataIdx=0;DataIdx<BASE_HEIGHT;DataIdx++) 1138 { 1139 TheByte = pgm_read_byte(BaseGfx + DataIdx); 1140 Base[i].Gfx[DataIdx]=TheByte; 1141 } 1142 Base[i].Ord.X=17+(i*Spacing)+(i*BASE_WIDTH)+(Spacing/2); 1143 Base[i].Ord.Y=BASE_Y; 1144 Base[i].Ord.Status=ACTIVE; 1145 } 1146} 1147 1148 1149void NewGame(){ 1150 InitPlayer(); 1151 NextLevel(&Player); 1152} 1153 1154void InitAliens(int YStart) { 1155 for(int across=0;across<NUM_ALIEN_COLUMNS;across++) { 1156 for(int down=0;down<NUM_ALIEN_ROWS;down++) { 1157 Alien[across][down].Ord.X=X_START_OFFSET+(across*(LARGEST_ALIEN_WIDTH+SPACE_BETWEEN_ALIEN_COLUMNS))-(AlienWidth[down]/2); 1158 Alien[across][down].Ord.Y=YStart+(down*SPACE_BETWEEN_ROWS); 1159 Alien[across][down].Ord.Status=ACTIVE; 1160 Alien[across][down].ExplosionGfxCounter=EXPLOSION_GFX_TIME; 1161 } 1162 } 1163 MotherShip.Ord.Y=0; 1164 MotherShip.Ord.X=-MOTHERSHIP_WIDTH; 1165 MotherShip.Ord.Status=DESTROYED; 1166} 1167
Downloadable files
Nano_Vaders Schematic
Schematic to build a Nano Every Space Invaders game
Nano_Vaders Schematic

Photo of Finished Project
Finished Project
Photo of Finished Project

Comments
Only logged in users can leave comments