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":
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.
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
Wir 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.
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
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.
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
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.
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
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.