Meteorescape Code

    /***************************
     Meteorescape http://kssb.ch/~gr12f/
     Jonas Heldner, Jeremy Gennheimer, Devin Imboden
     24.04.2023 KSSB Brig
     ***************************/
    
    //Spieler:
    int playerX = width/2; // X-Position des Spielers
    int playerY = 900; // Y-Position des Spielers
    int playerSize = 130  ; // Größe des Spielers
    int playerDirection;
    int speed = 7; // Geschwindigkeit des Spielers
    
    int anzahlHerzen = 3;
    int heartSpeed ;
    int mSpeed ;
    int gameChangeTime;
    float gameSpeedFactor;
    int zeitSpeicher;
    
    //Scores:
    int score = 0; //Punktzahl des Spielers
    int highscore = 0; //Highscore
    String[] scoreLines; //Score
    int lives = 0; // Anzahl der Leben des Spielers
    int state = 0; //States
    
    //Time:
    float loadingTime = 500;
    float timer = 4600; //Timer für Erklärung
    
    //Bilder definieren:
    PImage backgroundGame; //Hintergrund
    PImage backgroundStart; //Hintergrund Spiel
    PImage gameBackground;
    
    PImage meteor; //Meteor
    PImage player; //Spieler
    PImage playerR; //Spieler
    PImage loading; //Erklärung
    PImage erklaerung;  //Herz
    PImage herz;  //Herz
    ArrayList<PImage> meteors = new ArrayList<PImage>();
    
    // Schriftarten:
    PFont hashiba;
    PFont pixel;
    PFont daydream;
    
    // Obstacles:
    ArrayList<Obstacle> obstacles = new ArrayList<Obstacle>();
    
    //Music importieren
    import processing.sound.*;
    SoundFile music;
    SoundFile music2;
    SoundFile gameover;
    SoundFile start;
    SoundFile hurt;
    SoundFile bigmeteo;
    SoundFile meteoSound;
    SoundFile bonus;
    
    float volume = 0.3; //Lautstärke siehe Case 4
    
    
    
    void setup() {
      //allgemeines
      fullScreen();
      frameRate(100);
    
      //Image
      backgroundGame = loadImage("./images/bgG.jpg");
      gameBackground = loadImage("./images/bg2.png");
    
      meteor = loadImage("./images/meteor.png");
      meteor.resize(int(meteor.width*0.35), int(meteor.height*0.35));
      
      // Verschiedene Meteoritenbidler für Animation werden geladen
      for (int i = 1; i <= 11; i++) {
        PImage t = loadImage("./images/meteor/meteor000" + i + ".png");
        t.resize(int(200*0.45), int(350*0.45));
        meteors.add(t);
      }
    
      player = loadImage("./images/dino-l.png");
      player.resize(int(130f/player.width), int(130f/player.width*player.height));
    
      playerR = loadImage("./images/dino-r.png");
      playerR.resize(int(130f/playerR.width), int(130f/playerR.width*playerR.height));
    
      backgroundStart = loadImage("./images/bgS.png");
    
      herz = loadImage("./images/herz.png");
      herz.resize(int(herz.width*0.08), int(herz.height*0.08));
    
      loading = loadImage("./images/loading.png");
      loading.resize(width, height);
    
      erklaerung = loadImage("./images/erklaerung.png");
      erklaerung.resize(width, height);
    
      //Music
      music = new SoundFile(this, "./audios/music.wav");
      music2 = new SoundFile(this, "./audios/music2.wav");
      gameover = new SoundFile(this, "./audios/gameover.wav");
      start = new SoundFile(this, "./audios/start.wav");
      hurt = new SoundFile (this, "./audios/hurt.wav");
      bigmeteo = new SoundFile (this, "./audios/bigmeteo.wav");
      meteoSound = new SoundFile (this, "./audios/meteo.wav");
      bonus = new SoundFile (this, "./audios/Bonus.wav");
      start.loop(); //Musik im loop starten
      start.amp (volume); //Lautstärke anpassen
      
      // fonts
      hashiba = createFont("./fonts/hashiba.ttf", 40);
      daydream = createFont("./fonts/daydream.ttf", 20);
      pixel = createFont("./fonts/pixel.TTF", 20);
    
      //Highscore laden
      scoreLines = loadStrings("./data/highscore.txt"); //ist in Textdatei gespeichert
      if (scoreLines.length > 0) {
        highscore = int(scoreLines[0]);
      }
    }
    
    
    
    void draw() {
      switch (state) {
    
      case 0: // Startbildschirm
    
        gameover.stop(); // Gameover Musik gestoppt
    
        background(0);
        image(backgroundStart, 0, 0); // Backgroundbild
    
        fill(255);
        textFont(hashiba);
        noFill();
        textSize(130);
        text("Meteorescape", width/2, 400); // Titel
    
    
        textSize(50);
        textAlign(CENTER);
        fill(255);
        text("Highscore: " + highscore, width/2, 900); // Highscore
        textSize(40);
        fill(255);
        text("Press Space to Start", width/2, 800); // Start
    
        // Settingsbutton
        fill(255);
        rect(width/2 - 100, height/2 + 100, 200, 50);
        textAlign(CENTER, CENTER);
    
        fill(0);
        textSize(20);
        text("Menu", width/2, height/2 + 125); //Menu Knopf auf Startseite
    
        // leertaste = game start
        if (keyPressed && key == ' ') {
          state = 1; // state auf 1
          
          timer = loadingTime; // timer wird auf normalwert gesetzt
          
          music.loop();
          music.amp(volume);
        }
    
        break;
    
    
      case 1: //erlärung vom Spiel
        timer--; //timer minus zählen
        
        fill(255);
        
        lives= 3; // 3 Leben
        score = 0; //Score auf 0
        
        // Musik wird gestoppt
        start.stop();
        gameover.stop();
    
        // Fakeladeschreen Prozent anzeigen
        image(loading, 0, 0);
        textSize(50);
        fill(255, 200);
        textSize(50);
        text(Math.round(100 - timer/loadingTime * 100) + "%", width/2, height-150);
    
        // Leiste die von 0 - 100 geht
        strokeWeight(3);
        stroke(255);
        line(width/2 - 300, height-80, (width/2-300) + 600-Math.round(timer/loadingTime*600), height-80);
          
        if (timer < 1) { //bis 1 runter zählen -> state zu 2 wechseln
          background(0);
          playerX = width/2;
          heartSpeed = 1000;
          mSpeed = 300;
          gameChangeTime = 300;
          gameSpeedFactor = 1.0;
          playerDirection = RIGHT;
          frameCount = 0;
    
          if (loadingTime!=500) { // Falls das Spiel schon gespielt wurde geht es direkt ins Spiel
            state=2;
            zeitSpeicher=frameCount;
          } else { // Der erklärscreen wird zuerst gezeigt
            state=5;
          }
        }
        break;
    
    
      case 2: // Spiel
        background(0);
        image(gameBackground, 0, 0);
    
        // Spieler bewegen
        control();
    
        // Meteoriten bewegen und zeichnen
        // Alle mSpeed-Frames wird ein Meteorit erschaffen
        if ((frameCount % int(mSpeed)) == 0) {
          println("Meteor erschaffen");
          meteoSound.play(); // Meteoritsound abspielen
    
          // Obstacle-Objekt wird erstellt, es wird x y, speed bild und objekttyp gesetzt
          
          Obstacle o = new Obstacle(Math.round(random(backgroundGame.width, backgroundGame.width*2-herz.width)), -meteor.height, 6, meteor, "meteor");
          obstacles.add(o); // meteorit wird zur objektliste hinzugefügt
        }
      
    
         // Mit 3% Chance oder Alle 60000 Frames werden 5 Meteoriten auf einmal erschaffen
        if ((frameCount % int(mSpeed) == 0 && int(random(0, 100)) <= 3) || frameCount % 60000 == 0) {
          println("5-Fach-Meteorit erschaffen");
          bigmeteo.play();
          for (int i = 0; i <= 4; i++) {
            Obstacle o = new Obstacle(backgroundGame.width +300*i, -meteor.height, 6, meteor, "meteor");
            o.scored = true;
            obstacles.add(o);
          }
        }
    
        if ((frameCount % int(heartSpeed)) == 0) { // Alle heartSpeed-Frames wird ein Herz erschaffen
          println("Herz erschaffen");
          // Hier das gleiche wie beim Meteorit
          Obstacle o = new Obstacle(Math.round(random(backgroundGame.width, backgroundGame.width*2-herz.width)), -herz.height, 6, herz, "herz");
          obstacles.add(o);
        }
        
        // Alle gameChangeTime-Frames wird das Spiel schneller gemacht
        if ((frameCount % gameChangeTime) == 0) {
          println("Spiel schneller [ySpeed-factor: "+gameSpeedFactor+", Herzenrate: "+(heartSpeed/frameRate)+"s, Meteorrate: "+(mSpeed/frameRate)+"s]");
    
          gameSpeedFactor+=0.05; // Objekte kommen schneller runter
          heartSpeed-=10; // Herzen spawnen öfters
          mSpeed-=10; // Meteoriten spawnen öfters
          // Hier wird geschaut, das der Wert nicht auf 0 geht
          if (heartSpeed==0)heartSpeed=20;
          if (mSpeed==0)mSpeed=20;
        }
    
    
        // Hier machen wir einen Loop durch alle Objekte
        for (int i = 0; i < obstacles.size(); i++) {
          Obstacle o = obstacles.get(i); //Aktuell ausgewähltes Objekt
          o.show(); // Objekt wird angezeigt
          o.y+=o.speed*gameSpeedFactor; // Objekt Y-Koordinate wird vergrössert mit Objektgeschwindigkeit*Spielgeschwindigkeit (Objekte kommen so herunter)
          if (isColliding(playerX, playerY, player.width, player.height, int(o.x), int(o.y), o.img.width, o.img.height)) { // Hier wird gecheckt ob der Spieler das Objekt berührt
            o.collide();
          }
          
          // SCORE FUNKTION
          if (isYColliding(playerX, playerY, player.width, player.height, int(o.x), int(o.y), o.img.width, o.img.height) && o.type == "meteor" && !o.scored) { // Hier wird gecheckt ob der Spieler auf der gleichen Höhe eines Meteoriten ist, der noch nicht zum Score zugezählt wurde
            score++; // Score geht um 1höher
            o.scored =true; // scored wird true, damit der Meteornit nicht mehrmals gezählt werden kann
          }
        }
    
    
        // Spieler zeichnen (Jenach Bewegungsrichtig gespiegelt oder nicht)
        if (playerDirection == RIGHT) {
          image(playerR, playerX, playerY);
        } else image(player, playerX, playerY);
    
    
        // Punktzahl und Leben anzeigen
        fill(255);
        textSize(100);
        textAlign(CENTER);
        text("Score: " + score, 250, height/2);
        textAlign(CENTER);
        
        // Der Wert aufteilung wird berechnet (benötigt man einfach dafür, dass die Herzen am rechten bildschirmrand nicht ganz am rand kleben)
        int aufteilung = (lives <= 3) ? 4 : lives+2;
        
        for (int i = 1; i <= lives; i++) { // eine For-Loop-Schleife wird für jedes Herz ausgeführt
          imageMode(CENTER);
          image(herz, width-250, height/aufteilung*i); // Das Herz wird angezeigt und auf die richtige Höhe  mit Hilfe von Dreisatz gesetzt
          imageMode(CORNER);
        }
        
        textAlign(LEFT);
        if (lives == 0) { //Wenn Leben 0 sind zu state 3 wechseln
          state = 3;
        }
        
        break;
    
    
      case 3 : //Game Over, wenn alle Leben verloren sind
    
        // restart Knopf zeichnen
        fill(255);
        rect(width/2, height/2 + 50, 200, 50);
        textAlign(CENTER, CENTER);
        fill(0);
        textSize(20);
        text("Restart", width/2+100, height/2 + 75);
    
        //Close Knopf zeichnen
        fill(255);
        rect(width/2, height/2 + 170, 200, 50);
        textAlign(CENTER, CENTER);
        fill(0);
        textSize(20);
        text("Close", width/2+100, height/2 + 192);
    
        // Startseite Knopf zeichnen
        fill(255);
        rect(width/2, height/2 + 110, 200, 50);
        textAlign(CENTER, CENTER);
        fill(0);
        textSize(20);
        text("Startseite", width/2+100, height/2 + 135);
    
      fill(255);
        textSize(80);
        text("Game Over!", width/2 +100, height/2);
        break;
    
    
      case 4 : //Menu
        image(backgroundStart, 0, 0);
        fill(255);
        textSize(255);
        text("Meteorescape", width/2, 350);
        textSize(30);
        textAlign(CENTER);
        text("Highscore: " + highscore, width/2, 850);
        textSize(50);
        text("Press backspace to go back", width/2, 800);
        if (keyPressed && key == BACKSPACE) {  //wenn ich backspace drücke im Menu wechsle ich zurück zum state 0 also Startseite
          state = 0; //
        }
    
        //Knöpfe
        fill(255);
        text("Lautstärke:"+ nf(volume, 0, 1), width/2, height/2 + 40);
    
        //Lautstärkeknopf +
        fill(255);
        rect(width/2 - 100, height/2 + 50, 50, 50);
        textAlign(CENTER, CENTER);
        fill(0, 255);
        textSize(40);
        text("+", width/2 - 75, height/2 + 70);
    
        //Lautstärkeknopf -
        fill(255);
        rect(width/2 + 30, height/2 + 50, 50, 50);
        textAlign(CENTER, CENTER);
        fill(0, 255);
        textSize(40);
        text("-", width/2 + 55, height/2 + 70);
    
        //Reset Highscore
        fill(255);
        rect(width/2 - 110, height/2 + 110, 200, 50);
        textAlign(CENTER, CENTER);
        fill(0);
        textSize(20);
        text("Highscore Zurückstzen", width/2 - 8, height/2 + 135);
        break;
      case 5:
    
        image(erklaerung, 0, 0);
        if (zeitSpeicher/100f + 10 < frameCount/100f) {
          state=2;
        }
        break;
      }
    }
    
    void keyPressed() {
      if (key == 'g') state++;
    }
    
    
    void mouseClicked() { //Funktionen der Knöpfe
      println(mouseX, mouseY);
    
      switch (state) {
      case 0 : //Startseite Knopf
        if (mouseX > width/2 - 100 && mouseX < width/2 + 100 && mouseY > height/2 + 110 && mouseY < height/2 + 160) {
          state = 4; // zum Menu wenn Menu Knopf gedrückt wird
          loop();
        }
        break;
    
    
      case 3 : //Game over Knöpfe
        // restart knopf funktion
        if (mouseX > width/2 - 100*2 && mouseX < width/2 + 200 && mouseY > height/2 + 50 && mouseY < height/2 + 100) {
          loadingTime =100;
          timer = loadingTime;
    
          state = 1;
    
          music.loop();
          music.amp(volume);
        }
    
        // Startseite knopf funktion
        if (mouseX > width/2 - 100*2 && mouseX < width/2 + 100*2 && mouseY > height/2 + 110 && mouseY < height/2 + 160) {
          start.loop();
          state = 0; // zur Startseite
          loop();
        }
    
        //exit
        if (mouseX > width/2 - 100*2 && mouseX < width/2 + 100*2 && mouseY > height/2 + 170 && mouseY < height/2 + 250) {
          exit(); //QUIT
        }
        break;
    
    
      case 4 : // Menu Knöpfe
        //lauter
        if (mouseX > width/2 - 120 && mouseX < width/2 - 50 && mouseY > height/2 + 50 && mouseY < height/2 + 100 ) {
          volume = min(1, volume + 0.1); // Lautstärke um 0.1 erhöhen
        }
    
        //leiser
        if (mouseX > width/2 + 10 && mouseX < width/2 + 80 && mouseY > height/2 + 50 && mouseY < height/2 + 100) {
          volume = max(0, volume - 0.1); // Lautstärke um 0.1 verringern
        }
    
        //Reset Highscore
        if (mouseX > width/2 - 100 && mouseX < width/2 + 100 && mouseY > height/2 + 110 && mouseY < height/2 + 160) {
          highscore = 0;
          String[] scoreArray = { str(highscore) };
          saveStrings("./data/highscore.txt", scoreArray);
        }
        break;
      }
    }
    
    // Hier wird geschaut ob die x und y Koordinaten sich im Bereich der x y Koordinaten eines anderen Rechteck befinden
    boolean isColliding(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) {
      boolean xRichtig = false;
      boolean yRichtig = false;
      for (int i = x1; i<x1+w1; i++) if (i > x2 && i < x2+w2) xRichtig = true;
      for (int i = y1; i<y1+h1; i++) if (i > y2 && i < y2+h2) yRichtig = true;
      return xRichtig&&yRichtig;
    }
    
    // hier wird nur geschaut ob ein objekt auf gleicher höhe mit einem anderen ist 
    boolean isYColliding(int x1, int y1, int w1, int h1, int x2, int y2, int w2, int h2) {
      boolean yRichtig = false;
      for (int i = y1; i<y1+h1; i++) if (i > y2 && i < y2+h2) yRichtig = true;
      return yRichtig;
    }
    
    // Spielerbewegung
    void control() {
      if (keyPressed) {
        if (key == 'a' && playerX > backgroundGame.width) {
          playerX -= speed;
          playerDirection=LEFT;
        } else if (key == 'd' && playerX < backgroundGame.width*2 - playerSize) {
          playerX += speed;
          playerDirection = RIGHT;
        }
      }
    }
    
    // Obstacle (=Gegner) Objekt
    class Obstacle {
      
      float x, y, speed; // Koordinaten + Fallgeschwindigkeit
      PImage img; // Bild
      String type; // Objekttyp für System
      boolean scored = false; // Ob das Objekt bereits zum Score gezählt wurde
      int frame; // Welcher Frame (gilt nur für Meteoriten) aktuell ist -> Animation
    
      Obstacle(float x, float y, float speed, PImage img, String type) {
        this.x = x;
        this.y = y;
        this.speed = speed;
        this.type=type;
        this.img = img;
        frame=1;
      }
    
      void show() {
        if (type == "meteor") {
          if (frameCount%10==0) frame = frame+1==11 ? 1 :frame+1; // frame-Wert wird um 1 höher oder auf 1 gesetzt falls er 10 überschreitet (nur alle 10 Frames)
          image(meteors.get(frame), x, y); // Meteorit wird je nach frame angezeigt
          this.img = meteors.get(frame);
        } else
          image(img, x, y);
        if (this.y > height+img.height) obstacles.remove(this);
      }
        
        
      void collide() {
        obstacles.remove(this);
        if (type == "meteor") {
          hurt.play();
          lives--;
          if (lives==0) {
            obstacles.clear();
            state++;
            loadingTime = 100;
            //Music
            music2.stop();
            music.stop();
            gameover.play();
            gameover.amp(volume);
            //Highscore speichern
            if (score > highscore) {
              highscore = score;
              String[] scoreArray = { str(highscore) };
              saveStrings("./data/highscore.txt", scoreArray);
            }
          }
        } else {
          bonus.play();
          lives++;
        }
      }
    }