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