Ein turbulentes Semester neigt sich dem Ende und auf unserem Blog hat sich mittlerweile eine überschaubare Anzahl an Einträgen gesammelt. Kurzum: unsere treue Leserschaft müssten den letzten Beitrag mittlerweile auswendig kennen.

Als Entschädigung (und Prüfungsleistung) folgt nun ein ausführlicher Rückblick auf unser finales Projekt mit dem Titel ShipTunes. Wer den ganzen Artikel liest (oder die Scrollleiste bedienen kann) bekommt als Belohnung den Downloadlink zum Spiel.
(Im Folgenden werden nur kleine Codebeispiele gezeigt, der komplette Quellcode ist ebenfalls im Download enthalten)

Die Idee

Als wir auf die Idee kamen unsere Soundvisulaisierung in einem klassischen „Shoot’em Up“ zu verpacken, hatten wir sofort den alten Klassiker „Hybris“ vor Augen.

Mit dem Retro-Look drücken wir nicht nur unsere Schwäche für liebgewonnene Diskettenschätze aus, sondern konnten durch den simplen Grafikstil bereits nach kurzer Zeit eine vorläufige Visualisierung realisieren.
Die eingebundene Soundvisualisierung mag für den ein oder anderen nicht auf den ersten Blick ersichtlich sein, spielt aber dennoch eine tragende Rolle in unserem Shooter. Denn nur mit dem richtigen Beat kann unser kleines Schiff dem Asteroidenhagel standhalten.

Der Sound

Ertönen Hi-Hat, Basedrum oder Snare im Lied gibt das Schiff den passenden Schuss dazu ab.
DreiSchuss

Für die Realisierung verwenden wir die Klasse BeatDetect der Minim Library.

void shoot() {
beat.detect(player.mix);
firepower = 5;
if (beat.isKick()) {
fireAtAngle(0,"yellow");
}
if(beat.isSnare()){
fireAtAngle(5,"blue");
fireAtAngle(-5,"blue");
}
if(beat.isHat()){
fireAtAngle(20,"red");
fireAtAngle(-20,"red");
}
}

Wird ein Asteroid zerstört, teilt er sich in vier kleine Asteroiden auf, die ebenfalls zerstört werden müssen. Mit ein bisschen Glück versteckt sich in einem der großen Brocken ein PowerUp.
Um die volle Schusskraft zu erhalten müssen zwei Raketen-PowerUps eingesammelt werden. Mit der Cola-Büchse werden verlorene Herzchen wieder aufgefüllt. Sind maximale Herzchen und Schusszahl erreicht, bringt das PowerUp weitere Punkte auf dem Highscore.
Coke
EinSchuss

Der Code

Wie bereits erwähnt programmieren wir ShipTunes in der Entwicklungsumgebung Eclipse.
Unsere Programmstruktur fußt auf ein paar wenigen Klassen, in denen wir die grundlegenden Funktionen und Eigenschaften aller Spielobjekte definieren.

Die Basisklasse

In der Basisklasse sind z.B. Variablen für die Position, die Geschwindigkeit oder den aktuelle Status (lebendig oder nicht) festgelegt. Alle Spielobjekte (z.B. das Schiff, die Asteroiden und Schüsse) werden von dieser Klasse abgeleitet. D.h. sie erben alle Methoden und Eigenschaften der Basisklasse.


public class Ship extends Basisklasse {
int hearts = 3;
boolean invinsible = false;
Timer timer, timer2;

/***********************************************************************************
* @param p : PApplet
* @param image : Darstellung (PImage)
* @param loc : Startposition (PVector)
***********************************************************************************/
Ship(PApplet p, PImage image, PVector loc) {
super(p, image, loc);
timer = new Timer(p, 100);
timer2 = new Timer(p, 3000);
}

/***********************************************************************************
* Wiederbelebung nach Tod
***********************************************************************************/
void revive() {
if (hearts > 0) {
invinsible = true;
setLoc(new PVector(p.width / 2, p.height / 2));
setAlive(true);
}
}

/***********************************************************************************
* Herzchen verlieren bei Tod
***********************************************************************************/
void loseHeart() {
if (hearts > 0) {
hearts--;
}
}

void update() {
if (alive) {
move();
// Unverwundbar und blinkend
if (invinsible) {
if (timer.countdownLoop())
display();
if (timer2.countdownLoop())
invinsible = false;
} else {
display();
}

}
}
}

Mit extends Basisklasse wird die Klasse Ship erweitert. Das hat den Vorteil, dass in der Klasse Ship Methoden und Variablen der Basisklasse nicht noch einmal erstellt werden müssen aber dennoch verwendet werden können. Wie in der update- Methode zu sehen, werden die Methoden move() und display() gerufen. Diese stehen jedoch nicht in der Klasse Ship sondern in der Basisklasse:


/***********************************************************************************
* Entählt alle grunlegenden Eigenschaften und Methoden eines Spielobjekts
***********************************************************************************/
public class Basisklasse {
[...]
public Basisklasse(PApplet p, PImage image, PVector loc) {
[...]
}

/***********************************************************************************
* Bewegungsberechung
***********************************************************************************/
void move() {
vel.add(acc);
vel.limit(topspeed);
loc.add(vel);
boundingBox.update((int) (loc.x - width / 2),
(int) (loc.y - height / 2));
}

/***********************************************************************************
* Darstellung des Objekts
***********************************************************************************/
void display() {
//Stylemthoden (z.B. imageMode) werden nur auf dieses Objekt angewendet
p.pushStyle();
//Translationen werden nur auf dieses Objekt angewendet
p.pushMatrix();

p.imageMode(p.CENTER);
p.translate(loc.x, loc.y);
calcAngle();
p.rotate(angle);
p.image(image, 0, 0);

p.popMatrix();
p.popStyle();
}

Die Boundingbox

Eine weitere wichtige Klasse für die Kollisionsabfrage ist unsere BoundigBox.
BoundingBox

Jedes Objekt, das an einer Kollision beteiligt sein kann (egal ob Asteroid oder PowerUp) ist von einer BoundingBox umhüllt. Im Grunde ist diese Box nichts weiter als ein Rechteck mit den Dimensionen seines inne liegenden Objekts. Wir leiten die Klasse BoundingBox von Rectangleab, um auf die komfortable Methode intersect() zugreifen zu können (in der Java API enthalten – alternativ könnte man über das Interface Comparable die gleiche Methode schreiben) . Zusätzlich können wir auf alle Eckpunkte zugreifen. Dadurch ist eine noch genauere Abfrage möglich (z.B. bestimmte Bereiche innerhalb der BoundingBox), die jedoch in unserem Spiel nicht nötig ist.

/***********************************************************************************
* Um jedes Spielobjekt wird eine Boundingbox zur Kollisionsabfrage gelegt
* @see java.awt.Rectangle
***********************************************************************************/
public class BoundingBox extends Rectangle {
PApplet p;
Point p1, p2, p3, p4; //Eckpunkte p(x,y)

Point[] eckpunkte; //Enthält p1,p2,p3,p4
int[] pointX;//Enthält x Koord. der Eckpunkte
int[] pointY; // Enthält y Koord. der Eckpunkte

static boolean visible = false; //Boundingbox anzeigen? Static, damit Boundingboxen aller Objekte angezeigt werden2

/***********************************************************************************
* @param tp : PApplet
* @param tx : x-Position
* @param ty : y-Position
* @param twidth : Breite
* @param theight : Höhe
***********************************************************************************/
BoundingBox(PApplet tp, int tx, int ty, int twidth, int theight) {

p = tp;
this.x = tx;
this.y = ty;
this.height = theight;
this.width = twidth;
eckpunkte = new Point[4];
pointX = new int[4];
pointY = new int[4];

// Oben links
p1 = new Point(this.x, this.y);
// oben rechts
p2 = new Point(this.x + this.width, this.y);
// unten links
p3 = new Point(this.x, this.y + this.height);
// unten rechts
p4 = new Point(this.x + this.width, this.y + this.height);

eckpunkte[0] = p1;
eckpunkte[1] = p2;
eckpunkte[2] = p3;
eckpunkte[3] = p4;

setPoints();

}

/***********************************************************************************
* @return alle Eckpunkte point[]-Array
***********************************************************************************/
Point[] getPoints() {
eckpunkte[0] = p1;
eckpunkte[1] = p2;
eckpunkte[2] = p3;
eckpunkte[3] = p4;

return eckpunkte;
}

/***************************************************************************************************************
* update der x und y Werte
***************************************************************************************************************/
void setPoints() {

for (int i = 0; i < pointY.length; i++) {
pointY[i] = eckpunkte[i].y;
pointX[i] = eckpunkte[i].x;
}
}

/***********************************************************************************
* @return x-Wert aller Eckpunkte in int[]-Array
***********************************************************************************/
int[] getPointX() {
this.getPoints();
for (int i = 0; i < pointX.length; i++) {
pointX[i] = eckpunkte[i].x;
}
return pointX;

}

/***********************************************************************************
* @return y-Wert aller Ecktpunkte in int[]-Array
***********************************************************************************/
int[] getPointY() {
this.getPoints();
for (int i = 0; i < pointY.length; i++) {
pointY[i] = eckpunkte[i].y;
}
return pointY;
}

/***********************************************************************************
* @param tx : x-Position (int)
* @param ty : y-Position (int)
***********************************************************************************/
void update(int tx, int ty) {
this.x = tx;
this.y = ty;

p1.setLocation(x, y);
p2.setLocation(this.x + this.width, this.y);
p3.setLocation(this.x, this.y + this.height);
p4.setLocation(this.x + this.width, this.y + this.height);

setPoints();
if(visible){
showBox();
}
}

/***********************************************************************************
* Boundingbox anzeigen
***********************************************************************************/
void showBox() {
p.noFill();
p.rect(x, y, width, height);
p.fill(255, 0, 0);
p.ellipse(p1.x, p1.y, 10, 10);
p.ellipse(p2.x, p2.y, 10, 10);
p.ellipse(p3.x, p3.y, 10, 10);
p.ellipse(p4.x, p4.y, 10, 10);
}
/***********************************************************************************
* @param in : Boundingbox anzeigen? (boolean)
*
***********************************************************************************/
void setVisible(boolean in){
visible = in;
}
}

Team Team Team

Um getrennt an einzelnen Spieelementen arbeiten zu können nutzen wir SVN. SVN bietet die Möglichkeit den Quellcode auf einen Server hochzuladen und mit anderne zu teilen. Für Eclipse gibt es ein komfortables Plugin . Damit können direkt aus Eclipse heraus Projekte geteilt, gepatcht oder geupdatet werden.

Und so geht es weiter

Während der Programmierung von ShipTunes hatten wir viel Spaß und noch immer schwirren uns jede Menge Ideen zur Weiterentwicklung des Spiels im Kopf. Dieser Eintrag mag wohl das Ende dieses Blogs sein, er ist jedoch auch der Anfang unserer neuen Seite. Unter den Namen Index out of Bounds werden wir nicht nur an ShipTunes weiterarbeiten sondern auch alle folgenden großen und kleine Projekte dokumentieren.

http://indexoutofbounds.de/

Hier könnt ihr die erste Version von ShipTunes downloaden!
Oder direkt online spielen!

Wir sagen Danke an Markus für die großartige Unterstützung und vor allem sein Verständnis für unsere arbeitsintensive Studioproduktion.
Unsere treue Leserschaft möchten wir an dieser Stelle natürlich auch nicht vergessen:
Danke an Chris ,Julia und natürlich den zahlreichen Spam-Bots für eure Kommentare.

Annabel, Manuel & Robert
Team1_blog

Comments Kommentare deaktiviert für ShipTunes 0.1 – Rückblick

Auszüge eines Dialoges an Silvesterabend:

Manu: “ Der Sand is irgendwie langweilig.“

Annabel: „Ja, warum haben wir nichts cooleres gemacht?“

Manu: „Mmmh, weiß auch nicht wie wir auf diesen verfluchten Sand gekommen sind.“

Annabel: „Ein Spiel wär doch lustiger gewesen.“

Manu: „Ja, ein Shooter z.B.“

Annabel: „Oh ja! Shooter. Im Weltall. Mit Raumschiffen. Und Kawumm“

Manu: „Genau! Und das Schiff schießt passend zur Musik.“

Annabel: „Jaaaa! Das ist super! Viel besser als der Sand!“

Zwei Tage später konnten wir von der an Silvester entstandenen Idee immer noch nicht ablassen und haben uns kurzerhand entschlossen das alte Konzept über Bord zu werfen und mit einem neuen das Jahr zu beginnen – und wir haben es nicht bereut. Nur wenige durchwachte Nächte später können wir erste Spielsequenzen präsentieren.

Als erstes kann man das gewünschte Lied auswählen. Gesteuert wird mit Maus und Tastatur, wobei die Maus die Schussrichtung angiebt und die Pfeiltasten das Schiff über den Screen navigieren. Geschossen wird sobald Hi Hat (äußere Schüsse) oder Snare (innere Schüsse) ertönen. Mit jedem abgeschossenen Asteroid wird der Highscore erhöht. Tirfft ein Asteroid das Raumschiff wird ein Herz abgezogen. Verliert man alle drei Herzen ist das Spiel beendet. Ausgiebige Tests haben ergeben, dass es bereits jetzt viel Spaß macht.

Songauswahl

spiel02 spiel03

Beitrag von Annabel

Comments 3 Kommentare »

Da Sand nicht aus grauen Ellipsen besteht haben wir unserem Partikelsystem ein passendes Aussehen verliehen.
Als nächstes werden wir das Bewegungsverhalten noch verfeinern. Danach kommt die Musik ins Spiel.

Beitrag von Annabel

Comments Kommentare deaktiviert für Schöner Sand

Für alle die auch bei Processing nicht auf die komfortable Prgrammierumgebung Eclipse verzichtet wollen gibt es ein nützliches Plugin, das eine Verknüpfung ermöglicht. Ein netter Nebeneffekt, alle Java-Libraries sind automatisch mit dabei. Außerdem lassen sich externen Jar-Files wie z.B. Minim problemlos einem Projekt hinzufügen.

Eine ausführliche Anleitung gibt es hier: Proclipsing

Beitrag von Annabel und Manu

Comments Kommentare deaktiviert für Processing in Eclipse

Wie im letzten Eintrag schon berichtet kam unser Sand nicht so richtig in die Gänge.
Die Ursache: In jedem Frame wurde für jeden Partikel eine neue Ellipse gezeichnet, das brachte den Prozessor zum glühen.
Die Lösung: Statt für jeden Partikel eine geomtrische Form berechnen zu lassen, wird eine Bitmap verwendet und schon läuft das Ganze deutlich flüssiger.

Als nächstes verleihen wir unserem Sand das richtige Aussehen.

Im PDF – File befindet sich der aktuelle Quellcode (leider noch nicht vollständig auskommentiert- wird nSandpartikelSystemoch verbessert).

SandpartikelSystem

Beitrag von Annabel

Comments Kommentare deaktiviert für Perfomanceproblem gelöst!

Die ersten Versuche unseres Partikelsystems

Die Partikel reagieren bereits auf die Maus und bewegen sich in eine zufällige Richtung. So weit, so schön. Leider hat sich schon das erste Performanceproblem ergeben, das sich aber immerhin leicht erklären lässt: In jedem Frame werden alle Punkte neu gezeichnet, auch jene, die sich nicht verändert haben. Das kostet natürlich Rechenleistung.

Bevor wir uns weiter mit der eigentlichen Mausinteraktion beschäftigen müssen wir also zunächst an der Darstellung schrauben, damit wir am Ende kein Ruckelbild sondern eine weiche Animation erhalten.

Beitrag von Annabel

Comments Kommentare deaktiviert für Erste Versuche erfolgreich

Ein virtuelles Sandpartikelsystem kann zunächst über die Mausbewegung des Benutzers beeinflusst werden.  Gleichzeitig wird Musik abgespielt. Der Mauszeiger hat die Eigenschaft eines Lautsprechers. D.h. die „Schallwellen“ breiten sich von der Cursorposition kreisförmig aus und stoßen die Sandpartikel ab.

Beitrag von Annabel

Comments Kommentare deaktiviert für Konzept – Sandpartikelsystem