GameLoopThread – Bewegung von Bildern
Ein Thread in Java kann aus verschiedenen Sichten charakterisiert werden. Inhaltlich sind einzelne Threads eine in sich sequenzielle Abfolge von Anweisungen, als Ganzes gesehen laufen sie jedoch parallel zu anderen Threads.
Vom objektorientierten Standpunkt betrachtet ist ein Thread in Java auch ein Objekt. Um einen neuen Thread zu erzeugen, kann man eine Unterklasse der Klasse Thread ableiten, in dieser die Methode run() überschreiben und dann ein Exemplar von dieser neuen Unterklasse erzeugen. Dieses so erzeugte Thread-Objekt kann dann über den Aufruf entsprechender Methoden gestartet und gesteuert werden.
Legen wir also eine Klasse “GameLoopThread” an die von Thread erbt. Wir brauchen zwei globale Variablen:
private GameView theView;
private boolean isRunning = false;
Da wir den Thread in unserem GameView verwenden möchten, legen wir ein Objekt davon an. Die boolsche Variable isRunning brauchen wir um festzulegen ob unser Thread nun laufen soll oder angehalten sein soll.
Und wir benutzen nun einen Thread dazu unser Bild bei jedem Aufruf der onDraw Methode um einen Pixel tiefer zu zeichnen. Daher entsteht der Eindruck, dass das Bild sich bewegt.
Im Konstruktor legen wir nur fest, welchen Parameter wir unserem GameLoopThread übergeben möchten.
public GameLoopThread(GameView theView) {
this.theView = theView;
}
Dann benötigen wir einen Setter für unsere Variable isRunning, um von extern aus den Thread anzuhalten, bzw zu starten.
public void setRunning(boolean run) {
isRunning = run;
}
Und nun kommt der spannende Teil. Die Methode run() die wir von Thread haben und überschreiben:
@Override
public void run() {
while (isRunning) {
Canvas theCanvas = null;
try {
theCanvas = theView.getHolder().lockCanvas();
synchronized (theView.getHolder()) {
theView.onDraw(theCanvas);
}
} finally {
if (theCanvas != null) {
theView.getHolder().unlockCanvasAndPost(theCanvas);
}
}
}
}
Während isRunning true(wahr) ist, soll der Canvas leer gemacht, also auf “null” gesetzt, werden. Dann wird der surfaceHolder der GameView klasse synchronisiert(via synchronized), damit keine Konflikte mit anderen Threads auftreten. Außerdem malen wir auf den Canvas der GameView Klasse. Wenn das geschehen ist, und theCanvas somit einen Inhalt hat, soll der der Canvas wieder unlocked und gepostet werden:
Jetzt sind wir soweit mit der GameLoopThread Klasse fertig und müssen nun nurnoch Änderungen in unserer GameView Klasse vornehmen, damit unser Bild sich bewegt.
Dazu müssen wir zuersteinmal eine globale Variable und einen GameLoopThread in unserer GameView Klasse anlegen.
private int y=0;
private GameLoopThread theGameLoopThread;
Und eine Instanz unseres GameLoopThreads im Konstruktor nach dem Aufruf von super(); anlegen und ihr this, also diesen GameView übergeben:
theGameLoopThread = new GameLoopThread(this);
Im Konstruktor müssen wir noch unsere Methoden surfaceCreated() und surfaceChanged noch erweitern:
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
theGameLoopThread.setRunning(false);
while(retry){
try {
theGameLoopThread.join();
retry=false;
}catch(InterruptedException e){
}
}
}
public void surfaceCreated(SurfaceHolder holder) {
theGameLoopThread.setRunning(true);
theGameLoopThread.start();
}
Wenn die Oberfläche zerstört wird, soll running unserer Instanz des GameLoopThreads auf false gesetzt werden und es soll versucht werden, den Thread anzuhalten, bis es funktioniert hat.
Wenn die Oberflächer erzeugt wird, soll running unserer Instanz des GameLoopThreads auf true gesetzt und der Thread gestartet werden.
Außerdem müssen wir unsere Methode onDraw() editieren, sodass sie folgendermaßen aussieht:
@Override
protected void onDraw(Canvas canvas) {
canvas.drawColor(Color.DKGRAY);
if(y <= getHeight() - bmp.getHeight()){
y=y+3;
}
canvas.drawBitmap(bmp, 25, y, null);
}
Hier noch die gesamte GameLoopThread Klasse
package com.panjutorials.lazypudding;
import android.annotation.SuppressLint;
import android.graphics.Canvas;
public class GameLoopThread extends Thread {
private GameView theView;
private boolean isRunning = false;
public GameLoopThread(GameView theView) {
this.theView = theView;
}
public void setRunning(boolean run) {
isRunning = run;
}
// @SuppressLint("WrongCall") hinzugefügt am 20.02.14
@SuppressLint("WrongCall") @Override
public void run() {
while (isRunning) {
Canvas theCanvas = null;
try {
theCanvas = theView.getHolder().lockCanvas();
synchronized (theView.getHolder()) {
theView.onDraw(theCanvas);
}
} finally {
if (theCanvas != null) {
theView.getHolder().unlockCanvasAndPost(theCanvas);
}
}
}
}
}