Einführung in Greenfoot

Greenfoot ist eine interaktive Java-Entwicklungsumgebung, die primär für Ausbildungszwecke entwickelt wurde. Sie erlaubt die einfache Entwicklung zweidimensionaler graphischer Anwendungen wie z. B. Simulationen und Spiele. Da die unterstützte Programmiersprache Standard-Java ist, können auch recht komplexe und anspruchsvolle Projekte implementiert werden. Die Hauptattraktion für Lernende ist, dass sehr schnell und interaktiv animierte graphische Projekte implementiert werden können. Einfache Spiele sind selbst für Anfänger nach kurzer Zeit erreichbar.

Da an meiner Schule mit dem Unterrichtswerk "Informatik" aus dem Schöningh-Verlag gearbeitet wurde bzw. gearbeitet wird, gehe ich hier als erstes auf das Greenfoot-Szenario "Planetenerkundung" ein. Daneben werden auch noch andere Szenarien betrachtet, die bei Suchen im Internet immer wieder anzutreffen sind wie zum Beispiel "Little Crab", "Bloodstream", "Kara" und "Asteroids."

Das Greenfoot-Szenario Planetenerkundung

Ich gehe davon aus, dass Greenfoot installiert wurde und das benötigte Szenario Planet_1.03 installiert wurde. Notwendige Hinweise zur Installation wurden im Unterricht besprochen bzw. lassen sich im Internet nachlesen.

Nachdem das Szenario gestartet wurde, liegen folgende Klassen vor (siehe Klassendiagramm":

fs Die Klasse "World" mit der Unterklasse "Planet" und die Klasse "Actor" mit den Unterklassen "Rover", "Marke", "Gestein" und "Huegel".

Betrachten wir die Unterklasse "Rover". Dazu klickt man zuerst mit der rechten Maustaste auf den Klassennamen und plaziert einen Rover auf dem Spielfeld. Ein weiterer Rechtsklick auf den plazierten Rover öffnet das Objektmenü. Hier sind alle Methoden aufgelistet, die man mit dem Objekt Rover ausführen kann. Die Methode fahre lautet vollständig void fahre(). Es wird also nicht nur der Methodenname angegeben sondern auch das Wort void und ein rundes Klammernpaar. Das erste Wort am Anfang wird auch als Rückgabetyp bezeichnet. Es teilt uns mit, was die Methode zurückliefert, wenn sie aufgerufen wird. Methoden mit dem Rückgabetyp void liefern nichts zurück. Sie führen nur einen Auftrag aus und enden dann. Bei der Methode boolean gesteinVorhanden() erwartet man einen Rückgabewert. Beim Rückgabetyp boolean gibt es nur zwei mögliche Werte: true (wahr) und false (falsch). Die Methode void drehe(String richtung) befiehlt dem Rover je nach Eingabe des Befehls "links" oder "rechts" (die Anführungsstriche müssen miteingegeben werden, da es sich um einen STRING-Befehl handelt) eine 90 Grad Drehung in die jeweilige Richtung. Durch Anklicken des Befehls im Objektmenü können die Methoden auch direkt aufgerufen werden.

df Wir wollen nun noch ein paar andere Objekte platzieren. Dabei fällt auf, dass die Gesteine zufällig rot oder blau sein können. Außerdem sieht man an diesem einfachen Beispiel, dass es möglich ist, beliebig viele Objekte einer Klasse zu erzeugen. Sind diese aber identisch? Im Objektmenü findet sich der Befehl "Inspizieren". Untersucht man die beiden Rover, so sieht man, dass diese unterschiedliche Namen haben (sinnigerweise rover und rover2) und unterschiedliche Werte für die x- und y-Koordinaten. Rover2 ist also keine Kopie von Rover; beide gehören aber zur selben Klasse. Eine Gesteinsuntersuchung liefert auch die richtige Farbe.

Programmierung in Greenfoot

Man kann den Rover natürlich durch direkte Methodenaufrufe per Hand steuern. Auf Dauer ist dies aber extrem lästig. Schaut man sich noch einmal die Oberfläche von Greenfoot an, dann entdeckt man im unteren Teil die drei Schaltflächen Run, Act und Reset. Betrachtet man mit dem Editor die Unterklassen von Actor, so sieht man, dass in allen Programmen die Methode public void act() vorhanden ist. Diese wird standardmäßig ausgeführt, wenn der Kopf "act" gedrückt wird oder auch mehrfach, nachdem der Knopf "run" angeklickt wurde.

Alle Anweisungen einer Methode sind von geschweiften Klammern umschlossen und jede Anweisung innerhalb einer Methode muss mit einem Semikolon beendet werden. Soll der Rover drei Felder vorwärts fahren, so lautet die Befehlsfolge in der Methode "act":

pubic void act()
{
fahre();
fahre();
fahre();
}

Man kann aber auch eigene Methoden schreiben. Der Methodenname sollte dabei aussagekräftig sein. Einmal definierte Methoden können von anderen Methoden aufgerufen werden. Der Methodenname kann frei gewählt werden, muss jedoch immer mit einem Buchstaben beginnen.

Beispiel 1

Aufgabe1Wir wollen die Theorie nun in die Praxis umsetzen. Das folgende Planetenfeld soll erkundet werden. Der Rover soll in den Tälern des Gebirges die Gesteinsvorkommen untersuchen und anschließend auf seine Heimatposition zurückkehren.

Die Anweisungen mit einzelnen Befehlen könnte wie folgt aussehen:

public void act()
{
fahre();
fahre();
drehe("links");
fahre();
fahre();
drehe("rechts");
fahre();
fahre();
drehe("rechts");
fahre();
analysiereGestein();
drehe("links");
drehe("links");
fahre();
drehe("rechts");
fahre();
fahre();
drehe("rechts");
fahre();
analysiereGestein();
drehe("links");
drehe("links");
fahre();
fahre();
analysiereGestein();
drehe("links");
drehe("links");
fahre();
drehe("rechts");
fahre();
fahre();
drehe("rechts");
fahre();
analysiereGestein();
drehe("links");
drehe("links");
fahre();
drehe("rechts");
fahre();
fahre();
fahre();
fahre();
drehe("links");
fahre();
fahre();
drehe("links");
}

Dieser Quellcode funktioniert, er ist aber unübersichtlich. Schaut man ihn genauer an, so sieht man, dass bestimmte Befehlsabfolgen mehrfach vorhanden sind.

fahre();
analysiereGestein();
drehe("links");
drehe("links");
fahre();

 

Diese Anweisungen kommen viermal vor; nämlich jedesmal, wenn der Rover ein Tal untersucht. Wir lagern deshalb diese Befehle in eine eigene Methode mit dem Namen "untersucheTal" aus. Unser Quellcode wird dann schon etwas übersichtlicher.

public void act()
{
fahre();
fahre();
drehe("links");
fahre();
fahre();
drehe("rechts");
fahre();
fahre();
drehe("rechts");
untersucheTal();
drehe("rechts");
fahre();
fahre();
drehe("rechts");
untersucheTal();
untersucheTal();
drehe("rechts");
fahre();
fahre();
drehe("rechts");
untersucheTal();
drehe("rechts");
fahre();
fahre();
fahre();
fahre();
drehe("links");
fahre();
fahre();
drehe("links");
}

Jetzt fällt auf, dass der Befehl "fahre" immer doppelt vorkommt. Also lagern wir diesen auch in eine neue Methode mit dem Namen "zweimalFahren" aus. Betrachtet man dann noch einmal den Quellcode, so sieht man, dass in der Regel jede Taluntersuchung mit einer Rechtsdrehung begonnen und beendet wird. Eine Ausnahme bildet der Übergang vom zweiten zum dritten Tal oder vom Übergang der unteren Talreihe in die obere Talreihe. Im Sinne einer Optimierung des Quellcodes kann es aber sinnvoll sein, auch für diesen Zustand durch entsprechende Zusatzbefehle die Standardmethoden zu verwenden. Wir ergänzen die Methode "untersucheTal" mit den beiden Drehbefehlen und ergänzen die Methode "act" mit den nötigen Zusatzbefehlen. Der Quellcode ist jetzt wesentlich übersichtlicher und kürzer.

public void act()
{
zweimalFahren();
drehe("links");
zweimalFahren();
drehe("rechts");
zweimalFahren();;
untersucheTal();
zweimalFahren();
untersucheTal();
drehe("rechts");
drehe("rechts");
untersucheTal();
zweimalFahren();
untersucheTal();
zweimalFahren();
zweimalFahren();
drehe("links");
zweimalFahren();
drehe("links");
}
public void untersucheTal()
{
drehe("rechts");
fahre();
analysiereGestein();
drehe("links");
drehe("links");
fahre();
drehe("rechts");
}
public void zweimalFahren()
{
fahre();
fahre();
}

 

Wiederholungen

Beispiel 2

In Beispiel 1 war die Anzahl der Täler vorgegeben. Stellen wir uns jetzt vor, dass die Schlucht mit den linken und rechten Tälern eine unbekannte Länge hat. Wie findet der Rover alle Täler und kommt heile zurück? Betrachten wir uns noch einmal die Landschaft. Der Hinweg wird durch eine Bergkette begrenzt. Solange diese nicht erreicht wird, können Täler besucht werden.

fg

while (!huegelVorhanden())
{
untersucheTal();
}

Das Ausrufezeichen (not) steht für die Verneinung der Bedingung. Allgemein lautet die Befehlsfolge while (Bedingung) {Anweisung; Anweisung; ...};. Der Wahrheitswert der Bedingung wird am Schleifenanfang (Schleife mit Eintrittsbedingung) überprüft. Es gibt auch die Möglichkeit, die Wahrheit der Bedingung am Schluss zu überprüfen (Schleife mit Austrittsbedingung). Hier lautet die Befehlsfolge do {Anweisung; anweisung; ...} while (Bedingung);. Im ersten Fall wird die Schleife nur ausgeführt, wenn die Bedigung erfült ist. Im zweiten Fall wird die Schliefe mindestens einmal durchlaufen.

Wie wird aber das Ende des Rückwegs überprüft? Hier gibt es keine Gebirgskette. Hier hilft ein simpler Trick. Der Rover setzt zu Beginn der Schlucht eine Markierung. Diese sucht er auf dem Rückweg. Hat er sie erreicht, ist das Ende der Schlucht erreicht und der geordnete Heimweg kann angetreten werden. Der Quellcode für das Beispiel 2 sieht dann wie folgt aus:

public void act()
{
zweimalFahren();
drehe("links");
zweimalFahren();
drehe("rechts");
setzeMarke();
while (!huegelVorhanden("vorne"))
{
zweimalFahren();
untersucheTal();
}
drehe("rechts");
drehe("rechts");
while (!markeVorhanden())
{
untersucheTal();
zweimalFahren();
}
entferneMarke();
zweimalFahren();
zweimalFahren();
drehe("links");
zweimalFahren();
drehe("links");
}

 

Beispiel 3

g

Der Rover soll einen Gebirgsblock umrunden. An dieser Stelle sei noch einmal darauf hingewiesen, dass sich die Lösung der Aufgabe immer auf das konkrete vorliegende Beispiel bezieht. Der Rover muss als Erstes seinen Startpunkt markieren. Die folgenden Befehle führt er nur dann aus, wenn er sich nicht auf seiner Startposition befindet. Deshalb muss er zunächst einen Schritt vorwärts fahren, damit er sein Startfeld verlässt. Anschließend fährt er solange sich Gebirge rechts befinden geradeaus. Anschließend dreht er sich nach rechts und fährt einen Schritt vor. Danach beginnt das Prozedere von vorn.

public void act()
{
setzeMarke();
fahre();
while (!markeVorhanden())
{
while (huegelVorhanden("rechts")) fahre();
drehe("rechts");
fahre();
}
}

 

Beispiel 4

Das Arbeitsfeld wird jetz etwas abgewandelt. Es gibt keine geraden Gebirgsketten mehr und zusätzlich muss der Rover auch noch eventuell vorkommende Gesteine untersuchen.

fd

Die Lösung ist sehr kurz. Aber wie kommt man zu diesem Ergebnis? Betrachten wir die Fahrtrichtung des Rovers. Er umkreist das Gebirge immer im Uhrzeigersinn.Geradeaus kann er fahren, wenn rechts ein Hügel und vorne kein Hügel ist. Zwei Bedingungen müssen also miteinander verknüpft werden. Dies erfolgt mit Hilfe logischer Operatoren. Die UND-Verbindung wird mit && und die ODER-Verbindung mit || dargestellt. Eine UND-Verbindung ist nur dann wahr, wenn beide Bedingungen, die verknüpft werden sollen, wahr sind. EIne ODER-Verbindung ist dann wahr, wenn mindestens eine Bedingung wahr ist. Wenn rechts kein Hügel und vorne kein Hügel ist, muss der Rover sich nacht rechts drehen und einen Schritt vorwärts fahren. Nach links muss er sich drehen, wenn rechts und vorne ein Hügel vorhanden sind.

public void act()
{
setzeMarke();
fahre();
while (!markeVorhanden())
{
while (huegelVorhanden("rechts") && (!huegelVorhanden("vorne"))) {fahre();analysiereGestein();}
while (huegelVorhanden("rechts") && (huegelVorhanden("vorne")))drehe("links");
while (!huegelVorhanden("rechts") && (!huegelVorhanden("vorne"))) {drehe("rechts");fahre();analysiereGestein();}
}
}

 

Bedingte Anweisungen

Betrachten wir uns noch einmal den Quelltext des letzten Beispiels. Nach jedem Schritt vorwärts wird eine Gesteinsanalyse durchgeführt, auch dann, wenn gar kein Gestein vorhanden ist. Die letzte While-Schleife wird genau einmal durchlaufen: Solange rechts und vorne kein Hügel ist, drehe dich und fahre vorwärts. Umgangssprachlich kann man in diesem Fall das Problem auch so formulieren; Wenn rechts und vorne kein Hügel ist, drehe dich und fahre vorwärts. Auch für diese Formulierung gibt es einen Java Befehl:

if (!huegelVorhanden("rechts") && (huegelVorhanden("vorne))

{

drehe("rechts");

fahre();

}

Wenn die Bedingung erfüllt ist, wird die Anweisung durchgeführt. Man kann auch eine Anweisung angeben, die ausgeführt werden soll, wenn die Bedingung nicht erfüllt ist. Sie sieht in Java wie folgt aus:

if (Bedingung)

{

Anweisung 1

}

else

{

Anweisung 2

}

Beispiel 5

fd

Der Rover fährt entlang eines Tales, an dem sich in unregelmäßigen Abständen Abzweigungen befinden. Jede Abzweigung soll markiert werden. Zu beachten ist, dass bereits am Startpunkt eine Abzweigung vorhanden sein kann oder auch am Wendepunkt.

public void act()
{
while (!huegelVorhanden("vorne"))
{
if (!huegelVorhanden("rechts")) setzeMarke();
fahre();
}
if (!huegelVorhanden("rechts")) setzeMarke();
dreheum();
while (!huegelVorhanden("vorne")) fahre();
dreheum();
}
public void dreheum()
{
drehe("rechts");
drehe("rechts");
}

Beispiel 6

Der Rover steht am Anfang eines langen Tals. Von diesem gehen mehrere Seitentäler nach Norden ab. Am Ende dieser Seitentäler können Getseine liegen. Diese sollen analysiert werden. Anschließend fährt der Rover an seine Startposition zurück.

er

public void act()
{
while (!huegelVorhanden("vorne"))
{
if (!huegelVorhanden("links")) besucheTal();
fahre();
}
if (!huegelVorhanden("links")) besucheTal();
dreheum();
while (!huegelVorhanden("vorne")) fahre();
dreheum();
}
public void dreheum()
{
drehe("rechts");
drehe("rechts");
}
public void besucheTal()
{
drehe("links");
while (!huegelVorhanden("vorne")) fahre();
if (gesteinVorhanden()) analysiereGestein();
dreheum();
while (!huegelVorhanden("vorne")) fahre();
drehe("links");
}

 

Zählschleifen

Eine spezielle Form der Wiederholung ist die Zählschleife. Sie wird eingesetzt, wenn bekannt ist, wieviele Wiederholungen eines Befehls durchgeführt werden sollen. DIe Schreibweise in JAva sieht wie folgt aus:

for (int i=0; i<6; i++)

{

Anweisung(en);

}

Die Schleife beginnt mit dem Schlüsselwort for. In runden Klammern folgenden dann die Angaben über die  Häufigkeit des Durchlaufes. Int steht für Integer und bedeutet, dass die Variable i eine Ganzzahl ist. Die Schleife beginnt mit dem Startwert i=0. Nach jedem Durchgang wir i um 1 erhöht. Der Befehl dazu lautet i=i+1 bzw. in der Kurzschreibweise i++. Solange i<4 ist, wird die Schleife durchlaufen. In diesem Beispiel wären es also vier Schleifendurchläufe: 1. Lauf i=0, 2. Lauf i=1, 3. Lauf i=2, 4. Lauf i=3. Nach dem vierten Lauf hat i den Wert 4. Da die Bedingung für den Schleifendurchlauf am Anfang der Schleife überprüft wird (i<4) findet kein weiterer Durchgang statt.

Beispiel 7

public void act()
{
for (int i=0;i<7;i++)
{
for (int j=0; j<2*i+1; j++)
{
setzeMarke();
fahre();
}
dreheum();
for (int j=0;j<2*i+2;j++)
{
fahre();
}
drehe("links");
fahre();
drehe("links");
}
for (int k=0;k<7;k++)
{fahre();
}
drehe("rechts");
setzeMarke();
fahre();
setzeMarke();
fahre();
drehe("rechts");
setzeMarke();
fahre();
}

Dieses Beispiel zeigt, dass Zählschleifen auch geschachtelt angewendet werden können. Zuerst startet die äußere Schleife; anschließend wird die innere Schleife komplet durchlaufen. Es geht wieder in die äußere Schleife usw., bis alle Durchgänge abgeschlossen sind. Zum Starten des Beispiels sezt man den Rover in die Mutte der obersten Zeile.

 

Das Greenfoot-Szenario Little Crab

In der Einleitung wurde erwähnt, dass Greenfoot sich besonders für die einfache Entwicklung von Simulationen und Spielen eignet. Dazu stellt das Programm eine Menge Befehle zur Verfügung, die wir bisher überhaupt nicht genutzt haben. Ganz richtig ist diese Formulierung nicht. Wenn man den Editor für die Planetenwelt öffnet, findet man in den installierten Methoden einige Greenfootbefehle wie move(), setRotation(), getRotation() usw.

Öffent man die Dokumentation der Klasse Actor, sieht man, welche Methoden diese Klasse für die Anwendung zu Verfügung stellt. Drei davon sind für das Projekt Little Crab wichtig: boolean isAtEdge() (stellt fest, ob der Akteur den Rand der Welt erreicht hat), void move(int distance) (bewegt den Akteur um die angegebene Strecke in Blickrichtung vor) und void turn(int amount) dreht den Akteur um die angegebene Zahl in Grad).

Es gibt aber auch Methoden, die nicht in der verwendeten Klasse definiert sind sondern in einer anderen Klasse. Eine solche Methode ist getRandomNumber(int limit). Sie ist eine Methode der Klasse Greenfoot.Deshalb muss im Methodenaufruf die Klasse mitgenannt werden: Greenfoot.getRandomNumer(20). Das System generiert jetzt eine Zufallszahl zwischen 0 und 19.

In unserem neuen Szenario soll eine Krabbe sich zufällig über den Bildschirm bewegen. Sie soll sich in Laufrichtung bewegen und in etwa 10% aller Aufrufe der Methode act ziwschen 0 und 45 Grad nach rechts oder links drehen. Da es sehr anstrengend ist, laufend die Taste act zu drücken, können wir hier die Taste run drücken. Sie ruft die Methode act in einer Endlosschleife auf.

Beispiel 8

gd

public void act()
{
if ( isAtEdge() )
{
turn(17);
}

if ( Greenfoot.getRandomNumber(100) < 10 )
{
turn( Greenfoot.getRandomNumber(90)-45 );
}

move(5);
}

Krabben brauchen Futter. Deshalb soll unserer Szenario eine Futterquelle für Krabben erhalten: Würmer. Was muss der Wurm können? Nichts! Er ist nur zum Fressen vorgesehen. Wie erkennt die Krabbe aber, dass ihr eventuel ein Wum begegnet? In der Dokumentation zur Klasse Actor werden die Methoden boolean isTouching(java.lang.Class clss) und void removeTouching (java.lang.Class clss) beschrieben. Beide Methoden erwarten die Angabe einer Java-Klasse. Diese haben wir ja gerade gebildet: Worm.class. Wir ergänzen unseren Programmcode und plazieren anschließend einige Würmer in der Krabbenwelt.

public void act()
{
if ( isAtEdge() )
{
turn(17);
}

if ( Greenfoot.getRandomNumber(100) < 10 )
{
turn( Greenfoot.getRandomNumber(90)-45 );
}

move(5);
if ( isTouching(Worm.class) )
{
removeTouching(Worm.class);
}
}

Fressen und gefressen werden: der natürliche Feind der Krabbe ist der Hummer. Wir werden also nich eine weitere Klasse bilden. Da der Hummer dasselbe wie die Krabbe macht, übernehmen wir die Methoden der Klasse Crab in die Klasse Lobster mit den notwendigen Namensänderungen. Wenn wir zwei Hummer platzieren, hat die Krabbe keine Chance mehr. Die Krabbe kann weder die Würmer noch die Hummer sehen. Wie sehen die Chancen aber aus, wenn wir die Krabbe steuern? Die Klasse Greenfoot enthält die Methode static boolean isKeyDOwn(String key). Mit Hilfe einer if-Abfrage kann die Methode act erkennen, welche Taste der Tastatur gedrückt wurde. Da wir die Krabbe steuern wollen soll die Drehung der Krabbe nicht durch einen Zufalsswert getseuert werden, sondern jed Tatstaturclick soll eine Drehung um 4 Grad nach rechts bzw nach links erzeugen.

public void act()
{
if ( isAtEdge() )
{
turn(17);
}

if ( Greenfoot.isKeyDown("left"))
{
turn(-4);
}
if ( Greenfoot.isKeyDown("right"))
{
turn(4);
}

move(5);
if ( isTouching(Worm.class) )
{
removeTouching(Worm.class);
}
}

Es ist lästig, bei jedem neuen Aufruf des Szenarios alle Objekte neu zu erstellen. Viel einfacher wäre es, wenn beim Start des Programmes alles automatisch erstellt würde. Deshalb betrachten wir die Klasse CrabWorld. Die Import-Befehle betrachten wir im Moment noch nicht. Es folgt der Klassenkopf. Der Klassenname ist frei wählbar. Man hätte statt des englischen Begriffes auch den deutschen Namen Krabbenwelt wählen können. Die Ergänzung extends World gibt an, dass die Klasse Crabworld von der Oberklasse WOrld abgeleitet wird. Danach kommt der für uns interessante Teil: der Konstruktor der Klasse. Der Name des Konstruktors muss mit dem Namen der Klasse identisch sein. Der Konstruktor setzt für die Welt eine Größe von 560 * 560 Zeilen mit einer Pixelgröße von 1 für jede Zeile fest. Dieser Konstruktor wird jedesmal ausgeführt, wenn eine Welt erzeugt wird. Um ein Krabbenobjekt zu erzeugen, muss erst eine Objektvariable erzeugt werden. Dies geschieht durch die Anweisung Crab myCrab;. Zuerst wird also die Klasse des Objektes angegeben und anschließend der Name, den das Objekt erhalten soll. Mit myCrab = new Crab(); wird ein neues Krabbenobjekt erzeugt und der Vraiblen myCrab zugeordnet. Beide Befehle können auch in einer Zeile zusammengefasst werden in der Form Crab myCrab = new Crab();. Das neugebildete Objekt muss jetzt noch einen Platz in der Krabbenwelt erhalten. Diese Zuweisung erfolgt durch die Anweisung addObject(myCrab, 250,200);. Die Krabbe wird damit auf die Position x=250 und y=200 gesetzt. Die Koordinaten sind dabei so aufgebaut, dass sich x=0 und y=0 links oben befinden.

 public CrabWorld()
{
super(560, 560, 1);
Crab myCrab = new Crab();
addObject(myCrab,250,200);
}

 

Setzt man nun alle benötigten Objekte mit Drop and Down in die Welt und speichert anschließend diese Welt unter "Ausführen --> Die Welt speichern", werden alle Objekte der Übersichtlichkeit wegen in einer Methode prepare gelistet, die beim Aufruf des Konstruktors automatisch aufgerufen wird.

Wir wollen jetzt noch eine letzte Veränderung des Programmes durchführen. Wie schon mehrfach erwähnt lassen sich mit Greenfoot leicht Spiele und Animationen programmieren. Die Krabbe soll deshalb animiert werden, d.h. es soll der Eindruck erweckt werden, dass sie laufen kann. Jeder hat sicher schon einmal ein Daumenkino hergestellt. Der Bewegungseffekt entsteht dadurch, dass jedes Bild ein etwas anderes Aussehen hat. Erzeugt man diese Bilder sehr rasch hintereinander, entsteht für das Auge eine Bewegung. Diesen Effekt nützen wir für die Animation der Krabbe aus. Wir benötigen zwei Bilder, die schnell hin und her getauscht werden. Greenfoot stellt eine Klasse namens GreenfootImage bereit, die die Verwendung und Verwaltung von Bildern erleichtert. Die Bilder müssen sich im Ordner images des entsprechenden Szenarios befinden. Wie ein neues Objekt innerhalb einer Klasse erstellt wird, haben wir vorhin beschrieben. Der benötigte Code lautet GreenfootImage image1=new GreenfootImage("crab.png"); und GreenfootImage iamge2 = new GreenfootImage("crab2.png");. Mit der Befehlsfolge setImage(image1) und setImage(image2)  werden die Bilder aufgerufen. Bei jedem Aufruf würde also das Objekt erzeugt, gespeichert und angewendet. Dieses Verfahren verschwendet Zeit und Speicherplatz. Warum sollte das Objekt zigmal geildet werden? Es reicht doch aus, es einmal zu speichern und dann entsprechend oft anzuwenden. Dafür muss man die Erzeugung des Objekts (GreenfootImage image1) von seiner Initialisierung image1=new GreenfootImage("Crab.png") trennen.

In Java und auch anderen Programmiersprachen unterscheidet man zwischen globalen und lokalen Variablen. Globale Variablen sind im gesamtten Programm, lokale nur in Unterprogrammen gültig. Globale Variablen oder Instanzvariablen werden zu Beginn der Klasse definiert. Vor der globalen Variablen muss das Schlüsselwort private stehen. Lokale Variablen werden innerhalb einer Methode definiert. SIe sind nur innerhalb der Methode gültig. Jedesmal wenn die Methode aufgerufen wird, werden die Variablen neu erzeigt. Mit Abschluss der Methode werden sie gelöscht. Globale Variablen sind für die Laufzeit der Klasse gültig. DAs Zuwesien der Werte und das Speicher in den Instanzvariablen erfolgt im Konstruktor. Für unser Spiel sieht der passende Ausschnitt deds Quellcodes folgendermaßen aus:

public class Crab extends Actor
{
private GreenfootImage image1;
private GreenfootImage image2;
public Crab()
{
image1 = new GreenfootImage("crab.png");
image2 = new GreenfootImage("crab2.png");
setImage(image1);
}

Der Befehl für das Umschaletn erfolgt dann in der Methode act. Die folgenden Befehle müssen noch eingeführt werden:

if (getImage() == image1)
{
setImage(image2);
}
else
{
setImage(image1);
}

== ist kein Gleichheitszeichen sondern ein Operator, mit dem sich zwei Werte vergleichen lassen. Andere Vergleichsoperatoren sind < kleiner als, > gößer als, <= kleiner gleich, >= größer gleich und != ungleich.