
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.
1. Płytka Arduino UNO lub kompatybilna
2. Shield SIC Game Console
Komputer PC z Arduino IDE
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


#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;
}