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);
}
}
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
}
}
}
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;
}