Programmierung einer Spiele-App

Wie programmiert man eine Spiele-App in Java?

In diesem Beispiel zeige ich, wie eine Spiele-App für Android in Java programmiert wird. Zunächst erstellen wir ein Layout in der Datei activity_main.xml, bestehend aus zwei Schaltflächen -Buttons- und dem Spielfeld. Die Buttons sollen nebeneinander über dem Spiefeld erscheinen. Dazu werden sie in ein horizontales LinearLayout gepackt. Dieses LinearLayout packen wir zusammen mit der Spielfläche in ein übergeordnetes LinearLayout mit vertikaler Ausrichtung. Das Spielfeld nennen wir zeichenFlaeche.


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="8dp">

        <Button
            android:id="@+id/buttonLinks"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginEnd="4dp"
            android:layout_marginTop="20dp"
            android:text="New Object" />

        <Button
            android:id="@+id/buttonRechts"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:layout_marginStart="4dp"
            android:layout_marginTop="20dp"
            android:text="Button 2" />
    </LinearLayout>

    <de.siebenberge.beispiel.ZeichenAnsicht
        android:id="@+id/zeichenFlaeche"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>

Wir erstellen eine Klasse ZeichenAnsicht. Diese Klasse verwenden wir, um auf die Zeichefläche zu zeichnen. Hier stellen wir später unsere Spielobjekte dar. Momentan wird einfach nur eine blaue Kreisfläche in die Mitte gezeichnet. Die Klasse Zeichenansicht verwendet die Datei ZeichenAnsicht.java.


package de.siebenberge.beispiel;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class ZeichenAnsicht extends View {

    private Paint pinsel;

    // Konstruktoren, die Android Studio benötigt, um die View im Layout anzuzeigen
    public ZeichenAnsicht(Context context) {
        super(context);
        init();
    }

    public ZeichenAnsicht(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        pinsel = new Paint();
        pinsel.setColor(Color.BLUE); // Beispielfarbe für das Zeichnen
        pinsel.setAntiAlias(true);
        pinsel.setStrokeWidth(5f);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // Hier wird gezeichnet!
        // Hintergrundfarbe der Zeichenfläche:
        canvas.drawColor(Color.LTGRAY);

        // Beispiel: Ein Kreis in der Mitte der Fläche zeichnen
        float x = getWidth() / 2f;
        float y = getHeight() / 2f;
        float radius = 100f;

        canvas.drawCircle(x, y, radius, pinsel);
    }
}
  

Nun kommen wir noch zur MainActivity. Wir definieren meineZeichenflaeche vom Typ ZeichenAnsicht und die Schaltflächen btnLinks und btnRechts vom Typ Button. In onCreate weisen wir dem ContentView unsere activity-main zu. Dann folgen die Zuweisungen für meineZeichenflaeche und die Schaltflächen. Sonst passiert zunächst noch nichts weiter. Dies werden wir in Beispiel 2 ändern, welches demnächst folgt.


package de.siebenberge.beispiel;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private ZeichenAnsicht meineZeichenFlaeche;
    private Button btnLinks, btnRechts;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        meineZeichenFlaeche = findViewById(R.id.zeichenFlaeche);
        btnLinks = findViewById(R.id.buttonLinks);
        btnRechts = findViewById(R.id.buttonRechts);
        
    }
}
Beispiel1
So sieht der Bildschirm nun aus.
Oben die zwei Schaltflächen, darunter die Zeichefläche mit einem blauen Punkt.
Im nächsten Beispiel kommt dann Bewegung ins Spiel!

Jetzt kommt Bewegung ins Spiel

Wir haben nun gesehen, wie wir auf die zeichenFläche zeichnen können. Sobald die View ZeichenAnsicht das erste Mal auf dem Bildschirm angezeigt wird, löst Android das onDraw-Ereignis aus und übergibt dabei ein Canvas, also eine Leinwand. Auf diese Leinwand können wir nun zeichen. Im ersten Beispiel haben wir zunächst den Hintergrund komplett in hellgrau gestrichen (canvas.drawColor(Color.LTGRAY);). Dann haben wir die Mitte der Leinwand ermittelt und dort einen Kreis mit Radius 100 Pixel gezeichnet. Zum Zeichnen haben wir einen Pinsel verwendet, der in init() definiert wurde. Um nun Bewegung ins Spiel zu bringen, müssen wir nur die Werte für x und y regelmäßig verändern und dafür sorgen, dass onDraw auch regelmäßig ausgelöst wird.

Da wir später mehrere Objekte im Spiel bewegen wollen, definieren wir erst mal eine neue Klasse Objekt:.


package de.siebenberge.beispiel;

import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
import android.util.Log;

public class Objekt {
    public static int id = 0;   //Diese Variable wird später inkrementiert und
    //und jedem neuen Objekt als myId zugewiesen
    private int groesse = 20;
    private PointF position = new PointF(1f, 1f); //aktuelle Position des Objekts
    private Point speed = new Point(1, 1); // und Geschwindigkeit in Pixel/Sekunde
    private Objekt nextObject = null;           // Zeiger auf weiteres Objekt
    //So können wir mehrere Objekte nacheinander
    //abarbeiten
    private int myId;
    private int col = Color.RED;           //Farbe des Objekts

    // Zum Erzeugen eines neuen Objektes, wird Startosition, Geschwindigkeit, Groesse und Farbe
    //gleich mitgegeben
    public Objekt(PointF pos, Point speed, int groesse, int col) {
        this.position = pos;
        this.speed = speed;
        this.groesse = groesse;
        this.myId = id++;
        this.col = col;
    }

    public int getCol() {
        return col;
    }

    public void setNextObject(Objekt nextObject) {
        this.nextObject = nextObject;
    }

    public int getMyId() {
        return this.myId;
    }

    public Objekt getNextObject() {
        return this.nextObject;
    }

    public void setSpeed(Point speed) {
        this.speed = speed;
    }


    public Point getSpeed() {
        return this.speed;
    }

    public void setPosition(PointF pos) {
        this.position = pos;
    }

    public PointF getPosition() {
        return this.position;
    }

    public void update(float deltaTime) {
        // Neue Position berechnen: Position = Position + (Geschwindigkeit * Zeit)
        // Da Point nur int nimmt, runden wir das Ergebnis
        this.position.x += this.speed.x * deltaTime;
        this.position.y += this.speed.y * deltaTime;
        //Log.d("Beispiel", "MyId = " + this.myId);

        // Wichtig: Wenn es ein nächstes Objekt gibt, das auch updaten!
        if (this.nextObject != null) {
            this.nextObject.update(deltaTime);
        }
    }


    // Eine Hilfsmethode, um alle Objekte auf das Canvas zu zeichnen
    public void draw(android.graphics.Canvas canvas, android.graphics.Paint pinsel) {
        // Kreis an der aktuellen Position zeichnen
        pinsel.setColor(this.getCol());
        canvas.drawCircle(this.position.x, this.position.y, this.groesse * 1f, pinsel);

        // Nächstes Objekt zeichnen
        if (this.nextObject != null) {

            this.nextObject.draw(canvas, pinsel);
        }
    }
}

Jetzt haben wir die Möglichkeit, mehrere Objekte mit unterschiedlichen Eigenschaften zu erzeugen. Mit jedem Klick auf den linken Button soll ein weiteres Objekt erzeugt werden. Dazu erweitern wir MaunActivity.java:


package de.siebenberge.beispiel;

import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {

    private ZeichenAnsicht meineZeichenFlaeche;
    private Button btnLinks, btnRechts;
    private Objekt firstObject, aktObject, lastObject;
    private float posX, posY;
    private boolean isRunning = false;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_rettung);

        meineZeichenFlaeche = findViewById(R.id.zeichenFlaeche);

        btnLinks = findViewById(R.id.buttonLinks);
        btnRechts = findViewById(R.id.buttonRechts);
        btnLinks.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                posX = meineZeichenFlaeche.getWidth() / 2f;
                posY = meineZeichenFlaeche.getHeight() / 2f;
                float x, y;
                int speedX, speedY, groesse, col;
                speedX = Zufall.getZufall(-200, 200);
                speedY = Zufall.getZufall(-200, 200);
                groesse = Zufall.getZufall(20, 200);
                col = Zufall.getRandomColor(0, 240);

                x = Zufall.getZufall(posX * .9f, posX * 1.1f);
                y = Zufall.getZufall(posY * .9f, posY * 1.1f);
                if (firstObject == null) {
                    firstObject = new Objekt(new PointF(x, y), new Point(speedX, speedY), 100, col);
                    aktObject = firstObject;
                    lastObject = firstObject;
                    if (!isRunning) {
                        meineZeichenFlaeche.starteThread(firstObject);
                        isRunning = true;
                    }
                } else {
                    aktObject = lastObject;
                    aktObject.setNextObject(new Objekt(new PointF(x, y), new Point(speedX, speedY), groesse, col));
                    lastObject = aktObject.getNextObject();
                }
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();

    }

    @Override
    protected void onPause() {
        super.onPause();
        // Thread stoppen, um Akku zu sparen, wenn die App im Hintergrund ist
        meineZeichenFlaeche.stoppeThread();
    }
}

Beim ersten Klick wird ein Thread gestartet, der alle 20ms die gesamte Objektkette abarbeitet und die einzelnen Objekte darstellt. Um unterschiedliche Objekte zu erzeugen, wird die Klasse Zufall verwendet, der Minimum und Maximum mit übergeben werden. So werden die Objekte in der Nähe vom Mittelpunkt erzeugt und unterscheiden sich in Größe, Farbe und Geschwindigkeit. Hier noch der Code von Zufall:


package de.siebenberge.beispiel;

import android.graphics.Color;

import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

public class Zufall {

    public static int getZufall(int min, int max) {
        return ThreadLocalRandom.current().nextInt(min, max + 1);
    }

    public static float getZufall(float min, float max) {
        return min + ThreadLocalRandom.current().nextFloat() * (max - min);
    }

    public static int getRandomColor(int min, int max){
        int r = ThreadLocalRandom.current().nextInt(min, max+1);
        int g = ThreadLocalRandom.current().nextInt(min, max+1);
        int b = ThreadLocalRandom.current().nextInt(min, max+1);
        return android.graphics.Color.rgb(r, g, b);
    }
}

Damit nun endlich Bewegung ins Spiel kommt, hier die geänderte Klasse ZeichenAnsicht. Die Klasse implementiert nun das Interface Runnable. Dadurch können wir den Thread starten, der zyklisch die Objektkette durchläuft und die Objekte an ihre aktuelle Position zeichnet. Die Spielschleife läuft, solange der Thread lebt. Zunächst wird die Zeit berechnet, die seit dem letzten Durchgang vergangen ist. Mit dieser Zeitspanne wird nun in erstesObjekt.update() die Position jedes Objekts neu berechnet. (Siehe oben in der Klasse Objekt). Dann wird durch postInvalidate() der Aufruf von onDraw ausgelöst. Hier wird nun nicht mehr direkt gezeichnet, sondern die Zeichenroutine der Klasse Objekt aufgerufen. So werden alle Objekte an ihrer neuen Position gezeichnet. Fertig ist die Bewegung. Dann pausiert der Thread für 20ms, bevor es oben mit der Schleife wieder von vorn anfängt.


package de.siebenberge.beispiel;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class ZeichenAnsicht extends View implements Runnable {

    private Paint pinsel;
    private Objekt erstesObjekt;
    private Thread spielThread;
    private boolean laeuft = false;
    private long letzteZeit;


    public ZeichenAnsicht(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PointF getSize() {
        return new PointF(getWidth() * 1f, getHeight() * 1f);
    }

    private void init() {
        pinsel = new Paint();
        pinsel.setColor(Color.RED); // Beispielfarbe für das Zeichnen
        pinsel.setAntiAlias(true);
        pinsel.setStyle(Paint.Style.FILL);
        pinsel.setStrokeWidth(5f);
    }

    // Startet den Thread (wird  von der MainActivity aufgerufen)
    public void starteThread(Objekt erstesObjekt) {
        this.erstesObjekt = erstesObjekt;

        if (!laeuft) {
            laeuft = true;
            letzteZeit = System.currentTimeMillis();
            spielThread = new Thread(this);
            spielThread.start();
        }
    }

    // Stoppt den Thread, wenn die App pausiert wird
    public void stoppeThread() {
        laeuft = false;
        try {
            if (spielThread != null) spielThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (laeuft) {

            // 1. Zeitdifferenz seit dem letzten Durchlauf berechnen (in Sekunden)
            long jetzt = System.currentTimeMillis();
            float deltaTime = (jetzt - letzteZeit) / 1000f;
            letzteZeit = jetzt;

            if (erstesObjekt != null) {
                erstesObjekt.update(deltaTime);
            }
            postInvalidate();

            try {
                Thread.sleep(20); // 20 Millisekunden = ca. 50 FPS
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawColor(Color.LTGRAY);
        if (erstesObjekt != null) {
            erstesObjekt.draw(canvas, pinsel);
            // in der Klasse Objekt, wird dann die gesamte Objektkette abgearbeitet
        }
    }
}




Beispiel2
So sieht der Bildschirm nun aus.
Mehrere unterschiedliche Objekt fliegen über den Bildschirm.
Daraus lässt sich doch wohl ein Spiel entwickeln.
Dazu demnächst mehr auf SoftwareAusDemSiebengebirge!

Objekte automatisch erzeugen, unsichtbare Objekte löschen

Statt immer nur ein Objekt zu erzeugen, wenn auf den Start-Button gedrückt wird, sollen die Objekte nun automatisch erzeugt werden. Sobald das Objekt den Bildschirm verlassen hat und nicht mehr sichtbar ist, wird es gelöscht. Dies spart Rechenzeit und Speicher. Wir erzeugen einen Handler mit Namen timerHandler als Taktgeber. Dieser Handler ruft nach Start und nach Ablauf der in der Variablen zyklenTimer abgelegten Zeit den objectSpawner auf. Im objectSpawner wird ein neues Objekt erzeugt und an die Kette der Objekte angehängt. Im objectSpawner wird auch die Zeit des zyklenTimer verringert, um immer schneller neue Objekte zu erzeugen. Nachdem 300 Objekte erzeugt wurden, wird die Schleife im objectSpawner verlassen und es werden keine weiteren Objekte mehr erzeugt. Ist schon erstaunlich, wieviele Objekte sich in flüssiger Bewegung darstellen lassen.



    private int zyklenTimer, anzahlDurchlaeufe ;

    private float density;
	
	....
	
	protected void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_rettung);

        meineZeichenFlaeche = findViewById(R.id.zeichenFlaeche);
		density = getResources().getDisplayMetrics().density;  // Neu, Bildschirmauflösung beachten
	
	...
	
			zyklenTimer = 600;
			anzahlDurchlaeufe = 0;
        }
	
	    @Override
		protected void onResume() {
			super.onResume();
			timerHandler.postDelayed(objectSpawner, zyklenTimer);  // Handler starten
		}
		
	    private Handler timerHandler = new Handler(Looper.getMainLooper());
		private Runnable objectSpawner = new Runnable() {
        
		@Override
        public void run() {
            if(anzahlDurchlaeufe>= 300)
            {
                return;
            }
            anzahlDurchlaeufe++;
            posX = meineZeichenFlaeche.getWidth() / 2f;
            posY = meineZeichenFlaeche.getHeight() / 2f;
            float x, y;
            int speedX, speedY, groesse, col;
            speedX = Zufall.getZufall((int)(-75*density), (int)(75*density));
            speedY = Zufall.getZufall((int)(-75*density), (int)(75*density));
            groesse = Zufall.getZufall((int)(20*density), (int)(75*density));
            col = Zufall.getRandomColor(0, 240);

            x = Zufall.getZufall(posX * .9f, posX * 1.1f);
            y = Zufall.getZufall(posY * .9f, posY * 1.1f);
            if(meineZeichenFlaeche.newObject == null){
                meineZeichenFlaeche.newObject = new Objekt(new PointF(x, y), new Point(speedX, speedY), groesse, col);
                if(!isRunning){
                    meineZeichenFlaeche.starteThread(null);
                    isRunning = true;
                }
            }
            if(zyklenTimer>50){
                zyklenTimer-=5;
                if(zyklenTimer<50){
                    zyklenTimer = 50;
                }
            }
            timerHandler.postDelayed(this,zyklenTimer);
        }
    };	
	

Hier die Änderungen in der Klasse Zeichenansicht. Neu ist die Variable newObject. In dieser Variablen erfolgt die Übergabe eines neuen Objektes an die Zeichenansicht. Die Zeichenansicht fügt dieses Objekt in die Kette der Objekte ein und setzt newObject wieder auf null. Somit weiß die MainActivity, dass sie ein neues Objekt erzeugen darf. Nachdem für alle Objekte die Position aktualisiert wurde, wird aufraumen() aufgerufen. Objekte, deren Position außerhalb des Bildschirms liegen, werden aus der Kette genommen und können so vom Garbage-Collector des Android-Betriebssystems gelöscht werden.


public class ZeichenAnsicht extends View implements Runnable {

    private Paint pinsel;
    private Objekt erstesObjekt;
    private Thread spielThread;
    private boolean laeuft = false;
    private long letzteZeit;

    public  Objekt newObject = null;
	...
	
	
    @Override
    public void run() {
        while (laeuft) {

            // 1. Zeitdifferenz seit dem letzten Durchlauf berechnen (in Sekunden)
            long jetzt = System.currentTimeMillis();
            float deltaTime = (jetzt - letzteZeit) / 1000f;
            letzteZeit = jetzt;
			
            synchronized (this) {
                if (newObject != null) {
                    if (erstesObjekt == null) {
                        erstesObjekt = newObject;
                        newObject = null;
                    } else {
                        Objekt aktuell = erstesObjekt;
                        while (aktuell.getNextObject() != null) {
                            aktuell = aktuell.getNextObject();
                        }
                        aktuell.setNextObject(newObject);
                        newObject = null;
                    }
                }

                if (erstesObjekt != null) {
                    erstesObjekt.update(deltaTime);
                }
                aufraeumen();
            }
            postInvalidate();

            try {
                Thread.sleep(20); // 20 Millisekunden = ca. 50 FPS
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
	
	
	    private void aufraeumen(){
        if (erstesObjekt == null) return; // keine Objekte vorhanden

        int breite = getWidth();
        int hoehe = getHeight();

        while (erstesObjekt != null && erstesObjekt.istAusserhalb(breite,hoehe)){

            erstesObjekt = erstesObjekt.getNextObject(); //aktuelles Objekt ist ausserhalb des
                                                            // Bildschirms und wird gelöscht
                                                            // daher wird das nächste Objekt
                                                            // zum erstenObjekt
        }
        if (erstesObjekt != null){
            Objekt aktuellesObjekt = erstesObjekt;
            while (aktuellesObjekt.getNextObject() != null){
                Objekt naechstesObjekt = aktuellesObjekt.getNextObject();
                if(naechstesObjekt.istAusserhalb(breite,hoehe)){
                     aktuellesObjekt.setNextObject(naechstesObjekt.getNextObject());

                 }else {
                    aktuellesObjekt = aktuellesObjekt.getNextObject();
                }
            }
        }
    }
	

Hier noch die Methode, die ermittelt, ob das Objekt außerhalb der ZeichenAnsicht liegt:



public class Objekt {
...

    public boolean istAusserhalb(int breite, int hoehe) {
        // Wenn das Zentrum weiter als 'groesse' (Radius) im Aus ist, sieht man nichts mehr
        return (this.position.x < -this.groesse ||
                this.position.x > breite + this.groesse ||
                this.position.y < -this.groesse ||
                this.position.y > hoehe + this.groesse);
    }


Touch me! Objekte berühren und beeinflussen

Wie jede View, so besitzt auch die ZeichenAnsicht ein touchEvent-Ereignis. Um auf Berührungsereignisse zu reagieren, fangen wir das Ereignis ab und werten es aus. Wenn wir auf das Ereignis reagieren, beenden wir die Funktion mit return true;, andernfalls mit super.onTouchEvent(event); und Android weiß, das Ereignis interessierte uns nicht. Wir fügen in die Klasse onTouchEvent(MotionEvent event) ein.

Zunächst fragen wir ab, ob es sich um ein ACTION_DOWN-Event handelt. Wenn ja bearbeiten wir es, wenn nein soll sich Android drum kümmern. Die Routine ist selbsterklärend, pruefeTreffer ist neu hinzugekommen.


			
    @Override
    public boolean onTouchEvent(MotionEvent event){
        if(event.getAction() == MotionEvent.ACTION_DOWN){
            float x = event.getX();
            float y = event.getY();
            pruefeTreffer(x,y);
            return true;
        }
        return super.onTouchEvent(event);
    }			
			
			

Hier noch pruefeTreffer(float klickX, float klickY) Die Routine ruft für alle Objekte die Methode .pruefeTreffer(x,y) auf. Diese Methode kennt die eigene Position und bekommt die Position des TouchEvent übergeben. Wenn das Objekt berührt wird, liefert die Methode true, sonst false. Wenn eine Berührung stattgefunden hat, wird das getroffene Objekt in getroffenesObjekt kopiert, wenn die Position durch mehrere Objekte belegt ist, ist am Ende der Schleife nur das letzte Objekt in getroffenesObjekt abgelegt. Dies wird auf der View auch als oberstes Objekt angezeigt und daher erwartet man auch, dass dieses Objekt reagiert. In unserem Beispiel, soll das Objekt bei Berührung verschwinden, wir entfernen es also aus der Kette.
Die Berührung kann zu einem völlig beliebigen Zeitpunkt erfolgen. Es muss sichergestellt sein, dass wir kein Objekt aus der Kette entfernen, das gerade in der Schleife in run() bearbeitet wird. Durch synchronized(this) (hier und in run()) wird dies verhindert.


    private void pruefeTreffer(float klickX, float klickY){
        synchronized (this){
            Objekt aktuellesObjekt = erstesObjekt;
            Objekt getroffenesObjekt = null;

            while(aktuellesObjekt != null){
                if(aktuellesObjekt.pruefeTreffer(klickX,klickY)){
                    getroffenesObjekt = aktuellesObjekt;
                }

                aktuellesObjekt = aktuellesObjekt.getNextObject();
            }
            if(getroffenesObjekt!=null){
                entferneAusKette(getroffenesObjekt);

            }
        }
    }
			

entferneAusKette sieht wie folgt aus. Das Objekt wird einfach aus der Kette herausgenommen, um den Rest kümmert sich der GarbageCollector.

		private  void entferneAusKette(Objekt objectToDestroy){
        if (objectToDestroy == erstesObjekt){
            erstesObjekt=erstesObjekt.getNextObject();
        }else{
            Objekt vorgaengerObjekt = erstesObjekt;
            while (vorgaengerObjekt!= null && vorgaengerObjekt.getNextObject() != objectToDestroy){
                vorgaengerObjekt=vorgaengerObjekt.getNextObject();
            }
            if(vorgaengerObjekt!= null){
                vorgaengerObjekt.setNextObject(objectToDestroy.getNextObject());
            }
        }
    }
			

Die Methode pruefeTreffer wird der Objekt-Klasse zugefügt.

		public boolean pruefeTreffer(float touchX, float touchY){
        float dx = touchX - this.position.x;
        float dy = touchY - this.position.y;
        float abstand = (float) Math.sqrt(dx * dx + dy * dy);

        return abstand <= this.groesse;


    }