FLYER 3000 (na podstawie gry Frogger)| Arduino UNO

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

Przed rozpoczęciem podniebnej przygody w grze Flyer 3000 musisz wybrać jeden z trzech poziomów trudności w menu głównym, używając przycisków góra i dół oraz zatwierdzając wybór przyciskiem OK. Twoim nadrzędnym celem jest bezpieczne wprowadzenie maszyny do trzech ponumerowanych garaży znajdujących się na samym szczycie ekranu, co wymaga od Ciebie wykonania trzech pełnych przelotów. Sterowanie samolotem odbywa się za pomocą przycisków bocznych, które pozwalają na płynne manewrowanie w lewo i prawo podczas gdy maszyna automatycznie pnie się do góry. Musisz wykazać się dużą zręcznością, aby omijać losowo nadlatujące chmury oraz znacznie szybsze od nich ptaki, ponieważ każdy kontakt z przeszkodą kończy się natychmiastową katastrofą. Do dyspozycji masz ograniczony zapas dwóch pocisków widocznych w dolnej części ekranu, które możesz wystrzelić przyciskiem OK, aby zniszczyć blokujące drogę obiekty. Za każde zestrzelenie przeszkody lub zebranie wirującej, pięcioramiennej gwiazdy otrzymujesz dodatkowe punkty zwiększające Twój końcowy wynik. Gwiazdy pojawiają się w losowych odstępach czasu i poruszają się w poprzek ekranu. Pamiętaj, że raz zajęty garaż staje się niedostępny, więc musisz precyzyjnie celować w pozostałe wolne, unikając uderzenia w czarne ściany hangaru. Gra kończy się sukcesem dopiero w momencie, gdy wszystkie trzy miejsca postojowe zostaną wypełnione, co zostanie potwierdzone radosnym komunikatem o zwycięstwie. W przypadku przegranej na ekranie wyświetli się Twój ostateczny rezultat punktowy, a Ty będziesz mógł powrócić do menu głównego, aby spróbować pobić swój rekord na wybranym poziomie.

Niezbędne elementy

1. Płytka Arduino UNO

2. Shield SIC Game Console

 

Opis projektu

Zasady gry:

W grze Flyer 3000 wcielasz się w pilota, którego zadaniem jest potrójne pokonanie niebezpiecznego sektora i zaparkowanie maszyn w trzech oddzielnych hangarach. Każdy udany przelot kończy się dokowaniem, po którym natychmiast wracasz na start, by wypełnić kolejny wolny slot, aż cała baza zostanie skompletowana.

Główną przeszkodą w dotarciu do celu jest gęsty ruch powietrzny, składający się z powolnych, masywnych chmur oraz błyskawicznie poruszających się ptaków.

W grze Flyer 3000 zyskujesz premię za celne zestrzeliwanie przeszkód przy użyciu amunicji (2 pociski) oraz za ryzykowne wlatywanie kadłubem w wirujące na niebie gwiazdy. Każde zderzenie z chmurą lub ptakiem oznacza natychmiastową porażkę. Rozgrywka kończy się katastrofą również wtedy, gdy spudłujesz przy wjeździe do hangaru lub przez pomyłkę spróbujesz wylądować w zajętym już wcześniej slocie.

  • LĄDOWANIE W GARAŻU: +50 PKT (Główny cel misji).
  • ELIMINACJA PRZESZKODY: +10 PKT (Zestrzelenie chmury lub ptaka).
  • ZDOBYCIE GWIAZDY: +10 PKT (Wleć w rotujący obiekt, aby przechwycić bonus).
Zdjęcia
Intro- tuż po załadowaniu gry pojawia się krótka animacja lecącego samolotu
Możliwe jest wybranie jednego z trzech poziomów trudności
Opis funkcji przycisków
kod programu
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>
#include <math.h> 

#define i2c_Address 0x3c
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

Adafruit_SH1106G display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Button pin definitions
const int buttonLeft = 7;
const int buttonRight = 9;
const int buttonSelect = 5;   
const int buttonUp = 8;       
const int buttonDown = 10;     
const int buttonSkip = 4;     

// Game states and global variables
enum State { INTRO, MENU, PLAYING, GAMEOVER, WON };
State gameState = INTRO;
int difficulty = 0; 
const char* diffNames[] = {"EASY", "MEDIUM", "HARD"};
long score = 0;
int ammo = 2;
float bulletX, bulletY;
bool bulletActive = false;
float planeX = 32;
float planeY = 110;
float flightSpeed = 0.6; 
float sideSpeed = 1.3;
bool garageFull[3] = {false, false, false};

// Structures for game objects
struct Obstacle {
  float x; int y; float speed; bool isBird; int size; 
};
struct Star {
  float x; int y; float speed; bool active; float rotation;
};

const int maxObstacles = 7;
int currentNumObstacles = 5; 
Obstacle obstacles[maxObstacles];
Star collectibleStar;
unsigned long lastUpdate = 0;
const int frameDelay = 25;

// Initialize hardware and display settings
void setup() {
  pinMode(buttonLeft, INPUT_PULLUP);
  pinMode(buttonRight, INPUT_PULLUP);
  pinMode(buttonSelect, INPUT_PULLUP);
  pinMode(buttonUp, INPUT_PULLUP);
  pinMode(buttonDown, INPUT_PULLUP);
  pinMode(buttonSkip, INPUT_PULLUP);
  display.begin(i2c_Address, true);
  Wire.setClock(400000); 
  display.setRotation(3); 
}

// Function to draw the  star with rotation
void drawStar(int x, int y, int r, float rot) {
  for (int i = 0; i < 5; i++) {
    float angle1 = (i * 144 - 90) * 3.14159 / 180.0 + rot;
    float angle2 = ((i + 1) * 144 - 90) * 3.14159 / 180.0 + rot;
    display.fillTriangle(x, y, 
                         x + cos(angle1) * r, y + sin(angle1) * r, 
                         x + cos(angle2) * r, y + sin(angle2) * r, SH110X_WHITE);
  }
}

// Function to draw the plane with a specific rotation angle
void drawRotatedPlane(int x, int y, float angle) {
  float px[] = {0, 0, -8, 8, -4, 4}; 
  float py[] = {-7, 5, 0, 0, 5, 5}; 
  int rx[6], ry[6];
  for(int i = 0; i < 6; i++) {
    rx[i] = x + (px[i] * cos(angle) - py[i] * sin(angle));
    ry[i] = y + (px[i] * sin(angle) + py[i] * cos(angle));
  }
  display.drawLine(rx[0], ry[0], rx[1], ry[1], SH110X_WHITE);
  display.drawLine(rx[2], ry[2], rx[3], ry[3], SH110X_WHITE);
  display.drawLine(rx[4], ry[4], rx[5], ry[5], SH110X_WHITE);
}

// Animated intro sequence with spiral movement
void playIntro() {
  float phase = 0;
  float speedY = 1.7;
  float currentY = 128;
  float amplitude = 23;
  while (currentY > -15) {
    if (digitalRead(buttonSkip) == LOW) { gameState = MENU; return; }
    display.clearDisplay();
    display.setTextSize(1);
    display.setTextColor(SH110X_WHITE);
    display.setCursor(2, 5);
    display.print("FLYER 3000");
    float currentX = 32 + sin(phase) * amplitude;
    float angle = cos(phase) * 0.5; 
    drawRotatedPlane((int)currentX, (int)currentY, angle);
    currentY -= speedY;
    phase += 0.15;
    display.display();
    delay(15);
  }
  gameState = MENU;
}

// Reset star position and state
void resetStar() {
  collectibleStar.y = random(20, 100);
  collectibleStar.x = (random(0, 2) == 0) ? -15 : 75;
  collectibleStar.speed = (collectibleStar.x < 0) ? 1.0 : -1.0;
  collectibleStar.active = true;
  collectibleStar.rotation = 0;
}

// Reset obstacle (cloud or bird) with random properties
void resetObstacle(int i) {
  obstacles[i].y = random(20, 100); 
  obstacles[i].isBird = (random(0, 10) > 8);
  obstacles[i].size = random(1.2, 2); 
  float s = (obstacles[i].isBird) ? random(12, 20)/10.0 : random(6, 12)/10.0;
  obstacles[i].x = (random(0, 2) == 0) ? -40 : 95;
  obstacles[i].speed = (obstacles[i].x < 0) ? s : -s;
}

// Setup initial game variables 
void initGame() {
  planeX = 32; planeY = 120; score = 0; ammo = 2; bulletActive = false;
  for(int i = 0; i < 3; i++) garageFull[i] = false; 
  if (difficulty == 0) { flightSpeed = 1.0; currentNumObstacles = 5; } 
  else if (difficulty == 1) { flightSpeed = 1.4; currentNumObstacles = 6; } 
  else { flightSpeed = 1.8; currentNumObstacles = 7; } 
  for(int i = 0; i < currentNumObstacles; i++) {
    resetObstacle(i);
    obstacles[i].x = random(-20, 60); 
    obstacles[i].y = random(20, 110);
  }
  resetStar();
}

// Main game logic: movement, collisions, and scoring
void updateGame() {
  planeY -= flightSpeed;
  if (digitalRead(buttonSelect) == LOW && !bulletActive && ammo > 0) {
    bulletActive = true; bulletX = planeX; bulletY = planeY - 4; ammo--; delay(150); 
  }
  if (bulletActive) {
    bulletY -= 4; 
    if (bulletY < -5) bulletActive = false;
  }
  // Landing logic for garages
  if (planeY < 12) {
    int px = (int)planeX;
    int hitIdx = (px > 8 && px < 22) ? 0 : (px > 25 && px < 39) ? 1 : (px > 42 && px < 56) ? 2 : -1;
    if (hitIdx != -1) {
      if (!garageFull[hitIdx]) {
        garageFull[hitIdx] = true; planeY = 120; score += 50; 
        if(garageFull[0] && garageFull[1] && garageFull[2]) gameState = WON;
      } else gameState = GAMEOVER;
    } else if (planeY < 2) gameState = GAMEOVER;
  }
  // Obstacle movement and collision detection
  for(int i = 0; i < currentNumObstacles; i++) {
    obstacles[i].x += obstacles[i].speed;
    if (obstacles[i].x > 110 || obstacles[i].x < -60) resetObstacle(i);
    int hitW = obstacles[i].isBird ? 6 : (obstacles[i].size * 11);
    int hitH = obstacles[i].isBird ? 3 : (obstacles[i].size * 4);
    if (planeX + 4 > obstacles[i].x && planeX - 4 < obstacles[i].x + hitW &&
        planeY < obstacles[i].y + hitH && planeY + 8 > obstacles[i].y) gameState = GAMEOVER;
    if (bulletActive) {
      if (bulletX >= obstacles[i].x && bulletX <= obstacles[i].x + hitW &&
          bulletY >= obstacles[i].y && bulletY <= obstacles[i].y + hitH) {
        score += 10; bulletActive = false; bulletY = -100; resetObstacle(i); 
      }
    }
  }
  // Star collection logic
  if (collectibleStar.active) {
    collectibleStar.x += collectibleStar.speed;
    collectibleStar.rotation += 0.15; 
    if (abs(planeX - collectibleStar.x) < 8 && abs(planeY - collectibleStar.y) < 8) {
      score += 10; collectibleStar.active = false;
    }
    if (collectibleStar.x > 110 || collectibleStar.x < -60) collectibleStar.active = false;
  } else if (random(0, 100) > 98) resetStar();
}

// Render the game frame 
void drawFrame() {
  display.clearDisplay();
  display.fillRect(0, 0, 64, 12, SH110X_WHITE);
  for(int i=0; i<3; i++) if(!garageFull[i]) display.fillRect(9+(i*17), 2, 12, 10, SH110X_BLACK);
  display.setTextColor(SH110X_WHITE);
  display.setCursor(2, 118); display.print("PTS:"); display.print(score);
  // Render ammo dots (leftmost disappears first)
  for(int i = 0; i < ammo; i++) {
    display.fillCircle(54 - (i * 6), 121, 1, SH110X_WHITE);
  }
  if (bulletActive) display.drawFastVLine((int)bulletX, (int)bulletY, 3, SH110X_WHITE);
  // Plane drawing
  int px = (int)planeX, py = (int)planeY;
  display.drawFastVLine(px, py, 8, SH110X_WHITE);        
  display.drawFastHLine(px - 4, py + 3, 9, SH110X_WHITE); 
  display.drawFastHLine(px - 2, py + 7, 5, SH110X_WHITE); 
  display.drawPixel(px, py-1, SH110X_WHITE);               
  // Obstacles drawing
  for(int i = 0; i < currentNumObstacles; i++) {
    int ox = (int)obstacles[i].x, oy = obstacles[i].y;
    if (obstacles[i].isBird) {
      display.drawLine(ox, oy, ox + 3, oy + 2, SH110X_WHITE);
      display.drawLine(ox + 3, oy + 2, ox + 6, oy, SH110X_WHITE);
    } else {
      int sz = obstacles[i].size;
      int baseW = sz * 11; int baseH = sz * 4; 
      display.fillRect(ox, oy + baseH, baseW, 2, SH110X_WHITE);
      display.fillCircle(ox + (sz * 2), oy + baseH, sz * 2.5, SH110X_WHITE);
      display.fillCircle(ox + (sz * 5.5), oy + (baseH - sz), sz * 3.5, SH110X_WHITE);
      display.fillCircle(ox + (sz * 9), oy + baseH, sz * 2.5, SH110X_WHITE);
    }
  }
  if(collectibleStar.active) {
     drawStar((int)collectibleStar.x, collectibleStar.y, 3, collectibleStar.rotation); 
  }
  display.display();
}

// Display the difficulty selection menu
void drawMenu() {
  display.clearDisplay();
  display.setTextColor(SH110X_WHITE);
  display.setCursor(14, 5); display.print("CHOOSE");
  display.setCursor(16, 15); display.print("LEVEL");
  display.drawFastHLine(0, 26, 64, SH110X_WHITE);
  for(int i = 0; i < 3; i++) {
    display.setCursor(10, 35 + (i * 12)); 
    if (difficulty == i) display.print("> "); else display.print("  ");
    display.print(diffNames[i]);
  }
  display.display();
  if (digitalRead(buttonUp) == LOW) { difficulty--; if (difficulty < 0) difficulty = 2; delay(200); }
  if (digitalRead(buttonDown) == LOW) { difficulty++; if (difficulty > 2) difficulty = 0; delay(200); }
  if (digitalRead(buttonSelect) == LOW) { initGame(); gameState = PLAYING; delay(300); }
}

// Display Game Over screen with score
void showGameOver() {
  display.clearDisplay();
  display.setCursor(5, 40); display.print("GAME OVER");
  display.setCursor(14, 55); display.print("SCORE:");
  String s = String(score);
  int xPos = ((64 - (s.length() * 6)) / 2) - 8; 
  display.setCursor(xPos, 70); display.print(score);
  display.display();
  if (digitalRead(buttonSelect) == LOW) { gameState = MENU; delay(300); }
}

// Display Victory screen with score
void showYouWon() {
  display.clearDisplay();
  display.setCursor(8, 40); display.print("YOU WON!");
  display.setCursor(14, 55); display.print("SCORE:");
  String s = String(score);
  int xPos = ((64 - (s.length() * 6)) / 2) - 8; 
  display.setCursor(xPos, 70); display.print(score);
  display.display();
  if (digitalRead(buttonSelect) == LOW) { gameState = MENU; delay(300); }
}

// Main loop to handle state switching
void loop() {
  switch(gameState) {
    case INTRO: playIntro(); break;
    case MENU: drawMenu(); break;
    case PLAYING:
      if (millis() - lastUpdate >= frameDelay) {
        lastUpdate = millis();
        if (digitalRead(buttonLeft) == LOW && planeX > 4) planeX -= sideSpeed;
        if (digitalRead(buttonRight) == LOW && planeX < 60) planeX += sideSpeed;
        updateGame(); drawFrame();
      }
      break;
    case GAMEOVER: showGameOver(); break;
    case WON: showYouWon(); break;
  }
}
Pliki_projektu
Schemat
Youtube
Tagi
Arduino Frogger samolot crossy_road FLYER_3000