SPACE INVADERS

Typ_projektu
Arduino
Zdjecie główne
Krótki opis projektu

Gra space invaders została wydana jako jedna z pierwszych gier na automaty w 1978 r. Tomohiro Nishikado tworząc swoją grę opierał się między innymi na filmie "Gwiezdne Wojny" umieszczając grę w kosmosie. Celem statku sterowanego przez gracza jest niszczenie statków przeciwnika. Została umieszczona w Księdze Rekordów Guinessa jako najważniejsza gra na automaty.

Niezbędne elementy

1. Płytka Arduino UNO lub kompatybilna

2. Shield SIC Game Console

Sprzęt

Komputer PC z Arduino IDE

Opis projektu

Opis
Przy pomocy AI wykonaliśmy grę space invaders. Chcieliśmy odwzorować grę wydaną na pierwsze automaty. Stworzyliśmy statek powietrzny gracza, statki powietrzne NPC oraz pociski. W naszej wersji wraz ze zwiększaniem się wyniku punktowego zwiększa się także poziom trudności. Nasza wersja podobna jest do gier typu bullet hell.

Ruch
Możliwe jest poruszanie się statkiem powietrznym góra-dół oraz prawo-lewo (na szerokość wyświetlacza), strzelanie (pojedyncze lub bursty) oraz ponowne uruchomienie rozgrywki w przypadku przegranej. 

Porażka
Gracz przegrywa w momencie zderzenie się ze statkiem powietrznym nieprzyjaciela lub trafienia pociskiem. W momencie porażki wyświetla się napis "GAME OVER" wraz z wynikiem punktowym. 

Punktacja
- Eliminację statków powietrznych nieprzyjaciela - 1 punkt = 1 eliminacja
- Przetrwanie - 1 punkt = 3 sekundy

Użyte funkcje 
W projekcie zostały użyte funkcje:

void handleInput() - ruch gracza, nie pozwala poruszać się poza dozwolone pole

void updateBullets() - ruch pocisków gracza

void updateEnemyBullets() - ruch pocisków statków powietrznych przeciwnika

void updateEnemies() - ruch oraz strzelanie NPC

void spawnEnemies() - pojawianie się NPC

void rampDifficulty() - zwiększanie się poziomu trudności

void checkCollisions() - sprawdza zderzenie statku powietrznego gracza ze statkiem NPC

void drawGame() - rysowanie interfejsu graficznego

void drawGameOver() - rysowanie GAME OVER

void resetGame() - reset statystyk oraz ponowne rozpoczęcie gry

 

Zdjęcia
kod programu
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET    -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Button pins
const int LEFT_BTN = 10;
const int RIGHT_BTN = 8;
const int FIRE_BTN = 4;
const int UP_BTN = 7;
const int DOWN_BTN = 9;
const int SPECIAL_BTN = 5;

// Game settings
#define MAX_BULLETS 5
#define MAX_ENEMIES 10
#define MAX_ENEMY_BULLETS 5

enum GameState { PLAYING, GAME_OVER };
GameState gameState = PLAYING;

// Player
int playerX = 64;
int playerY = 56;

// Score
int score = 0;

// Bullet structure
struct Bullet {
  int x, y;
  bool active;
};

Bullet bullets[MAX_BULLETS];  // Player's bullets
Bullet enemyBullets[MAX_ENEMY_BULLETS];  // Enemy's bullets

// Enemy structure
struct Enemy {
  int x, y, speed;
  bool active;
  int shootCooldown;
};

Enemy enemies[MAX_ENEMIES];

// Timing
unsigned long lastEnemySpawn = 0;
unsigned long lastFrame = 0;
unsigned long lastScoreIncrementTime = 0; // NEW: for score every 5 seconds
const int initialEnemySpawnRate = 800;
int enemySpawnRate = initialEnemySpawnRate;
const int frameDelay = 33;  // ~30 FPS

void setup() {
  pinMode(LEFT_BTN, INPUT_PULLUP);
  pinMode(RIGHT_BTN, INPUT_PULLUP);
  pinMode(FIRE_BTN, INPUT_PULLUP);
  pinMode(UP_BTN, INPUT_PULLUP);
  pinMode(DOWN_BTN, INPUT_PULLUP);
  pinMode(SPECIAL_BTN, INPUT_PULLUP);

  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setRotation(2);
  display.clearDisplay();
  display.display();

  resetGame();
}

void loop() {
  if (millis() - lastFrame < frameDelay) return;
  lastFrame = millis();

  if (gameState == PLAYING) {
    handleInput();
    updateBullets();
    updateEnemyBullets();
    updateEnemies();
    checkCollisions();
    spawnEnemies();
    rampDifficulty();

    // NEW: Add 1 point every 5 seconds
    if (millis() - lastScoreIncrementTime >= 3000) {
      score++;
      lastScoreIncrementTime = millis();
    }

    drawGame();
  } else {
    drawGameOver();
    if (!digitalRead(SPECIAL_BTN)) {
      resetGame();
    }
  }
}

void handleInput() {
  if (!digitalRead(LEFT_BTN) && playerX > 0) playerX -= 2;
  if (!digitalRead(RIGHT_BTN) && playerX < SCREEN_WIDTH - 6) playerX += 2;
  if (!digitalRead(UP_BTN) && playerY > 0) playerY -= 2;
  if (!digitalRead(DOWN_BTN) && playerY < SCREEN_HEIGHT - 6) playerY += 2;

  if (!digitalRead(FIRE_BTN)) {
    for (int i = 0; i < MAX_BULLETS; i++) {
      if (!bullets[i].active) {
        bullets[i] = {playerX + 3, playerY, true};
        break;
      }
    }
  }
}

void updateBullets() {
  for (int i = 0; i < MAX_BULLETS; i++) {
    if (bullets[i].active) {
      bullets[i].y -= 3;
      if (bullets[i].y < 0) bullets[i].active = false;
    }
  }
}

void updateEnemyBullets() {
  for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
    if (enemyBullets[i].active) {
      enemyBullets[i].y += 2;
      if (enemyBullets[i].y > SCREEN_HEIGHT) enemyBullets[i].active = false;
    }
  }
}

void updateEnemies() {
  for (int i = 0; i < MAX_ENEMIES; i++) {
    if (enemies[i].active) {
      enemies[i].y += enemies[i].speed;
      if (enemies[i].y > SCREEN_HEIGHT) enemies[i].active = false;

      if (abs(enemies[i].x - playerX) < 6 && abs(enemies[i].y - playerY) < 6) {
        gameState = GAME_OVER;
      }

      if (enemies[i].shootCooldown <= 0) {
        for (int j = 0; j < MAX_ENEMY_BULLETS; j++) {
          if (!enemyBullets[j].active) {
            enemyBullets[j] = {enemies[i].x + 2, enemies[i].y + 5, true};
            enemies[i].shootCooldown = random(30, 100);
            break;
          }
        }
      } else {
        enemies[i].shootCooldown--;
      }
    }
  }
}

void spawnEnemies() {
  if (millis() - lastEnemySpawn > enemySpawnRate) {
    lastEnemySpawn = millis();
    for (int i = 0; i < MAX_ENEMIES; i++) {
      if (!enemies[i].active) {
        enemies[i] = {random(0, SCREEN_WIDTH - 5), 0, random(1, 2), true, 0};
        break;
      }
    }
  }
}

void rampDifficulty() {
  if (enemySpawnRate > 300) {
    enemySpawnRate -= 5;
  }

  for (int i = 0; i < MAX_ENEMIES; i++) {
    if (enemies[i].active && enemies[i].speed < 3) {
      enemies[i].speed += 1;
    }
  }
}

void checkCollisions() {
  for (int i = 0; i < MAX_BULLETS; i++) {
    if (!bullets[i].active) continue;

    for (int j = 0; j < MAX_ENEMIES; j++) {
      if (!enemies[j].active) continue;

      if (abs(bullets[i].x - enemies[j].x) < 5 &&
          abs(bullets[i].y - enemies[j].y) < 5) {
        bullets[i].active = false;
        enemies[j].active = false;
        score++;
        break;
      }
    }
  }

  for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
    if (!enemyBullets[i].active) continue;
    if (abs(enemyBullets[i].x - playerX) < 5 && abs(enemyBullets[i].y - playerY) < 5) {
      gameState = GAME_OVER;
    }
  }
}

void drawGame() {
  display.clearDisplay();

  display.fillRect(playerX, playerY, 6, 6, SSD1306_WHITE);

  for (int i = 0; i < MAX_BULLETS; i++) {
    if (bullets[i].active) {
      display.drawPixel(bullets[i].x, bullets[i].y, SSD1306_WHITE);
    }
  }

  for (int i = 0; i < MAX_ENEMY_BULLETS; i++) {
    if (enemyBullets[i].active) {
      display.drawPixel(enemyBullets[i].x, enemyBullets[i].y, SSD1306_WHITE);
    }
  }

  for (int i = 0; i < MAX_ENEMIES; i++) {
    if (enemies[i].active) {
      display.fillRect(enemies[i].x, enemies[i].y, 5, 5, SSD1306_WHITE);
    }
  }

  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(SCREEN_WIDTH - 60, 0);
  display.print("SCORE:");
  display.print(score);

  display.display();
}

void drawGameOver() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(30, 10);
  display.println("GAME OVER");

  display.setCursor(10, 0);
  display.print("FINAL SCORE: ");
  display.println(score);

  display.setCursor(10, 30);
  display.println("Press SPECIAL to restart");

  display.display();
}

void resetGame() {
  gameState = PLAYING;
  playerX = 64;
  playerY = 56;
  score = 0;
  for (int i = 0; i < MAX_BULLETS; i++) bullets[i].active = false;
  for (int i = 0; i < MAX_ENEMIES; i++) enemies[i].active = false;
  for (int i = 0; i < MAX_ENEMY_BULLETS; i++) enemyBullets[i].active = false;
  lastEnemySpawn = millis();
  lastScoreIncrementTime = millis(); // Reset 5s score timer
  enemySpawnRate = initialEnemySpawnRate;
}
Pliki_projektu
Youtube
Tagi
Space invaders kosmiczni najeźdźcy arduino uno