Threads

Werbung
Inhalt
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Threads
1.
Einleitung.................................................................................1
2.
Die Konstuktion von Threads...............................................1
3
Ein einfaches Beispiel.............................................................3
3.1
Laufzeitprobleme ...................................................................8
3.2
Synchronisationsprobleme ....................................................9
4.
Ein weiteres Beispielprogramm..........................................10
5
Eine Animation .....................................................................12
5.1
Aufgaben................................................................................18
6
Das Spiel Pong - ein Programmgerüst...............................19
6.1
Clipping .................................................................................22
6.2
Keyevents...............................................................................23
6.3
Aufgaben................................................................................24
Seite 1
Threads
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Was der Mensch über Threads wissen sollte
Im Normalfall haben wir es mit Programmen zu tun, in denen die Anweisungen einen "Anweisungsstrang" bilden, in dem die einzelnen Befehle nacheinader abgearbeitet werden. Java bietet
die Möglichkeit, mehrere von diesen Strängen ( Threads ) parallel ablaufen zu lassen, was z.B.
für animierte Grafiken von besonderm Vorteil sein kann, wie wir an einigen Beispielen sehen
werden oder schon gesehen haben.
Threads kann man in Java entweder aus der Klasse THREAD ableiten oder das Interface
RUNABLE verwenden.
In beiden Fällen muss der Befehlscode, der parallel zu einem anderen Befehlscode ausgeführt
werden soll, in einer Methode r u n angegeben werden.
Die Konstruktion von Threads - start, stop, run und interrupt
Ein neuer Thread wird mit der Methode s t a r t in Gang gesetzt. Ist s t a r t abgearbeitet, wird
selbsttätig die Methode run aufgerufen.
(Man sollte aus dem Applet oder dem Javaprogramm nie run selbst aufrufen.)
Im Normalfall wird ein Thread dadurch automatisch beendet, dass die Methode run fertig abgearbeitet ist. Manchmal jedoch ist eine Benutzung der Methode stop nötig, mit der man den
Thread von außen beenden kann.
Auch intern kann man einen Thread beenden, wenn man die Methode
public void interrupt()
benutzt, die dem Thread signalisiert, dass unterbrochen werden soll. Das wird in einem Flag
festgehalten, den man mit der Methode
public boolean isInterrupted()
abfragen kann.
Weiterhin kann man einen Thread dazu bringen, ein wenig schlafen zu gehen. Das geht mit der
Methode
public static void sleep(long Millisekunden)
die dafür sorgt, dass der Thread eine bestimmte Anzahl von Millisekunden anhält.
Nun kann aber ein anderer Thread einen Weckruf an den Schlafenden schicken und ihn vor Beendigung der Ruhezeit wecken wollen. Das packt Java nicht. Es ist eine Fehlerquelle, die immer mit einer try ... catch Anweisung abgefangen werden muss.
Diese sieht immer wie folgt aus:
try
{ threadname.sleep(Anzahlmillisekunden); }
catch (InterruptException e)
{}
Das wollen wir an einem einfachen Beispiel probieren.
Seite 2
Ein einfaches Beispiel
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Ein Applet soll parallel am Bildschirm folgendes tun:
a) Ein Zähler läuft von 1 bis 1000 und gibt seinen Zählerstand am Bildschirm aus. Wenn wir
davon ausgehen, dass das flott geht, können wir überlegen, ob wir mit sleep für eine gewisse Verzögerung sorgen wollen.
b) Ein Rechteck soll mehrfach mit jeweils einer neuen Zufallsfarbe am Bildschirm gemalt
werden.
c) In einem Rechteck soll ein Moiremuster gezeichnet werden.
Jeder sieht sofort , dass das ein Programm ist, das wir schon immer mal haben wollten.
Was dabei neu ist, ist das Moiremuster.
Wie geht das ??
Verraten wir jetzt ! Ein Rechteck am Bildschirm habe die Koordinaten (xEcke,yEcke) für die
obere linke Ecke und (xEcke+breit,yEcke+breit) für die untere rechte Ecke. Damit hat der
Mittelpunkt des Bildschirms die Koordinaten ((int)(xEcke+breit/2),(int)(yEcke+breit/2)). Die
Typkonvertierung mit dem (int) erfolgt nur für den Fall, dass breit/2 keine ganze Zahl ergibt.
Nun wird eine Linie gezogen von der Mitte zu (xEcke,yEcke), dann eine von der Mitte zu
(xEcke + dx ,yEcke), dann zum Punkt (xEcke+ 2*dx,yEcke) usw., wobei dx eine kleine Zahl ist, die
angibt, um wieviele Punkte man in x- Richtung vorankommen will. dy wird dann den Fortschritt in y- Richtung angeben. Es entsteht eine Art Fächer, der ein Moiremuster am Bildschirm hinterläßt.
Setze Zähler i auf 0
solange (xEcke + i*dx <= xEcke + breit)
Zeichne Linie von Mitte zum Randpunkt (xEcke+i*dx,yEcke)
erhöhe i um 1
Setze Zähler i auf 0
solange (yEcke + i*dy <= yEcke + breit)
Zeichne von Mitte zum Randpkt (xEcke+breit,yEcke+i*dy)
erhöhe i um 1
Setze Zähler i auf 0
solange (xEcke + i*dx <= xEcke + breit)
Zeichne von Mitte zum Randpunkt (xEcke+i*dx,yEcke+breit)
erhöhe i um 1
Setze Zähler i auf 0
solange (yEcke + i*dy <= yEcke + breit)
Zeichne von Mitte zum Randpunkt (xEcke,yEcke+i*dy)
erhöhe i um 1
Die entsprechenden Zeilen im Javaprogramm sehen wie folgt aus:
i=0;
while(xEcke+i*dx <= xEcke + breit)
{
myOffScreen.drawLine(xMitte,yMitte,xEcke+i*dx,yEcke); // obere Kante
i++;
Seite 3
Ein einfaches Beispiel
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
}
i=0;
while(yEcke+i*dy <= yEcke + breit)
{
myOffScreen.drawLine(xMitte,yMitte,xEcke+breit,yEcke+i*dy); // rechte Kante
i++;
}
i=0;
while(xEcke+i*dx <= xEcke + breit)
{
myOffScreen.drawLine(xMitte,yMitte,xEcke+i*dx,yEcke+breit); // untere Kante
i++;
}
i=0;
while(yEcke+i*dy <= yEcke + breit)
{
myOffScreen.drawLine(xMitte,yMitte,xEcke,yEcke+i*dy); // linke Kante
i++;
}
Die entsprechenden Variablen müssen natürlich vorher deklariert werden. Dabei ist myOffScreen ein Image für das Doublebuffering, wie wir es schon mehrfach verwendet haben.
Die Zählmaschine ( der Thread zu Punkt a ) geht wie folgt:
for (i=1;i<1001;i++)
{
myOffScreen.drawString("Zähler : "+Integer.toString(i);100,30);
}
wobei Integer.toString(i) die Zahl i in ein Stück String verwandelt
Das Rechteck mit der wechselnden Farbe ( der Thread zum Punkt b ) ist:
for (i=1 ;i<41;i++)
{
farbe = new Color((int)(Math.round(Math.random()*255)),
(int)(Math.round(Math.random()*255)),
(int)(Math.round(Math.random()*255)));
myOffScreen.setColor(farbe);
myOffScreen.paintRect(20,200,80,280);
}
Nun bauen wir das alles in einem Applet zusammen. Dabei wird wieder das Verfahren des
Doublebuffering angewendet, dass zuerst die Bilder in den Hintergrund malt und dann mit
paint sichtbar macht. Das sieht in dieser einfachen Fassung noch nicht besonders gut aus, wird
aber in den nächsten Programmen immer besser werden. Hierzu schaue man sich das Threadbälleprogramm von E. Modrow auch noch einmal genau an.
Nun aber das Listing:
Seite 4
Ein einfaches Beispiel
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
import java.awt.*;
import java.applet.Applet;
import java.lang.*;
public class Threadtest2 extends Applet
{
Moire
myMoire;
// eine Instanz der Klasse Moire ( siehe unten )
Recht
myRechteck; // eine Instanz der Klasse Recheck
Count
myCounter;
// der Zähler
Image
myoffScreenImage;
// Bild im OffScreen zum Malen
Graphics
myoffScreenGraphics; // Graphicobjekt für Bild im OffScreen
Dimension myoffScreenDim;
// Dimensionen des OffScreenbildes
public void init()
{
myMoire
= new Moire();
myRechteck = new Recht();
myCounter
= new Count();
myoffScreenDim = getSize();
// Hole Dimension aus HTML/Appletwindow
myoffScreenImage = createImage(myoffScreenDim.width,myoffScreenDim.height);
// erzeuge OffScrennImage mit createImage
myoffScreenGraphics = myoffScreenImage.getGraphics();
// füge ein Graphicsobjekt hinzu
myMoire.start();
// Methode start für alle Threads aufrufen
myCounter.start();
myRechteck.start();
} // Ende von Init, alle Threads erzeugt und gestartet
public void paint(Graphics g)
{
g.drawImage(myoffScreenImage,0,0,this);
repaint(10);
// *** Anmerkung beachten !
}
public void update(Graphics g)
{
paint(g);
}
public void stop()
{
myRechteck.interrupt();
myRechteck.stop();
myCounter.interrupt();
myCounter.stop();
myMoire.interrupt();
myMoire.stop();
}
***
Hinweis: Die Methode repaint mit einer Zahlenangabe für die Verzögerung funktioniert zur Zeit nicht, wenn die Javamaschine von SUN implementiert ist. ( März 2004 ). Da wir hoffen, dass dieser Fehler im nächsten Update behoben wird, bleibt die
Zeile im Programmlistimng stehen.
Seite 5
Ein einfaches Beispiel
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
//********************** jetzt die Klassen *****************
class Moire extends Thread
{
int
xEcke=80;
int
yEcke=80;
int
breit= 200;
int
dx=2;
int
dy= 3;
int
i,j,k;
// Zähler
int
xMitte,yMitte;
Graphics g;
Color farbe;
public void run()
// jeder Thread braucht sein eigenes run
{
while(true)
{
farbe = new Color((int)(Math.round(Math.random()*255)),
(int)(Math.round(Math.random()*255)),
(int)(Math.round(Math.random()*255)));
//Farbe
myoffScreenGraphics.setColor(farbe);
if(isInterrupted()) break;
xMitte=(int)(xEcke+breit/2);
// Mitte rechnen, könnte auch
yMitte=(int)(yEcke+breit/2);
// globale Grösse sein
i=0;
while(xEcke+i*dx <= xEcke + breit)
{
myoffScreenGraphics.drawLine(xMitte,yMitte,xEcke+i*dx,yEcke); // oben
i++;
for(k=0;k<10000;k++){};
// Kurze Pause/Verzögerung
}
i=0;
while(yEcke+i*dy <= yEcke + breit)
{
myoffScreenGraphics.drawLine(xMitte,yMitte,xEcke+breit,yEcke+i*dy);
i++;for(k=0;k<10000;k++){};
// Kurze Pause/Verzögerung
}
i=0;
while(xEcke+i*dx <= xEcke + breit)
{
myoffScreenGraphics.drawLine(xMitte,yMitte,xEcke+i*dx,yEcke+breit);
i++;for(k=0;k<10000;k++){};
// Kurze Pause/Verzögerung
}
i=0;
while(yEcke+i*dy <= yEcke + breit)
{
myoffScreenGraphics.drawLine(xMitte,yMitte,xEcke,yEcke+i*dy);
i++;for(k=0;k<10000;k++){};
// Kurze Pause/Verzögerung
}
try
// Thread schlafen lassen
{Thread.sleep(1000);}
catch(InterruptedException e){}
} // Ende von while
} // Ende von run
} // Ende von Moire
Seite 6
Ein einfaches Beispiel
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
class Recht extends Thread
{
int
i,j;
Color
farbe;
Graphics g;
public void run()
{
while(true)
{
if(isInterrupted()) break;
farbe = new Color((int)(Math.round(Math.random()*255)),
(int)(Math.round(Math.random()*255)),
(int)(Math.round(Math.random()*255)));
myoffScreenGraphics.setColor(Color.white);
myoffScreenGraphics.fillRect(400,200,480,300);
myoffScreenGraphics.setColor(farbe);
myoffScreenGraphics.fillRect(400,200,480,300);
try
{Thread.sleep(1000);}
catch(InterruptedException e){}
} // Ende von while
} // Ende von run
} // Ende von Recht
class Count extends Thread
{
int
i;
Graphics g;
public void run()
{
while(true)
{
if(isInterrupted()) break;
try
{Thread.sleep(50);}
catch(InterruptedException e){}
myoffScreenGraphics.setColor(Color.white);
myoffScreenGraphics.fillRect(100,10,300,35); // freimachen
myoffScreenGraphics.setColor(Color.black);
myoffScreenGraphics.drawString("Zähler : "+Integer.toString(i++),100,30);
// draufschreiben
try
{Thread.sleep(500);}
catch(InterruptedException e){}
}
// Ende von while
}
// Ende von run
}
// Ende von Count
} // Ende von Applet
Probieren sie das Programm und beobachten, wie die einzelnen Teile nebeneinander abgearbeitet werden.
Seite 7
Laufzeitprobleme
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Laufzeitprobleme:
Die oben vorgestellte Lösung wirft mit dem Entwicklungssystem JBuilder von Borland einige
Probleme auf, die hier angesprochen werden
müssen, obwohl uns systemspezifische Feinheiten eigentlich nicht interessieren sollten.
Gibt man den obigen Programmtext so im JBuilder ein, wird das Programm ohne Fehlermeldungen übersetzt und erzeugt dann unter JBulider 9 nach dem Start Laufzeitfehler ( NullPointerException).
Das liegt an den OffScreenImages, mit denen
hier gearbeitet wird.
Der VLIN - Kollege Stefan Bartels schreibt dazu:
"....bei einigen Programmen aus dem Skript gab es einen Laufzeitfehler, wenn das Applet im Applet-Viewer vom JBuilder ausgeführt wurde. Das war dann der Fall, wenn Hintergrundbilder (Stichwort "Double-Buffering" bzw. "Doppelpufferung")
mithilfe der Zeilen
Image im;
Graphics bg;
im = createImage(breite,hoehe);
bg = im.getGraphics();
verwendet werden sollten.
Lösung des Problems:
Man zwingt den JBuilder, direkt die HTML-Datei und nicht das Applet solo anzuzeigen. Dazu wählt man den Menüpunkt
"Projekt | Projekteigenschaften...", Register "Laufzeit", "Bearbeiten", Register "Start" und dort nicht die "Hauptklasse",
sondern die "HTML-Datei". Als HTML-Datei muss dann die zugehörige HTML-Datei aus dem Unterverzeichnis "Java" des
Projekts ausgewählt werden.
Herzliche Grüße,
Stefan Bartels "
Bei diesem Verfahren kann es noch passieren, dass man die gewünschte HTML-Datei nicht finden kann. Dann reicht ein Doppelklick auf die HTML-Datei im Projektfentser und schon klappt
es - hoffentlich.
Seite 8
Synchronisationsprobleme
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Applet über HTML-Datei im JBulider gestartet
Applet im Explorer gestartet
Das Vorgehen ist eine "Notlösung". Es ist schon sehr erstaunlich, dass die virtuelle Javamaschine von SUN solches Verhalten zeigt. Wenn man etwa das fertige Applet im Internetexplorer
von MIcrosoft laufen lässt, in den die Laufzeitumgebung von SUN eingebaut wurde, dann gibt
es keine Probleme. Sogar, wenn man das Programm auf dem Apple Macintosh erzeugt, die
HTML-Datei und die Klassendateien auf den PC übeträgt und wieder im Explorer mit SUN Maschine laufen lässt, klappt es. Nur im JBuilder selbst macht es Probleme. Was will uns das
sagen ?
Es gibt natürlich noch einen weiteren Weg aus den Schwierigkeiten: Man verzichtet auf die
Hintergrundgrafik.
Synchronisationsprobleme
Man bemerkt beim Programmablauf ein Problem, dass immer dann auftaucht, wenn Threads
gemeinsam auf eine Variable ( hier auf das OffScreenImage) zugreifen und dabei unterschiedlich lange brauchen. Wenn dann der Thread vom System unterbrochen wird, bevor er fertig ist,
gibt das Synchronisationsprobleme, die zu unerwarteten Ergebnissen führen können. Java hat
dafür ein Schlüsselwort bereitgestellt - s y n c h r o n i z e d - mit dem man Methoden oder Variablen solange schützen kann, bis der Thread mit seiner Arbeit fertig ist. Erst danach steht die
Methode oder die Variable für den Zugriff durch andere Threads wieder zur Verfügung. Ein
einfaches Beispiel, das auf dem vorangegangenen Programm basiert zeigt das nun:
Seite 9
Ein weiteres Beispielprogramm
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
/* Ein Thraedtest, der zeigt, welche Synchronisationsprobleme auftreten können
wenn man nicht beachtet, dass möglicherweise mehrer Threads auf dieselbe Variable zugreifen
nach einem Beispiel aus "Goto Java" geändert und komentiert von
H.-G.Beckmann 12 /01
*/
import java.awt.*;
import java.applet.Applet;
import java.lang.*;
public class Threadtest4 extends Applet
{
int
zahl=0;
//
Count
myCounter1,myCounter2; //
Image
myoffScreenImage;
//
Graphics myoffScreenGraphics;
//
Dimension myoffScreenDim;
//
da greifen beide drauf zu
die Zähler
Bild im OffScreen zum Malen
Graphicsobjekt für Bild im OffScreen
Dimensionen des OffScreenbildes
public void init()
{
myCounter1 = new Count();
myCounter2 = new Count();
myoffScreenDim = getSize();
//die nächsten drei Zeilen, wie gehabt
myoffScreenImage = createImage(myoffScreenDim.width,myoffScreenDim.height);
myoffScreenGraphics = myoffScreenImage.getGraphics();
myCounter1.start();
myCounter2.start();
// Ende von Init, alle Threads erzeugt und gestartet
}
public void paint(Graphics g)
// wie schon gesehen
{
g.drawImage(myoffScreenImage,0,0,this);
// zeichnet das OffScreenImage
repaint(10);
}
public void update(Graphics g)
{
paint(g); }
// auch bekannt
public void stop()
// auch bekannt
{
myCounter1.interrupt();
myCounter1.stop();
myCounter2.interrupt();
myCounter2.stop();
}
//********************** jetzt die Klasse *****************
class Count extends Thread
{
Graphics g;
Seite 10
Ein weiteres Beispielprogramm
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
public void run()
{
while(true)
{
if(isInterrupted()) break;
{
myoffScreenGraphics.clearRect(100,10,300,40);
myoffScreenGraphics.setColor(Color.black);
myoffScreenGraphics.drawString("Zähler : "+Integer.toString(zahl++),100,30);
}
try
{Thread.sleep(500);}
catch(InterruptedException e){}
} // Ende von while
} // Ende von run
}// Ende von Count
} //Ende von Applet
Beim Programmlauf sieht man, dass der Zähler springt. Es werden immer wieder Zahlen ausgelassen. Eine einfache Änderung in der run-Methode macht das Programm fehlerfrei . Ändern sie die Methode wie folgt um:
public void run()
{
while(true)
{
synchronized(getClass())
{
if(isInterrupted()) break;
{
myoffScreenGraphics.clearRect(100,10,300,40);
myoffScreenGraphics.setColor(Color.black);
myoffScreenGraphics.drawString("Zähler : "+Integer.toString(zahl++),100,30);
}
try
{Thread.sleep(500);}
catch(InterruptedException e){}
} // Ende von synchronized
} // Ende von while
} // Ende von run
Nun zählt es korrekt. getClass() besorgt die aktuelle Klasse und die wird dann mit synchronized
solange geschützt, wie nötig.
Aufgabe:
Bauen sie verschieden lange Pausen in den einzelnen run-Methoden des ersten Programms
ein. Zum einen können sie in den "leeren" Wiederholschleifen die Endwerte verändern und
zum anderen die Millisekundenangaben in den sleep-Methoden.
Testen sie auch was passiert, wenn in den sleep-Methoden der Wert 0 eingestellt wird.
Seite 11
Eine Animation
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Ein weiteres Beispiel mit kleinen Bildchen
Gern werden Threads benutzt, um Animationen zu erzeugen. Das Threadbälleprogramm hat
das schon gezeigt.
Im Folgenden werden kleine Bilder geladen und dann in Bewegung gesetzt.
Das Programmlisting unterscheidet sich ein wenig von dem obigen Beispiel. Diesmal wird das
Interface r u n a b l e benutzt. Es gibt zwar einen Unterschied zu der oben gezeigten Verfahrensweise ( Klasse mit extends Thread ) für uns ist der jedoch nicht von großer Wichtigkeit. Es zeigt
aber, dass Einiges im Programmtext etwas einfacher aussieht.
Vorarbeiten:
Sie brauchen 11 Bilder im GIF-Format. Dieses Grafikformat sollte jedes einfache Malprogramm
beherrschen. GIF unterstützt auch Transparenz, wenn es mit der Option GIF89 abgespeichert
wird. Allerdings ist die Transparenz nicht wichtig. Ein Bild darf sich von den anderen unterscheiden. Es hießt in diesem Beispiel "eki.gif" Die anderen sollten die Namen Image1.gif ....
Image10.gif heißen.
Die Bilder müssen sich dann im gleichen Verzeichnis befinden wie die HTML-Datei und die
Javaklasse.
Wenn alles soweit vorbereitet ist, dann kann es los gehen.
//
//
//
//
//
SpriteDemo Version 2 nach einem Programm von Anatoly Goroshnik aus dem Buch
Java 2 für Doofe mit Bildern von HGB 11/2001. Es werden kleine Bildchen
sog. Sprites verwendet,außerdem Threads und Arrays ausführlich komentiert.
In vielen Teilen geändert
H.-G.Beckmann 4.4.2004
import java.awt.*;
import java.applet.*;
import java.lang.*;
// das Übliche zu Beginn
// in java.lang ist die Klasse TREAD enthalten.
public class Sprite2 extends Applet implements Runnable
{
final int pause = 10;
// für sleep
final int numSprites = 11;
// Anzahl der Sprites
final boolean showTraces = false; // Spur am Bildschirm zeigen ja/nein
Seite 12
Eine Animation
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Hier wird nun in der Deklaration des Applets schon implements Runable angegeben.
Kleine Bilder hießen früher Sprites, daher dies Bezeichnung. Es hat nichts mit dem gleichnamigen Getränk zu tun. Sind Variablen als final deklariert, dann können sie im Programmablauf
nicht verändert werden. Das macht alles ein ganz klein wenig schneller.
Image
Graphics
Dimension
offScreenImage = null;
offScreenGraphics = null;
offScreenSize = null;
// OffScreen; mit null initialisiert
// OffScreen
// OffScreengraphics- wie gehabt
Da runable implementiert wurde, kann nun eine Variable der Klasse Thread vereinbart werden.
Die Klasse heißt hier demo könnte aber auch z.B. Animation heissen.
Thread
demo;
// java.lang bietet die Klasse Thread, demo ist
// eine Instanz dieser Klasse
Nun kommt ein Array mit Sprites, die Klasse Sprite wird noch definiert, der Array heißt demoSprites. Man sieht hier schon einen Unterschied zum obigen Programm.Die Sprites sind Instanzen einer Klasse, die nicht auch gleichzeitig ein Thread ist. Der Thread wird extra verwaltet.
Sprite
demoSprites[] = new Sprite[numSprites];
// 11 Bildchen im array
//******************************************
// Ende der Vorrede und nun ersteinmal init
//******************************************
public void init()
{
Image meinBild = null;
// eine Variable vom Typ Image
showStatus("lade Bilder..."); // showStatus ("...... zeigt
// eine Nachricht am unteren
// Fensterrand
Das erste Sprite-Bild soll nun geladern werden.Es gibt mehrere Möglichkeiten, Bilder von Festplatte zu laden.Die Methode g e t I m a g e ( S t r i n g D a t e i n a m e ) tut es eigentlich schon.
Stehen aber die Bilder z.B. in einem Verzeichnis auf einem Webserver und wird das Applet auf
den heimischen Computer geladen, dann würde die Javamaschine auf dem häuslichen Computer nach den Dateien fahnden und sie dort nicht finden. Um dieses Problem zu beseitigen,
wird mit der Methode g e t D o c u m e n t B a s e ( ) die URL des aktuellen Verzeichnisses geliefert,
in dem das Applet liegt, dass den getImage-Aufruf enthält.
meinBild = getImage(getDocumentBase(), "eki.gif");
// Bild geladen
Das erste Bild ist drin und soll im Array die Position 0 einnehmen. Die Klasse Sprite enthält einen Konstruktor, der mehrere Parameter erwartet:
Sprite (Image meinBild, int posX, int posY)
Image
Links-Oben x
Links-Oben y
Nun kann das Bild in den Array aus Sprites eingefügt werden:
demoSprites[0] = new Sprite(meinBild, 100, getSize().height - 100);
Seite 13
Eine Animation
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Nun ist das Bild im Array und für die Darstellung am Bildschirm hat es die Koordinaten (100,
Fensterhöhe - 100). Nun werden sofort die weiteren Attribute für diesen neuen Sprite gesetzt,
so wie sie in der Klassendefinition vorgesehen sind. Das sind im Besonderen die Geschwindigkeit, die das Bildchen in x- und y-Richtung haben soll. Dann wird auch sofort die Methode
s t a r t M o v e m e n t aufgerufen, die für die Bewegung zuständig sein soll.
demoSprites[0].vx=5;
demoSprites[0].vy=6;
demoSprites[0].startMovement();
// Geschwindigkeitskomonente in x-Richtung
// und in y-Richtung
// flieg mal los
Jetzt werden die andern Arraybilder geladen. Arrayeintrag Nummer 0 ist schon vergeben, also
geht es mit 1 los.
for (int i = 1; i < numSprites; i++)
{
meinBild = getImage(getDocumentBase(), "image" + i + ".gif");
demoSprites[i] = new Sprite(meinBild, 30+30*i, 30);
// alle nebeneinander
demoSprites[i].vx=(int)i/2 + 2;
// unterschiedliche
demoSprites[i].vy=(int)((10-i)/2);
// Geschwindigkeiten
if(demoSprites[i].vy == 0){demoSprites[i].vy=4;}
// aber nicht Null
demoSprites[i].startMovement();
}// Ende von for .....
showStatus("");
}// Ende von init
// wenn alle drin dann Mitteilung weg
//************************************************************************
// Nun kommt die Methode run, die geschrieben werden muss da wir runable
// implementiert haben; run ist hier ans ganze Applet
// gebunden und nicht an eine bestimmte Klasse
//************************************************************************
public void run()
{
while (true)
// wiederhole auf ewig
{
for (int i = 0; i < numSprites; i++) // wiederhole für alle 11 Sprites ..
{
demoSprites[i].tick();
// führe die Methode ticks aus ( siehe unten )
//***********************************************************
// Abprallen am Randes des Grafikbildschirms
//**********************************************************
if ((demoSprites[i].positionX<=15) ||
(demoSprites[i].positionX >= getSize().width-15))
demoSprites[i].vx=-demoSprites[i].vx;
if ((demoSprites[i].positionY <= 15) ||
(demoSprites[i].positionY >= getSize().height-15))
demoSprites[i].vy=-demoSprites[i].vy;
}// Ende von for ........
Seite 14
// wenn Rand
// umkehren
// umkehren
Eine Animation
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
//************************************************************
// Try Anweisung ..... catch Ausnahmetyp x
// hier also: bei Tread.sleep könnte ein Fehler entstehen
// wenn der Ausnahmetyp InterruptException falsch ist
// wenn das der Fall ist wird bei break weitergemacht
//***********************************************************
update(getGraphics());
// die Methode update kommt noch weiter unten
try
{
Thread.sleep(pause);
// funktioniert auch ohne direkte Angabe der
// Threadbezeichnung
} catch (InterruptedException e)
{break;}
}
}// Ende von run
//*************************************
// Standardmethoden für Treads hier
// im Applet selbst überschrieben
//*************************************
public void start()
{
d emo = new Thread(this);
// neuer Thread, this bezieht sich aufs
// Applet
demo.start();
// starte den Thread
}// Ende von Start
public void stop()
{
demo.stop();
demo = null;
} // Ende von stop
// demo auf null zeigen lassen
Wenn eine Methode als final deklariert wird, dann kann sie während des Ablaufs des Programms nicht mehr überschrieben werden. Das wollten wir ja ohnehin nicht. Wieder wird der
Code durch diese Maßnahme ein klein wenig schneller.
Es wird wieder mal um die flimmerfreie Grafik mit Doublebuffering gehen. Also in ein
OffScreenImage hineinmalen und dann erst das fertige Gesamtbild anzeigen lassen. Hier mal
in der update-Routine untergebracht.
public final void update(Graphics g)
{
Dimension dim = getSize();
// Ausmaße des Bildes
if((offScreenImage==null) ||(dim.width != offScreenSize.width) ||
(dim.height != offScreenSize.height))
Was für eine Fehlerabfrage ! Wenn Das OffScreenImage auf null zeigt, also leer ist, oder die
Bildgröße von der Größe des Offscreenbildes abweicht, dann wird ersteinmal ein passendes
Bild mit c r e a t e I m a g e erzeugt, bei dem dann alles stimmt.
{
offScreenImage = createImage(dim.width, dim.height);
offScreenSize = dim;
Seite 15
Eine Animation
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
offScreenGraphics = offScreenImage.getGraphics();//hole die passende Grafik
if (showTraces)
//wollen wir eine Spur sehen ?
offScreenGraphics.clearRect(0, 0,offScreenSize.width,offScreenSize.height);
} // Ende der if((offScreenImage==null)... Abfrage
if (!showTraces)
// das wird es sein
offScreenGraphics.clearRect(0, 0,offScreenSize.width,offScreenSize.height);
paint(offScreenGraphics);
// Methode paint wird mit offSCreenGraphics aufgerufen
g.drawImage(offScreenImage,0,0,null);
// Komplettes Bild aus dem OffScreen holen
}
Hier wird also unterschieden. Ist s h o w Tr a c e s wahr, dann will man Spuren sehen und auf
dem Bild wird nichts gelöscht. Ist s h o w Tr a c e s unwahr, dann muss vor dem Neumalen der
Bildschirm gelöscht werden, was mit c l e a r R e c t passiert. Wie schon in unseren bisherigen update-Methoden wird hier dann paint aufgerufen. Normalerweise haben wir dann in paint den
g . d r a w I m a g e -Aufruf gepackt. Hier machen wir das innerhalb der update-Methode. Da aber
vorher paint aufgerufen wird müssen wir mal sehen, was da passiert.
public void paint(Graphics g)
{
for (int i = 0; i < numSprites; i++)
{
demoSprites[i].internPaint(g); // Achtung paint-Methode innerhalb von Sprite
}
// siehe unten
}
} // Ende von paint
//******************************************************************
// Nun aber endlich die Klasse Sprite, die die Klasse
// Panel erweitert. Panel ist ein Container, der die
// Objekte enthält. Es könnte auch heissen : extends Component
// das würde genauso funktionieren
//******************************************************************
class Sprite extends Panel
{
//Konstanten, die ausserhalb der Klasse
//benutzt aber nicht verändert werden koennen
final static int MAXSPEED = 12;
// Höchstgeschwindigkeit
// Konstanten, die nur fuer die abgeleiteten Klassen zugaenglich sind
// hier jeweils mit Starwerten besetzt
protected final static int StandardGroesse = 30;// Grösse der Bildchen
// ein
// mit
float
float
float
float
int
paar geometrische Informationen die ebenfalls
Startwerten initialisiert sind
positionX = StandardGroesse;
//Koordianten hier vom Typ float
positionY = StandardGroesse;
width = 0;
height = 0;
vx=5,vy=5;
// Geschwindigkeitskomponenten
//alles andere
Seite 16
Eine Animation
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
protected boolean isMoving = false;
protected Image myImage = null;
// erstmal noch keine Bewegung
// noch kein Bild da
// Nun der Konstruktor, denm wir benutzen -- siehe oben
Sprite (Image meinBild, int posX, int posY)
{
myImage
= meinBild;
positionX
= posX;
positionY
= posY;
setDimensions();
}
Hier kommt nun die Methode, die später für die Bewegung des Sprites zuständig sein wird.
Sie tut nichts anderes als ein Kontrollflag, das mit false besetzt war auf true zu stellen. So ein
Aufwand für sowenig Programm !
public void startMovement ()
{
isMoving = true;
}
// und nun das Gegenteil
public void stopMovement ()
{isMoving = false;}
//**************************************************************
// Die Tick-Methode wird von der bei jedem
// Tick aufgerufen. Sie bewegt das Sprite entlang seiner
// Bewegungsrichtung, falls noetig.
//**************************************************************
public void tick ()
{
if ((isMoving) && (vy != 0)&&(vy!=0)) // wenn in Bewegung dann ....
{
positionX =positionX+vx;
// kennen wir schon
positionY = positionY+vy;
// hatten wir schon
}
}
Jetzt kommt die interne paint-Methode, die nicht etwa die externe paint-Methode des Applets
überschreibt. Diese Methode malt in das OffScreenImage hinein, so wie wir es schon mehrfach in den vorangegangenen Beispielen gesehen haben.
//** malt das Sprite *
public void internPaint(Graphics g)
{
g.drawImage(myImage, (int)(positionX), (int)(positionY), this);
}
Seite 17
Eine Animation
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
// die folgende Methode stellt die Bildmasse fest und gibt sie bekannt
protected void setDimensions()
{
while (myImage.getHeight(this) == -1) // warte, bis das Bild geladen ist
{}
// tue nix
width = myImage.getWidth(this);
height = myImage.getHeight(this);
}
}
Nun sind hoffentlich die internen Abläufe in diesem Programm hinreichend erklärt und es ist
klar, dass sich hier nette Aufgaben anschließen sollten.
Aufgabe:
Erzeuge weitere Bilder, so dass sich 21 Sprites bewegen.
Sorge dafür, das die Sprites elastische Stösse ausführen.
Sorge dafür, dass ein grosses Sprite die kleinen bei Begegnung "auffressen" kann, dass also die
kleinen Sprites dann aus dem Array und damit aus dem Thread entfernt werden.
Aufgabe:
Nun wird es etwas umfangreicher. Das klassische Spiel "Pong" soll programmiert werden.
Dazu brauchen wir ein nettes Hintergrundbild und zwei kleine Bilder im Vordergrund - einen
Ball und einen sogenannten Paddel. Der Ball soll - wie es nicht anders zu erwarten war - über
den Bildschirm flitzen ( wie gehabt ). Am unteren Bildschirmrand aber bewegt sich der Paddel
horizonatl hin - und her. Er wird mit den Cursortasten gesteuert und soll den Ball daran hindern, nach unten zu verschwinden.
Eigentlich können wir alles, was für dieses Programm notwendig ist. Aber einige kleine Hilfen
sollten es schon noch sein, damit man nicht an unnnötigen Stellen Probleme bekommt.
Im folgenden Listing sind die wichtigsten Methoden und dargestellt, die das Spiel PONG benötigt. Die hier vorgestellte grobe Lösung arbeitet noch nicht mit Threads. Das Spiel mit
Threads zum Laufen zu bringen bleibt am Ende als Aufgabe übrig.
/*
Es geht um den Spieleklassiker PONG. Ein Paddel unten und ein Ball der Boing macht
und ein Hintergrundbild vor dem sich teilweise transparente GIF-Bilder bewegen
H.-G. Beckmann aus 4/2004
*/
import
import
import
import
java.awt.*;
java.awt.event.*;
java.applet.*;
java.lang.*;
// das Übliche zu Beginn
Seite 18
Das Spiel Pong - ein Programmgerüst
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
public class Pong extends Applet
{
Image
offScreenImage = null;
Graphics
offScreenGraphics = null;
Dimension
offScreenSize = null;
int
SpeedX=5,SpeedY=5;
int
SpeedPaddel=5;
int
paddelX = 300;
int
paddelY = 0;
Image meinBallBild,meinPaddelBild;
Image meinBG;
int
ballbreite,ballhoehe;
int
paddelbreite,paddelhoehe;
int
ballX=50,ballY=53;
MediaTracker myTracker;
kbListener myKBL= new kbListener();
boolean
boolean
boolean
.......
.......
leftdown=false;
rightdown=false;
imSpiel=true;
//
//
//
//
//
//
//
ein Bild, dass wir nicht sehen
dazu ein Graphicskontext
Ausmaße des Bildes
Ballgeschwindigkeit mit 5 initialisiert
Paddel mit Tempo 5 initialisiert
x-Position des Paddels
y-Position des Paddels
//
//
//
//
//
//
//
//
//
//
//
die Bildchen
das Hintergrundbild
Dimensionen des Ballbildes
Dimensionen des Paddelbildes
Ballposition
der Medienspürhund,siehe unten
für die Keyboardevents eine eigene
Klasse
Taste <-- ist gedrückt
Taste --> ist gedrückt
ist der Ball noch im Spiel ?
Hier sind wieder alle Variablen versammelt, die uns auch schon in den vorangegangenen Programmen begegnet sind.
Neu ist der M e d i a Tr a c k e r . Dieser Medienspürhund hilft beim fehlerfreien Laden von Bildern. Das Einlesen von Bildern ( unterschiedlicher Größe) erfolgt in Java asynchron. Das heißt,
es wird schon mit der nächsten Anweisung fortgefahren auch wenn der Ladevorgang noch
nicht beendet ist. Das kann zur Darstellung von halben Bildern führen und wer möchte das
schon.
Neu ist auch der KeyboardListener - k b L i s t e n e r - der ähnlich finktioniert, wie der schon bekannte ActionListener für Buttons.
Der kbListener ist für Tastaturevents zuständig und wird in einer eigenen Klassendefinition zu
sehen sein.
Zuerst aber zum MediaTracker:
In der Methode init wird der Mediatracker wie folgt eingebaut:
public void init()
{
// Hole Screendimension, erzeuge ein OffScreenBild, erzeuge dafür auch
// einen Grafikkontext und erzeuge einen neuen Mediatracker
offScreenSize = getSize();
offScreenImage = createImage(offScreenSize.width,offScreenSize.height);
offScreenGraphics = offScreenImage.getGraphics();
MediaTracker myTracker = new MediaTracker(this);
// ein neuer Spürhund
showStatus("lade Bilder...");
// Nachricht am unteren Fensterrand
Seite 19
Das Spiel Pong - ein Programmgerüst
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
//*************************************************************************
// Lade die Bilder
//*************************************************************************
meinBG = getImage(getDocumentBase(), "BG.jpg");
Nun kommt der Mediatracker zum Einsatz
addImage übergibt dem MediaTracker das angegebene Bild. Es bekommt im MediaTracker die
angegebene ID-Nummer - hier die Nummer 0
myTracker.addImage(meinBG,0);
try
{myTracker.waitForID(0); }
catch(InterruptedException e) {}
// Achtung Hund, hier das erste Bild
// warte bis Bild mit ID =0 fertig ist
// Fehler abfangen für Bild ID 0
offScreenGraphics.drawImage(meinBG,0,0, null);
// jetzt malen
Damit ist das Hintergrundbild eingelesen und im OffScreen gemalt. Nun geht das mit den beiden andern Bildern genauso:
meinBallBild = getImage(getDocumentBase(), "Ball2.gif");
myTracker.addImage(meinBallBild,1);
// Achtung Hund, hier das nächste Bild
try
{myTracker.waitForID(1);
// warte auf Bild mit ID = 1
} catch(InterruptedException e) {}
offScreenGraphics.drawImage(meinBallBild,ballX,ballY, null);
ballbreite=meinBallBild.getWidth(this); // Bildbreite falls man sie braucht
ballhoehe=meinBallBild.getHeight(this); // Bildhöhe falls man sie braucht
meinPaddelBild= getImage(getDocumentBase(),
paddelX=300;
//
paddelY=370;
//
myTracker.addImage(meinPaddelBild,2); //
try
{myTracker.waitForID(2);
//
} catch(InterruptedException e) {}
"Paddel.gif");
an dieser Position
soll das Bild erscheinen
und der Hund passt wieder auf
auch Bild 3 sicher laden lassen
offScreenGraphics.drawImage(meinPaddelBild,paddelX, paddelY, null);
paddelbreite=meinPaddelBild.getWidth(this); // Paddelausmaße, falls man sie
paddelhoehe=meinPaddelBild.getHeight(this); // später braucht
showStatus("");
// alles erledigt
//****************************************************
// Nun wird dem ganzen Applet ein KeyListener
// zugeordnet, desssen Klassendefiniton unten steht
//****************************************************
addKeyListener(myKBL);
} // Ende von init
Seite 20
Das Spiel Pong - ein Programmgerüst
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Jetzt die paint-Methode, in der sich in dieser Fassung alles abspielt
//**********************************************************
public void paint(Graphics g)
{
if (imSpiel==true)
{
ballX=ballX+SpeedX;
// Ball in x-Richtung
//************ Nun prüfen , ob am Rande reflektiert wird *******************
if((ballX >offScreenSize.width-25)||(ballX <10)) {SpeedX=-SpeedX;}
ballY=ballY+SpeedY;
Spannend nun die Abfrage in Y-Richtung, wenn der Ball in Höhe des Paddels kommt. Es
muss geprüft werden, ob der Paddel passend steht, sonst fliegt der Ball vorbei und ist aus
dem Spiel.
if(ballY >= paddelY-22)
// wir kommen in Paddelhöhe
{ if((ballX>=paddelX-10)&&(ballX<paddelX+100))
// Ball im Paddelbereich ?
{
SpeedY=-SpeedY;
// Am Paddel -->dann reflektieren
imSpiel = true;
}
else
{imSpiel =false;}
// am Paddel vorbeigeflogen
}
// **************** Reflexion am oberen Rand hier einfach *******************
if (ballY<10) {SpeedY=-SpeedY;}
//**********************************************************
// die Tasten abfragen mit Anschlag am rechten oder
// linken Spielfeldrand
//**********************************************************
if(leftdown)
// ist die Linkspfeiltaste gedrückt ?
{
paddelX=paddelX-SpeedPaddel;if (paddelX<=5) {paddelX=5;}
}
if(rightdown)
// ist die Rechtspfeiltaste gedrückt ?
{
paddelX=paddelX+SpeedPaddel;if (paddelX>=520) {paddelX=520;}
}
//Im offScreenImage alles auf den neuesten Stand bringen und dann an
// g übergeben
offScreenGraphics.drawImage(meinBG,0, 0, null);
// jetzt malen
offScreenGraphics.drawImage(meinBallBild,ballX, ballY, null); // jetzt malen
offScreenGraphics.drawImage(meinPaddelBild,paddelX, paddelY, null);
g.drawImage(offScreenImage,0,0,this); // zeichnet das OffScreenImage jetzt in g
repaint(10);
} // Ende von imSpiel = true. Ball unten druch, dann hält das Spiel einfach an
}
Seite 21
Das Spiel Pong - ein Programmgerüst
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Diese Art, die Bilder immer auf dem animierten aktuellen Stand zu zeigen hat offensichtlich
Nachteile. Trotz Malens im OffScreen treten Wackeleffekte auf. Das hat damit zu tun, dass das
System alle drei Bilder komplett neu malen muss. Da das Hintergrundbild den ganzen Bildschirm einnimmt, kostet es auch viel Zeit, das Bild neu zu malen.
Hier gibt es eine gute Lösung über das sogenannte Clipping. Das ist die Einschränkung des
neu zu malenden Bereichs auf einen rechteckigen Bereich, der sehr viel kleiner ist als das ganze
Bild. In unserem Spiel muss eine kleiner Bereich des Hintergrundbereichs neu gemalt werden,
wenn der Ball an der alten Position verschwinden soll und dort das Hintergrundbild wieder erscheinen soll. Das gilt auch für den Paddel, dessen Position sich verändert. Auch an desses alter
Position muss das Hintergrundbild wieder neu gemalt werden. Zuerst wird mit der Methode
setClip(int x, int y, int breit, int hoch)
festgelegt an welcher Stelle ein Bereich mit welchen Ausmaßen neu zu zeichnen ist. Im nachfolgenden drawImage - Befehl wird dann nur dieser Bereich bearbeitet. Man bemerkt sofort, dass
die Animation deutlich geschmeidiger abläuft.
Damit sollten die obigen Programmzeilen ersetzt werden:
offScreenGraphics.setClip(ballX-10,ballY-10,50,50); //wo war der Ball ?
offScreenGraphics.drawImage(meinBG,0, 0, null); // jetzt malen in Clippingregion
if(imSpiel==true)
{
// nun neuen Ball, falls der noch im Spiel ist
offScreenGraphics.drawImage(meinBallBild,ballX, ballY, null);
}
// gleiche Übung für den Paddel
offScreenGraphics.setClip(paddelX-10,paddelY,paddelbreite+20,paddelhoehe);
offScreenGraphics.drawImage(meinBG,0, 0, null); // jetzt malen
offScreenGraphics.drawImage(meinPaddelBild,paddelX, paddelY, null);// neuer Paddel
// Jetzt das ganze Bild in den Vordergrund
g.drawImage(offScreenImage,0,0,this); // zeichnet das OffScreenImage jetzt in g
repaint(10);
} // Ende von imSpiel = true .Ball unten durch, dann hält das Spiel einfach an
}
public void update(Graphics g)
{
paint(g);
}
Die Werte in der setClip-Methode sind hier teilweise "per Hand" gesetzt. Mann kann sie aber
komplett aus den Bildausmaßen ermitteln. Beachten Sie aber, dass hier kleine gemeine Fehler
auftreten können, da sich der Ball mal nach links bewegt ( seine x-Koordinaten werden dann
kleiner ) oder aber nach rechts ( seine x-Koordinaten werden dann größer ).
Bleibt zum guten Schluss noch der kbListener, der die Tastaturabfrage für uns erledigt.
Darin wird mit vorgegebenen Konstanten gearbeitet, nämlich VK_LEFT für die Linkscursortaste, VK_RIGHT für die Rechtscursortaste. Einige der pflichtweise zu überschreibenden Methoden sind hier leer gelassen worden.
Seite 22
Das Spiel Pong - ein Programmgerüst
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
//***************************************************************************
// Hier nun die Klasse für den KeyListener
// es muss mindestens keyPressed, keyTyped und keyReleased geschrieben werden
// dabei können diese Methoden auch leer sein
//****************************************************************************
class kbListener implements KeyListener
{
public void keyPressed (KeyEvent myEvent)
{
if (myEvent.getKeyCode()==KeyEvent.VK_LEFT)
{leftdown=true;}
if (myEvent.getKeyCode()==KeyEvent.VK_RIGHT)
{rightdown=true;}
// VK_LEFT ist vorgegeben
// VK_RIGHT ist vorgegeben
}
public void keyTyped (KeyEvent myEvent) {}
public void keyReleased (KeyEvent myEvent)
// leer
// man muss ja auch merken, wenn die
// Taste nicht mehr gedrückt ist
{
if(leftdown){leftdown=false;}
if(rightdown){rightdown=false;}
}
} // Ende von KeyListener Klasse
} // Ende vom Applet
Man muss die Keyboardabfrage nicht in einer eigenen Klasse behandeln. Wie auch schon beim
Abfragen von ActionEvents ist es möglich, dem ganzen Applet mit
public class Pong extends Applet implements KeyListener
einen KeyListener zu spendieren, der dann in der Methode init mit
addKeyListener(this);
dem Applet zugewiesen wird.
Dann muss man nur noch dieoben angegebenen Methoden überschreiben.
Wenn es wieder Laufzeitprobleme geben sollte, dann verzichten sie auf das OffScreenImage und
malen alles direkt in der paint-Methode in den Grafikkontext g hinein.
Dies ist nun insgesamt ein ausbaufähiges Programmgerüst, das auf mancherlei Weise erweitert
oder verbessert werden soll.
Seite 23
Das Spiel Pong - Aufgaben
Virtuelle Lehrerfortbildung im
Fach Informatik in Niedersachsen
© Hans-Georg Beckmann 2004
Aufgaben
Bringen sie das Programm mit Threads zum Laufen.
Fügen sie ein Punktezähler hinzu, der am Spielfeldrand Punkte mitzählt.
Fügen sie ein wenig Zufall bei der Ballreflexion am Paddel ein. (Geschwindigkeits - und Richtungsänderungen ).
Geben sie eine Maximalzahl von Bällen vor. Wenn alle verbraucht sind, ist das Spiel zu Ende.
Bauen sie Hindernisse auf, die mit dem Ball "abgeschossen" werden können .
Seite 24
Herunterladen