Letztes Update: 08.11.2017
Behandelte Befehle: new, set(), mult(), add(), dist(), equals(), length()

Lernziele

  • Objekte erzeugen und in entsprechenden Variablen speichern
  • Mit Punktnotation auf Eigenschaften und Methoden von Objekten zugreifen
  • Gleichheit von zwei Objekten (z.B. Strings) prüfen
  • Referenzen auf Objekte im Code so verfolgen, dass klar ist, welches Objekt welche Werte hat
  • Mit Vektorobjekten Berechnungen durchführen
  • Strings manipulieren und durchsuchen

Bisher haben wir Variablen verwendet, um sehr einfache Informationen zu speichern: ganze Zahlen (int), Gleitkommazahlen (float) oder Wahrheitswerte (boolean). Diese Datentypen nennt man primitive Datentypen. Jetzt möchte man häufig mehrere zusammengehörige Informationen bündeln, z.B. den x- und y-Wert eines Vektors oder die vielen Informationen, die zu einem digitalen Bild gehören.

In diesem Kapitel lernen Sie Objekte kennen, in denen man mehrere Informationen speichern kann. Es geht in diesem Kapitel nur um die Benutzung solcher Objekte. Wie man eigene Objekt-Typen, sogenannte Klassen, herstellt, lernen wir in einem späteren Kapitel. Ein Objekt ist ein Konstrukt irgendwo im Speicher, das mehrere Daten speichern kann:

Stellen Sie sich vor, Sie haben mehrere Spielfiguren in einem Computerspiel. Ihr Programm würde jede Figur in einem Objekt speichern, das bestimmte Eigenschaften hat, zum Beispiel die aktuellen (x, y)-Koordinaten, die Lebensenergie, die Besitztümern etc. Außerdem möchten Sie, dass die Figuren bestimmte Aktionen durchführen kann, z.B. springen, einen Schuss abgeben, einen Gegenstand an sich nehmen. Diese Aktionen müssen natürlich an individuelle Figuren (Objekte) gebunden sein. Sonst würde die falsche Figur springen oder schießen.

Wir beginnen aber mit einem sehr einfachen Objekt: einem Vektor.

6.1 Vektoren

Objekte können so viel mehr als einfache Variablen! Sie speichern mehrere Informationen in Eigenschaften und stellen sogar eigene Funktionen bereit - sogenannte Methoden.

Es gibt viele verschiedene Typen von Objekten, später werden Sie diese selbst programmieren. Einen bestimmten Typ von Objekten nennt man auch eine Klasse (engl. class). Die Klasse ist sozusagen der Bauplan einer Sorte von Objekten. Für Vektoren gibt es die Klasse PVector, für Zeichenketten die Klasse String, für Digitalbilder die Klasse PImage usw.

Wir schauen uns jetzt die Klasse PVector an.

Objekt erzeugen

Ein Vektor hat eine x- und eine y-Komponente, das sind die Eigenschaften eines jeden Objekts. Wir wollen einen neuen Vektor mit den Werten (4, 2) erstellen. Dazu definieren wir zunächst eine neue Variable vec vom Typ PVector:

PVector vec; // kann einen Vektor speichern

Sie sehen: das funktioniert genauso wie bei einer int-Variablen.

int foo; // kann eine ganze Zahl speichern

Subtiler Unterschied: PVector wird groß geschrieben. Die Großschreibung weist darauf hin, dass wir es nicht mit einem primitiven Datentypen, sondern mit einem Objekttypen zu tun haben.

Unsere Variable vec ist zunächst "leer". Im Gegensatz zu einer int-Variable, die im Zweifelsfall 0 ist, enthält unsere Variable das Stichwort null, das bedeutet, dass die Variable derzeit "auf kein Objekt zeigt". Wir haben ja auch noch gar kein Objekt hergestellt. Also sieht die Situation wie folgt aus:

Jetzt erzeugen wir das neue Vektorobjekt, das in vec gespeichert wird. Dazu schreiben wir das Schlüsselwort new, dann den Namen der Klasse und dann einige Parameter in Klammern. Die Art und Anzahl dieser Parameter kann variieren, bei PVector haben wir zwei float-Parameter für x und y:

vec = new PVector(4, 2); // neuer Vektor mit x=4 und y=2

Hier passieren zwei Dinge. Erstens wird mit new PVector(4, 2) ein neues Objekt im Speicher angelegt.

Im nächsten Schritt wird in der Variablen vec eine Referenz auf das Objekt angelegt. Das kann man sich als Pfeil oder Zeiger in den Speicher vorstellen.

Natürlich kann man Variablendeklaration und Zuweisung wie gewohnt auf einer Zeile durchführen:

PVector vec = new PVector(4, 2);

Wenn Sie den Nullvektor (0, 0) haben möchten, können Sie auch die Parameter weglassen, nur die Klammern müssen Sie unbedingt hinschreiben.

PVector vec = new PVector(); // Nullvektor

Eigenschaften

Wir haben jetzt eine neue Variable vec vom Typ PVector. Wie arbeiten wir jetzt mit dieser Variablen?

Wir können einerseits Eigenschaften auslesen (hier wären das x und y), indem wir den Variablennamen, dann einen Punkt, dann die Eigenschaft hinschreiben:

println(vec.x);
println(vec.y);

Dies nennt man auch die Punktnotation und erlaubt es uns, zwischen den Eigenschaften verschiedener Objekten zu unterscheiden. Definieren wir ein zweites Vektorobjekt in vec2...

PVector vec2 = new PVector(10, -30);

Schematisch kann man sich das so vorstellen:

...dann möchten Sie natürlich zwischen den x/y-Koordination von vec und vec2 unterscheiden:

println(vec.x);
println(vec.y);
println(vec2.x);
println(vec2.y);

Sie können sich die Objekte jeweils in ihrer Gesamtheit mit println() betrachten:

PVector vec = new PVector(4, 2);
PVector vec2 = new PVector(10, -30);
println(vec);
println(vec2);
[ 4.0, 2.0, 0.0 ]
[ 10.0, -30.0, 0.0 ]

Sie sehen, dass PVector eigentlich ein 3D-Vektor ist, also die Eigenschaften x, y und z hat. Ignorieren Sie einfach die z-Komponente, wenn Sie in 2D arbeiten.

Sie können auch die Eigenschaften durch Zuweisung verändern, genauso wie normale Variablen:

PVector v = new PVector(10, 20);

println(v);

v.x = 200;
v.y = 1;

println(v);
[ 10.0, 20.0, 0.0 ]
[ 200.0, 1.0, 0.0 ]

Schauen wir uns gleich ein Beispiel an, wo wir Vektoren verwenden. Wir lassen wieder mal einen Ball fliegen und speichern die aktuelle x/y-Position in einem Vektor:

PVector pos;

void setup() {
  pos = new PVector(); // neuer Null-Vektor
}

void draw() {
  background(255);
  ellipse(pos.x, pos.y, 20, 20); // Werte auslesen
  pos.x++; // Werte erhöhen
  pos.y++;
}

Der Ball fliegt von links oben diagonal nach rechts unten. Beachten Sie, dass Sie die Eigenschaften pos.x und pos.y wie ganz normale Variablen behandeln können (hier mit dem Inkrement-Operator ++).

Eine kürzere Variante ohne setup():

PVector pos = new PVector();

void draw() {
  background(255);
  ellipse(pos.x, pos.y, 20, 20);
  pos.x++;
  pos.y++;
}

Sie sehen schon einen kleinen Vorteil gegenüber der "alten Methode" (zwei Variablen x und y verwenden), wenn Sie einen zweiten Ball verwenden:

PVector pos1 = new PVector(0, 0); // links oben
PVector pos2 = new PVector(100, 0); // rechts oben

void draw() {
  background(255);
  ellipse(pos1.x, pos1.y, 20, 20);
  ellipse(pos2.x, pos2.y, 20, 20);

  pos1.x++; // von links nach rechts
  pos1.y++;
  pos2.x--; // von rechts nach links
  pos2.y++;
}

Mit der alten Methode hätten Sie hier bereits 4 Variablen (x1, y1, x2, y2). Die Objekte erlauben eine kompaktere Darstellung.

Methoden

Objekte können nicht nur mehr Informationen speichern als Variablen, sie bieten darüber hinaus auch Aktionen an, die etwas "tun", also etwas berechnen oder bewegen.

Ein Vektor kann z.B. seine Länge berechnen:

float l = vec.mag();

Die Methode mag() (für engl. magnitude) berechnet die Länge des Vektors und gibt diese zurück. Beachten Sie die Punktnotation und die Klammern!

Um die Eigenschaften x und y des Vektors zu ändern, können Sie die Methode set() verwenden:

PVector vec = new PVector();
vec.set(0, 50); // setzt x=0 y=50

Die Methode mult() multipliziert den gesamten Vektor (also sowohl x als auch y) mit einer Zahl. Diese Methode ändert also die Eigenschaften des Objekts!

PVector vec = new PVector(2, 3);

println("vorher:" + vec);
vec.mult(10);
println("nachher: " + vec);
vorher:[ 2.0, 3.0, 0.0 ]
nachher: [ 20.0, 30.0, 0.0 ]

Die Methode add() addiert einen anderen Vektor zu dem eigenen Vektor hinzu. Im folgenden Code wird vec also wieder verändert:

PVector vec = new PVector(1, 1);
PVector vec2 = new PVector(10, 10);

vec.add(vec2);

println(vec);
[ 11.0, 11.0, 0.0 ]

Ähnlich funktionieren die Methoden sub() (Subtraktion), dot() (Skalar- oder Punktprodukt) und cross() (Kreuz- oder Vektorprodukt). Jedesmal wird der Vektor, auf dem Sie die Methode aufrufen, verändert.

Sicherlich noch von Interesse ist die Methode dist(), welche die Distanz zwischen zwei Punkten misst (hier wird ein Vektor als Punkt x, y im Raum interpretiert):

PVector v1 = new PVector(1,1);
PVector v2 = new PVector(5,1);
float d = v1.dist(v2);

println(d);
4.0

In diesem Fall wird v1 natürlich nicht verändert. Und richtig, man könnte auch schreiben v2.dist(v1).

Identität und Gleichheit

Nehmen wir an, Sie haben zwei Vektoren v und w:

PVector v = new PVector(5, 0);
PVector w = new PVector(5, 0);

Im Speicher sieht das so aus:

Sind die beiden Vektoren "gleich"? Bei int-Variablen prüfen Sie Gleichheit mit dem doppelten Gleichheitszeichen. Lassen Sie uns das versuchen:

println(v == w);
false

Offensichtlich findet Processing nicht, dass v und w gleich sind. Warum ist das so? Weil v und w zwei unterschiedliche Objekte sind, wie man im Bild des Speichers oben klar sieht.

Das == prüft also, ob zwei Variablen das selbe Objekt beinhalten. Man kann auch sagen: Das == prüft die Identität.

Möchten wir prüfen, ob zwei Vektoren die gleichen Werte haben, können wir die PVector-Methode equals() verwenden:

println(v.equals(w));
true

Sie müssen bei Objekten also immer unterscheiden zwischen

  • Identität: Zwei Variablen beinhalten das selbe Objekt. Man sagt auch: zwei Variablen "zeigen" auf das selbe Objekt. Dies wird mit == geprüft.
  • Gleichheit: Zwei Variablen zeigen auf Objekte, die die gleichen Eigenschaften haben. Bei Vektoren heißt das: zwei Vektorobjekte haben die gleichen x/y-Werte. Dies wird mit der Methode equals() geprüft.

Im nächsten Abschnitt über Strings und in Kap. 11 werden Sie mehr dazu hören.

Anwendungsbeispiel

Wenn wir einen Ball animieren, brauchen wir zunächst seine Position (zunächst nur x) und eine Geschwindigkeit (xspeed):

float x = 0;
float xspeed = 1;

void draw() {
  background(255);
  ellipse(x, 50, 20, 20);
  x = x + xspeed;

  if (x > width) {
    x = 0;
  }
}

Jetzt wollen wir den Ball diagonal fliegen lassen und führen dazu die Variablen y und yspeed ein:

float x = 0;
float y = 0;
float xspeed = 1;
float yspeed = 1;

void draw() {
  background(255);
  ellipse(x, y, 20, 20);
  x = x + xspeed;
  y = y + yspeed;

  if (x > width) {
    x = 0;
  }

  if (y > height) {
    y = 0;
  }
}

Beachten Sie, dass Sie für xspeed und yspeed auch andere Werte einsetzen könnten. Genau betrachtet handelt es sich um einen Vektor, der die Richtung des Balls bestimmt. Probieren Sie z.B. xspeed = 2 und yspeed = 3:

Sowohl die Position des Balls (x, y) als auch die Geschwindigkeit (xspeed, yspeed) sind Vektoren. Also nutzen wir unsere neue Klasse PVector!

PVector pos = new PVector(); // Nullvektor
PVector speed = new PVector(3,2);

void draw() {
  background(255);
  ellipse(pos.x, pos.y, 20, 20);

    pos.add(speed);

  if (pos.x > width) {
    pos.x = 0;
  }

  if (pos.y > height) {
    pos.y = 0;
  }
}

Übungsaufgaben

(a) Addition/Subtraktion

Erzeugen Sie die Vektoren v1 = (5, 5) und v2 = (3, 2) und berechnen Sie sowohl v1 + v2 als auch v1 - v2. Drucken Sie das Ergebnis jeweils auf die Konsole.

Die Aktion zur Subtraktion heißt sub().

(b) Vektor als Hintergrund-Farbspeicher

Schreiben Sie eine einfache Ball-Animation (Ball von links nach rechts, dann wieder links eintretend).

Suchen Sie sich mit dem Farbselektor drei Farben aus und notieren Sie die RGB-Werte. Ihr Programm soll jetzt den Hintergrund auf diese Farben setzen, je nachdem, ob Sie die Taste 1, 2 oder 3 drücken. Verwenden Sie einen Vektor und "missbrauchen" Sie die x, y, z Werte, um die RGB-Werte für den Hintergrund zwischenzuspeichern. Verwenden Sie keyPressed() und key, um die Tasten abzufragen.

(c) Bälle mit Vektoren

Schreiben Sie ein Programm, wo ein Ball durch den 2D-Raum fliegt und von den Wänden abprallt. Verwenden Sie einen Vektor für die Position und einen Vektor für die Geschwindigkeit.

Wählen Sie als Startposition die Mitte des Bildschirms. Die Startgeschwindigkeit sollte im Bereich -3 bis 3 liegen, sowohl in x- als auch in y-Richtung.

(d) Mauskollision

Nutzen Sie die Methode dist(), um zu testen, ob der Ball aus den obigen Codeschnipseln mit der Maus kollidiert.

Tipp
Es hat etwas mit dem Radius des Balls zu tun. Sie können auch im Kap. 5 (If-Anweisung) nochmal nachlesen, wie das funktioniert.

Zusammenfassung

  • Eigenschaften und Methoden: Objekte sind komplexe Gebilde, die mehrere Informationen in Eigenschaften speichern können und Aktionen in Form von Methoden bereitstellen.
  • Klasse und Objekttyp: Der Typ oder die Klasse eines Objekts ist eine Art Bauplan, aus dem neue Objekte erzeugt werden können. Man kann Variablen eines solchen Typs anlegen, um ein Objekt darin zu speichern. Klassen sind gleichzeitig auch Datentypen. Im Gegensatz zu primitiven Datentypen nennen wir sie Objekttypen und schreiben diese immer groß (vergleiche primitiven Datentypen: int, float oder boolean).
  • Beispielklasse PVector: PVector ist eine Klasse zum Speichern von 2-dimensionalen (oder 3-dimensionalen) Vektoren oder Punkten.
  • Objekt erzeugen: Neue Vektor-Objekte werden mit nach dem Schema new PVector(5, 10); erzeugt, also das Schlüsselwort new, dann Klassenname und Klammern mit etwaigen Parametern.
  • Punktnotation: Auf die Eigenschaften eines Objekts kann man mit Punktnotation zugreifen, z.B. v2.x, wenn v2 ein Objekt des Typs PVector enthält. Ebenso ruft man Methoden auf, wie z.B. bei: v2.add(v1)

6.2 Strings

Klasse String und ihre Methoden

Es gibt eine wichtige Klasse, die fest eingebaut ist: String. So können Sie ein neues String-Objekt erzeugen:

String name = new String("Harry");

Sie kennen bislang hauptsächlich die Kurzform, die Sie natürlich auch weiterhin verwenden:

String name = "Harry";

Wie jede Klasse hat auch String eine Reihe von Methoden. Diese können Sie auf jedem String aufrufen. Die zwei wichtigsten Methoden sind

  • equals()
  • length()

Beachten Sie, dass length() mit Klammern geschrieben wird, weil es eine Methode ist, im Gegensatz zu der Eigenschaft "length" bei einem Array (ohne Klammern), denn bei einem Array handelt es sich intern um eine Instanzvariable des Array-Objekts.

Die Methode equals() ist deshalb so wichtig, weil Sie alle Vergleiche damit durchführen sollten und nicht mit ==, z.B.:

String a = "Harry";
String b = "Sally";

if (a.equals(b)) {
   println("samesame");
} else {
   println("different");
}

Zu beachten ist, dass ein mit Anführungszeichen definierter String - zum Beispiel "Harry" auch ein Objekt ist, auf dem Sie eine Methode aufrufen können. Das heißt Sie können schreiben:

String b = "Sally";

if ("Harry".equals(b)) {
   println("samesame");
} else {
   println("different");
}

Hier ein Überblick über interessante Methoden (siehe Processing-Referenz für eine Gesamtübersicht):

  • equals(): testet, ob der angegebene String inhaltsgleich mit dem String ist (gibt true oder false zurück)
  • length(): gibt die Länge des Strings zurück
  • contains(): testet, ob der angegebene String im eigenen String enthalten ist (gibt true oder false zurück)
  • indexOf(): einen String innerhalb eines anderen Strings suchen
  • toLowerCase(): gibt kleingeschriebene Variante zurück
  • toUpperCase(): gibt großgeschriebene Variante zurück
  • substring(): einen Teilstring herstellen - es wird der Startbuchstabe (inklusive) und der Endbuchstabe (exklusive) als Indexzahl angegeben

Wichtig ist, dass keine der Methoden den String, auf dem die Methode aufgerufen wird, verändert wird. Tatsächlich ist es so, dass Strings nie im Verlauf ihres Lebens verändert werden können. String-Objekte sind sogenannte Immutables. In der Praxis heißt das, dass Methoden wie toLowerCase() einen neuen String zurückgeben, der alte String aber erhalten bleibt.

Beispiel für contains():

String x = "Terminator";
println(x.contains("tor"));
println(x.contains("Tor"));
true
false

Beispiel für indexOf():

String x = "Terminator";
println(x.indexOf("er"));
1

Beispiel für toLowerCase() und toUpperCase():

String x = "Terminator";
println(x.toLowerCase());
println(x.toUpperCase());
terminator
TERMINATOR

Beispiel für substring():

String x = "I love processing.";
println(x.substring(2, 6));
love

Identität und Gleichheit

Wir haben gesagt, dass man bei Strings immer die Methode equals() verwenden sollte, um zu testen, ob zwei Strings gleich sind.

Dies liegt daran, dass auch Strings Objekte sind und Stringvariablen auf diese Objekte zeigen. Wenn Sie Strings mit == vergleichen, kann es also sein, dass Sie ein false bekommen, obwohl die Strings "gleich" sind:

String s = "hallo";
String s2 = new String("hallo");

println(s == s2);
false

Leider ist es bei Strings so, dass das == auch manchmal funktioniert:

String s = "hallo";
String s2 = "hallo";

println(s == s2);
true

Das hängt damit zusammen, dass Java versucht, die Behandlung von Strings möglichst ökonomisch abzuwickeln. Dabei werden Objekte intern "recyclet", so dass im zweiten Beispiel s und s2 tatsächlich auf das selbe String-Objekt zeigen.

Daher ist es umso schlimmer, bei Stringvergleichen == zu verwenden, weil es manchmal klappt und manchmal nicht.

Das heißt: bei Stringvergleichen immer equals() verwenden!

String s = "hallo";
String s2 = "hallo";
String s3 = new String("hallo");

println(s.equals(s2));
println(s.equals(s3));
true
true

String-Konkatenation

Das Pluszeichen bekommt im Zusammenhang mit Strings eine neue Bedeutung. Wenn mindestens ein Operand (links oder rechts) ein String ist, wird das Pluszeichen zur "String-Konkatenation". Konkatenation heißt "Aneinanderhängen". Beispiel:

String name = "Harry";
String message = "Hallo, " + name + "! Wie geht's?";
println(message);
Hallo, Harry! Wie geht's?

Sie können auch Zahlen und boolesche Werte mit Hilfe von String-Konkatenation zu einem String hinzufügen, sowohl als Variable oder als Wert (Literal):

int x = 15;
boolean wahrheit = true;
println("Zahl: " + x + ", Zahl: " + 10 + ", boolesch: " + wahrheit);
Zahl: 15, Zahl: 10, boolesch: true

Zahlen und boolesche Werte werden bei der String-Konkatenation automatisch in Strings umgewandelt.

Übungsaufgaben

(a) Vergleich

Erzeugen Sie die Strings:

String s1 = "mama";
String s2 = "Mama";

Testen Sie mit equals(), ob die Strings für gleich befunden werden.

Erzeugen Sie einen neuen String s3, indem Sie s2 in die klein geschriebene Variante verwandeln (sehen Sie oben im Skript nach, wie die Aktion heißt).

Vergleichen Sie s1 und s3 mit equals. Sind diese gleich?

(b) Teilstring

Erzeugen Sie den String:

String m = "Harry Potter";

Drucken Sie mit Hilfe von substring() aus:

otter

(c) Vergleich ohne Groß-/Kleinschreibung

Erzeugen Sie die Strings:

String m1 = "Harry Potter";
String m2 = "harry potter";

Vergleichen Sie die Strings mit equals. Sie bekommen ein false zurück, weil die Groß-/Kleinschreibung nicht identisch ist.

Wenden Sie jetzt toLowerCase so an, dass die Groß-/Kleinschreibung ignoriert wird. Schaffen Sie das, ohne neue Variablen zu erzeugen?

(d) Familienname

Gegeben sei:

String n1 = "Thomas Mann";
String n2 = "Erika Mann";

Schreiben Sie eine If-Anweisung, die "gleiche Familie" ausgibt, wenn n1 und n2 den gleichen Nachnamen haben.

Versuchen Sie zunächst, die Position des Leerzeichens zu finden und dann entsprechend die Strings zu schneiden.

Testen Sie Ihr Programm auch mit

String n1 = "Thomas Mann";
String n2 = "Erika Schmidt";
String n1 = "Viktoria Mann";
String n2 = "Erika Mann";
String n1 = "Donald Duck";
String n2 = "Dagobert Duck";

(e) Kleine Wanze

Schreiben Sie ein Programm, das das Wort "Wanze" darstellt. Bei jedem Mausdruck, wird das Wort um einen Buchstaben kürzer. Also erst "Wanze", dann "anze", dann "nze" und so weiter.

Sobald das Wort ganz verschwunden ist, erscheint wieder "Wanze" beim nächsten Mausdruck und das ganze beginnt von vorn.

Zusammenfassung

  • Objekte: String ist kein primitiver Datentyp. Strings sind Objekte.
  • Vergleich mit equals: Strings sollten immer mit der Methode equals() verglichen werden, nicht mit dem == Operator.
  • Konkatenation: Mit dem Plus-Symbol kann man Strings aneinander reihen. Sie können Strings auch mit Zahlen und booleschen Werten kombinieren.

6.3 Bilder

PImage steht für "Processing Image" - diese Klasse erlaubt Ihnen, Bilder einzubinden. Unterstützt werden die Formate .gif, .jpg, .tga und .png. Die Bilder werden als Objekte gespeichert und funktionieren also ähnlich wie Objekte der Typen PVector und String.

Klassen, die mit einem "P für Processing" beginnen (und einige andere), stehen nicht in Java zur Verfügung. Nur als Vorwarnung.

Sie deklarieren zunächst eine Variable vom Typ PImage. Jetzt verwenden Sie nicht new, um ein neues Objekte herzustellen, sondern verwenden die Funktion loadImage(), um eine Datei zu laden und zu einem Objekt zu machen. Diese Datei muss in einem Unterverzeichnis mit Namen "data" liegen (im Verzeichnis Ihres aktuellen Programms). Nach Aufruf von loadImage() hat Processing aus den Bilddaten der Datei ein Bildobjekt hergestellt. Dies ist jetzt in der Variablen gespeichert.

Dann verwenden Sie das Kommando image(), um das Bild zu zeichnen. Als Paramter übergeben Sie das Bildobjekt und die Zielkoordinaten. Denken Sie daran, dass genauso wie rect() und ellipse() das Bild in jeder Runde von draw() neu gezeichnet wird.

PImage pic;

void setup() {
  pic = loadImage("face-smile.png");
}

void draw() {
  background(255);
  image(pic, width/2, height/2);
}

Die Bilder haben ebenfalls Eigenschaften. Interessant sind z.B. die Eigenschaften width und height, die Ihnen die Größe des Bildes zurückmelden. Nützlich, wenn Sie ein Bild fliegen lassen:

PImage devil;
int devilX = 0;
int xspeed = 1;

void setup() {
  devil = loadImage("face-devilish.png");
}

void draw() {
  background(0);
  image(devil, devilX, height/2);

  devilX += xspeed;
  if (devilX > width - devil.width || devilX < 0) {
    xspeed = -xspeed;
  }
}

Mit imageMode können Sie (ähnlich wie mit rectMode) beeinflussen, wo das Bild verankert ist, z.B. links oben (CORNER, dies ist der Default) oder in der Mitte (CENTER).

Dies ist nützlich, wenn Sie ein Bild mit der Maus hin- und herschieben wollen.

PImage player;
PImage devil;
int devilX = 0;
int xspeed = 2;

void setup() {
  size(200,200);
  imageMode(CENTER);
  player = loadImage("face-smile.png");
  devil = loadImage("face-devilish.png");
  devilX = devil.width/2;
}

void draw() {
  background(0);
  image(player, mouseX, mouseY);
  image(devil, devilX, 100);

  devilX += xspeed;
  if (devilX > width - devil.width/2 || devilX < devil.width/2) {
    xspeed = -xspeed;
  }
}

Bilder mit Public-Domain-Lizenz bekommen Sie z.B. von Tango Desktop Project.

6.4 Bibliotheken: Audio

Ein wichtiger Grund, Objekte zu verwenden, ist es, Code an Dritte in Form sogenannter Bibliotheken weiterzugeben. Eine Bibliothek (engl. library) ist nichts anderes als eine Kollektion von Klassen, die jemand programmiert hat und zur Verfügung stellt.

Wenn man in Processing Sounds abspielen will, kann man z.B. die Bibliothek Minim verwenden. In Java nennt man (einen Teil einer) Bibliothek auch Paket.

Diese Bibliothek muss man zunächst aus dem Internet laden. Processing macht es einem sehr leicht. Es gibt einen Menüpunkt Sketch > Import Library..., unter dem man direkt minim auswählen kann. Anschließend wird die entsprechende Datei runtergeladen und ab sofort können Sie minim jederzeit verwenden.

Jetzt müssen Sie allerdings jedesmal im Code ankündigen, dass Sie ein externes Paket wie minim verwenden. Dies machen Sie in den obersten Zeilen Ihres Codes. Für minim schreiben Sie:

import ddf.minim.*;

Das bedeutet "Stelle mir alle Klassen aus dem Paket ddf.minim" zur Verfügung. Processing lädt dann alle diese Klassen in seinen Speicher.

In Minim gibt es zwei Objekte, die Sie brauchen:

  • Das Minim-Objekt kann man sich vorstellen wie eine Fabrik, die tragbare Audioplayer herstellt.
  • Den Audioplayer kann man sich vorstellen wie einen tragbaren MP3-Player, der genau einen Song abspielen kann. Man sagt der Fabrik, welchen Song man möchte, und die Fabrik stellt einen Player her, der diesen Song spielen kann Er hat einen Play-Button und einen Pause-Button. Wenn Sie einen anderen Song spielen wollen, müssen Sie sich einen weiteren Audioplayer aus der Fabrik holen.

Sie müssen sich erstmal die Fabrik erschaffen. Das funktioniert so:

Minim minim;
minim = new Minim(this);

Sie erstellen also ein Objekt - das sehen Sie an dem new. Das Objekt ist in der Variablen minim gespeichert. Es ist durchaus üblich, die Variable so zu nennen wie die Klasse, bloß klein geschrieben.

Jetzt können Sie sich aus der Fabrik den Audioplayer "rauslassen". Da der Audioplayer immer auf einen Song spezialisiert ist, müssen Sie sagen welcher Song auf Ihr Gerät kommen soll - dann bekommen Sie ein Audioplayer-Objekt zurück.

Minim minim;
minim = new Minim(this);
AudioPlayer player;
player = minim.loadFile("jump.mp3");

Wichtig: Ihre Sound-Datei - hier: jump.mp3 - muss im Verzeichnis Ihres Processing-Programms liegen, genauer in einem Unterverzeichnis mit Namen "data".

Jetzt können Sie diesen einen Sound, der auf dem Audioplayer liegt, mit der Methode play() abspielen. Wir bringen den Code mal in keyPressed() unter, damit wir das öfter testen können:

import ddf.minim.*;

Minim minim;
AudioPlayer player;

void setup() {
  minim = new Minim (this);
  player = minim.loadFile("jump.mp3");
}

// Kann man auch weglassen:
void draw() {
}

void keyPressed() {
  player.play();
}

(Für obigen Code benötigen Sie ein Audiofile namens jump.mp3 in dem data-Unterverzeichnis)

Wenn Ihr Sound länger ist, kann es sein, dass Ihr Play-Kommando nichts tut, weil der Sound noch läuft. Um das zu beheben, müssen Sie vor jedem play() den Sound auf die Nullposition zurücksetzen. Dazu verwenden Sie die Methode cue(). Diese Methode bekommt die gewünschte Position in Millisekunden. Wir nehmen einfach Null.

import ddf.minim.*;

Minim minim;
AudioPlayer player;

void setup() {
  minim = new Minim (this);
  player = minim.loadFile("jump.mp3");
}

// Kann man auch weglassen:
void draw() {
}

void keyPressed() {
  player.cue(0);
  player.play();
}

Wollen Sie gleichzeitig eine Hintergrundmusik laufen lassen, erzeugen Sie mit Ihrer Fabrik einfach einen zweiten Player:

import ddf.minim.*;

Minim minim;
AudioPlayer playerFX;
AudioPlayer playerBackground;

void setup() {
  minim = new Minim (this);
  playerFX = minim.loadFile("jump.mp3");
  playerBackground = minim.loadFile("music.mp3");
  playerBackground.loop();
}

// restlicher Code ...

(Für obigen Code benötigen Sie zwei Audiofiles namens jump.mp3 und music.mp3 in dem data-Unterverzeichnis)

Mit dem Befehl loop() läuft die Musik im Wiederholmodus. Sie können die Hintergrundmusik mit pause() und play() steuern, z.B. per Tastendruck. Wir hängen den Jump-Sound an die Leertaste und das Play/Pause an die Taste P. Um zwischen Play und Pause umzuschalten, nutzen wir die Methode isPlaying(), die zurückgibt, ob der Player im Play- oder Pause-Modus ist (boolean).

void keyPressed() {
  if (key == ' ') {
    playerFX.cue(0);
    playerFX.play();
  }

  if (key == 'p') {
    if (playerBackground.isPlaying()) {
      playerBackground.pause();
    } else {
      playerBackground.play();
    }
  }
}

Hier finden Sie mehr Dokumentation zu den Klassen Minim und AudioPlayer.

Kostenlose Sounds für Ihre Spiele bekommen Sie u.a. von

Bitte achten Sie darauf, welche Lizenz ein Sound hat. Sie müssen zunächst darauf achten, dass der Sound kostenlos ist, aber darüber hinaus, ob eine kommerzielle Nutzung zulässig ist (falls Sie Ihr Spiel verkaufen möchten). Ebenfalls wichtig ist, ob die Nennung des Erschaffers notwendig ist (z.B. bei der Creative Commons Attribution 3.0). Wenn ja, üben Sie gleich das "Credit geben", indem Sie in Ihrem Code-Kommentar die Sound-Erschaffer angeben. Lesen Sie sich unbedingt die Lizenzbedingungen auf der jeweiligen Download-Webseite durch.