Kollisions- und Abprallverhalten BreakoutKlon

Fu3L

Top Contributor
Hallo,

Ich bin gerade dabei meinem Breakoutklon den letzten Schliff zu geben und stehe nun vor dem Problem meine etwas merkwürdige Kollisionserkennung zu optimieren^^
Das Problem: Wie erkenne ich, in welche Richtung der Ball abgelenkt werden muss? Wer eine geniale Lösung so herauszaubern kann, kann sich die Erklärung was ich bisher mache, bitte sparen (verwirrt nur^^ :D)

Meine bisherige Lösung sieht so aus, dass ich alle Blöcke durchlaufe und bei dem ersten, bei dem ein "intersects" mit dem Ball true liefert, erst teste, ob beide Ecken des Quadrats, das den Ball umgibt, in einem Block liegen. Dafür habe ich dann 4 verschiedene Abfragen, die 4 verschiedene Abprallverhalten für die 4 Seiten eines Blocks umsetzen. Das Funktioniert erst einmal gut. (Es sei denn ein anderer, weniger gut passender Block wird zuerst geprüuft, siehe weiter unten).
Ist dies nicht der Fall, prüfe ich, ob die vorherige Position darauf schließen lässt, ob der Ball zB von links kommt und nicht von unten (für den Fall, dass nur die rechte, obere Ecke im Block liegt. Siehe Bild "erkennung.png").. Das funktioniert leider nicht, wenn der Ball sehr genau die Ecke trifft, dann habe ich das so gelöst, dass der Ball seine Bahn zurückfliegt, was bei einem Treffer auf eine Blockwand sehr mies wirkt (siehe Bild "faelle.png" rechte Seite)^^ Außerdem wird auf meine Art auch oft ein Block weggemacht, der praktisch gar nicht erreichbar ist, aber bei dem eben zuerst mit .insects() geprüft wird, ob eine Überschneidung vorliegt (siehe BIld "faelle.png" linke Seite)

Eine zweite Frage: Kann es sein, dass AudioClip.play() nicht immer gaaanz zuverlässig die Töne abspielt?
 

Anhänge

  • faelle.png
    faelle.png
    4,8 KB · Aufrufe: 50
  • erkennung.png
    erkennung.png
    4,7 KB · Aufrufe: 45

Marco13

Top Contributor
Schwer zu sagen, das ist glaubich eines dieser Dinge, wo man sich irgendwas überlegen und das dann implementieren kann, und immer mehr bastelt und Fälle unterscheidet, und wenn man über die 1000 Zeilen raus ist, mal nachschaut, wie die das vor 30 Jahren auf dem C64 gemacht haben, und dann zu weinen anfängt ;( :D Also, vielleicht gibt's da irgendeinen "Trick", wie man das ganz einfach machen kann... :bahnhof:

Aber mir würde sich erstmal die Frage stellen, WANN man auf Kollisionen prüft. Offenbar prüfst du, nachdem der Ball einen Schritt gemacht hat. Da würde ich erstmal denken, dass der Ball, wenn er z.B. schnell auf zwei Reihen zufliegt, die erste Reihe "überspringen" könnte, und nur mit der zweiten (eigentlich unerreichbaren) Reihe kollidiert... ???:L

Der Rest ist unter dem Vorbehalt, dass man nicht den oben angedeuteten "Trick" kennt, mit dem das auf magische Weise ganz einfach wird:

Eigentlich müßte man ausgehend von der aktuellen Position und Geschwindigkeit rausfinden, wo und wann die erste Kollision stattfinden würde (wenn der Ball sich mit der aktuellen Geschwindigkeit einen Schritt weiter bewegen würde). Wenn man den Ball da durch einen Punkt annähern würde, wäre das ja relativ einfach: Man prüft das Liniensegment zwischen Position und nächster Position auf Überschneidungen mit irgendeiner Block-Seite. Das funktioniert bei einem Ball (d.h. bei einem Kreis) aber nicht so direkt. Vielleicht könnte man da auch tricksen, indem man nicht nur die "Bewegungslinie des Mittelpunktes" auf Kollisionen prüft, sondern auch zwei parallele (jeweils um +/- 'radius' nach links und rechts verschobene) Linien. Der Bereich, in dem der Kreis mit einem Rechteck kollidieren kann, ist eigentlich ein "abgerundetes Rechteck" - das eben die Umgebung des Rechtecks beschreibt, die höchstens 'radius' von irgendeinem Punkt des Rechtecks entfernt ist. Ein Teilaspekt eines allgemeinen Ansatzes könnte da ja eine Methode sein, die einem zu einem Kreis und einen Rechteck die beiden Punkte dieser Objekte liefert, die den geringsten Abstand haben (irgendwas wie: Das Lot vom Kresimittelpunkt auf alle Seitenlinien fällen und so...Für solche Distanz-Sachen kann man oft bei Geometric Tools: Distance hilfreiche Methoden klauen (die sich recht leicht von C++ nach Java übersetzen lassen)).

Aber das sind alles nur grobe Ideen... ohne eine Websuche nach sowas wie "Simple and efficient breakout collision detection" gemacht zu haben :D
 

Fu3L

Top Contributor
Aber mir würde sich erstmal die Frage stellen, WANN man auf Kollisionen prüft. Offenbar prüfst du, nachdem der Ball einen Schritt gemacht hat. Da würde ich erstmal denken, dass der Ball, wenn er z.B. schnell auf zwei Reihen zufliegt, die erste Reihe "überspringen" könnte, und nur mit der zweiten (eigentlich unerreichbaren) Reihe kollidiert... ???:L

Das könnte theoretisch sein.. ICh hatte das mal nachgerechnet und für unwahrscheinlich gehalten, aber bin mir nicht mehr im Klaren darüber, wie ich auf das Ergebnis kam.. Also es könnte wirklich leicht passieren (würde auch etwas seltsames Verhalten erklären, dass ich beobachtet habe, bei höheren Ballgeschwindigkeiten^^ :D)

Schwer zu sagen, das ist glaubich eines dieser Dinge, wo man sich irgendwas überlegen und das dann implementieren kann, und immer mehr bastelt und Fälle unterscheidet, und wenn man über die 1000 Zeilen raus ist, mal nachschaut, wie die das vor 30 Jahren auf dem C64 gemacht haben, und dann zu weinen anfängt Also, vielleicht gibt's da irgendeinen "Trick", wie man das ganz einfach machen kann...

Das beruhigt mich wenigstens so weit, dass ich nicht glaube: "Das muss doch einfach gehen, es gibt immer eine super geschickte Lösung"^^ (Die Erfahrung habe ich bisher immer gemacht, dass ichs im Nachhinein einfacher hätte haben können bei Sachen, die mir kompliziert erschienen^^)

Die Flugbahn und die Seiten der Blöcke als Geraden zu betrachten, kam mir natürlich auch schon in den Sinn, aber ich hielt es für übertrieben kompliziert^^ Allerdings scheints ja im Endeffekt eh auf irgendwas kompliziertes hinauszulaufen, also werd ichs morgen mal in Betracht ziehen^^

Edit: "Simple and efficient breakout collision detection" fördert gar nicht so schlechte Ergebnisse zu Tage :D Aber die ersten Links im Prinzip nichts, was du nicht schon genannt hättest ;)
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Der erste Link führt auf diesen Beitrag :lol:


EDIT: OK, hab auch mal kurz gesucht: Wenn man noch "rectangle" und "circle" mit einbezieht findet man schon einiges, schwierig wird das ganze ja aber nur, weil es ein "moving circle" ist (und dazu findet man dann doch nicht so schnell irgendeinen offensichtlichen "Trick"). Auf GeometricTools (oben schon verlinkt) gibt es zwar http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrBox2Circle2.cpp aber selbst dann muss man sich noch was für die Kollisionsantwort überlegen.
 
Zuletzt bearbeitet:

Fu3L

Top Contributor
So, ich habe mich bist gestern davor gedrückt, die Kollisionserkennung einzubauen und sitze seit gestern dran, aber es tanzt mir auf der Nase herum ;(

Ich lade mal das ganze Projekt hoch, damit ihr, wenn ihr es euch ansehen mögt, rumtesten könnt.
Ich hoffe meine Gedanken und mein Code sind nicht zu wirr und bitte nicht schimpfen, wenn etwas umständlich wirkt in meinem Code, ich finde einige Sachen selbst nicht mehr gut^^ :D (Kritik ist natürlich dennoch erwünscht, wenn ihr zufällig auf was stoßt, was nich zum eigentlich Problem gehört). Zum Testen einfach
Code:
test
in das Textfeld einfügen und "PlaySelected" klicken.
Die fraglichen Stellen sind "doBallLogic()" und "calcTarget()" in der Klasse engine.BlockStructur.

Der Gedanke bei der Kollisionserkennung, wie ich sie bauen wollte, ist folgender:
Der Ball befindet sich in einer Zelle eines Gitters in dem die Blöcke angeordnet sind. Nun prüfe ich, ob er in der Zeit
Code:
delta
, die seit dem letzten Aufruf der Logikmethode vergangen ist, eine neue Zelle erreichen kann. Wenn nicht, wird der loop abgebrochen. Kann er es, wird der Ball in die nächste erreichbare Zelle bewegt. Es wird geprüft, ob dort ein Block vorhanden ist. Wenn ja wird alles was zur Kollision gehört ausgeführt. Danach wird von
Code:
delta
die benötigte "Zeit" für diese Bewegung abgezogen und der loop beginnt mit continue von vorne.
Bei der Berechnung der Zielzelle addiere ich anfangs noch die Höhe und/oder Breite des den Ball umgebenden Rechtecks, sofern die Geschwindikeit positiv ist.

Er zeigt dabei aber komisches Verhalten: Zum einen kann ich mir nich erklären, wie in der Methode calcTarget() das
Code:
throw new NullPointerException()
und auch der Teil davor
Code:
if(diagonal) {
erreicht werden und zum anderen ist das Kollisionsverhalten immer noch sehr merkwürdig, wenn der Ball von der Seite auf die Blöcke trifft ;( Vllt bin ich einfach schon Textblind geworden...

Downloadlink. KOnnte es hier nicht hochladen, weils zu groß ist^^
 
Zuletzt bearbeitet:

Quaxli

Top Contributor
Das Spiel läuft bei mir nicht los.

Wenn ich, wie beschrieben, "test" eingebe und "PlaySelected" klicke, kriege ich einen leeren Bildschirm angezeigt. Unten ist der Schläger, den ich auch bewegen kann, aber das war's dann schon.
Weder sehe ich Steine, noch kriege ich den Ball dazu loszufliegen. :bahnhof:

***edit 08:59

Nachdem ich in LevelPanel folgende Zeile geändert habe:

Java:
	protected volatile boolean fireCmd = true;

(war vorher false) läuft der Ball jetzt los, aber Blöcke sehe ich immer noch nicht....


*** noch'n edit :oops:

O.k. die Enter-Tast startet den Ball. :oops:
Hat vorhin irgendwie nicht reagiert....
Blöcke fehlen immer noch. Ich hab einen kleinen Level gebastelt, aber auch der wird nicht angezeigt.
 
Zuletzt bearbeitet:

Fu3L

Top Contributor
Danke schonmal, dass du dir die Mühe machen möchtest, dir das anzusehen :)

Mhh^^ Ich hätte vllt nicht an der Methode zum Laden rumbasteln solln.. Außerhalb von Eclipse als jar funktioniert es leider bei mir auch noch nicht mit dem Laden der Levels, obwohl ich das Laden eigentlich so umgesetzt habe, wie bei meinem Notizprogramm bei den dortigen xml files...

Ich muss jetzt los, kümmere mich da nachher drum ;)
 

Fu3L

Top Contributor
Vielen Dank :)

Ich habs unter dem gleichen Link von oben nochmal hochgeladen. Bei mir tuts jetzt auch nach dem Exportieren (irgendwie hab ich das Gefühl, das ist mit jeder Eclipse Version und mit jedem neuen Projekt anders^^ :noe:).

Die Level müssen nach dem Exportieren aber aus dem Level Ordner im Project noch in einen "lvl" Ordner neben dem .jar-File kopiert werden. Habe auch mal einen Ordner mit einem ausführbaren .jar beigelegt, wo mans sich auch ohne Eclipse mal ansehen könenn sollte.

PS: Wer zum Testen schon ein schönes Level zusammenbaut, bewahre es bitte auf. Ich will, wenns fertig ist, mal einen Aufurf zum Level-kreieren starten ;) Das Levelformat soll sich nicht mehr ändern. Es kommen nur wahrschienlich noch neue Blöcke und/oder Upgrades hinzu^^

Achja: Das Level
Code:
3
is übrigends das einzige Level, das ich bisher gebaut habe^^
 

Apo

Bekanntes Mitglied
Gerade getestet. Habe zwar den lvl Ordner hinkopiert, aber ein Level laden tut er scheinbar nicht und auch im Editor erstellte Levels habe ich nicht zu sehen bekommen.
Mache ich was falsch?
 

Fu3L

Top Contributor
Also in der zip-Datei gibt es den Ordner "StartTest", wenn du da das .jar startest, müsste es eigentlich direkt funktionieren, weil der lvl Ordner ja schon daneben liegt ;(

Wenn nicht, dann starte es mal über die .bat Datei, ich meine, dass ich die Ausgabe zum Pfad noch drinhab und sag mal was da als Pfad ausgegeben wird...

Edit: Ganz wichtig: auf PlaySelected klicken. Start ist für die "Kampagne" gedacht und beginnt bei dem (momentan nicht vorhandenen) Level
Code:
1
^^
 
Zuletzt bearbeitet:

Quaxli

Top Contributor
Ich seh' auch kein Blöcke.
Die Bat-Datei hat gar keinen Effekt und wenn ich auf das Jar klicke oder das Projekt in Eclipse starte, bekomme ich zwar ein Fenster, etc. aber ich sehe da nur Schläger und Ball
 

Fu3L

Top Contributor
Dann poste ich mal meine Methode zum Laden der Blöcke:
Edit: Seh grad schlechtes Exceptionhandling :oops: Aber es ist schon so gedacht, dass wenn das Level nicht existiert, dass dann trotzdem der Editor lädt^^
Level.loadLevel() :
Java:
public void loadLevel() {
		Scanner sc = null;
		try {
                        //FÜr die Klasse Fct siehe unten
			File f = new File(Fct.getCurrentPath(this) + "lvl/" + LVL_FILE + ".txt");
			sc = new Scanner(f);
		} catch(Exception ex) {
			//No such lvl exists:
			return;
		}
		String line = sc.nextLine();
                //Read the first line to get information like name and author of the level
		this.info = line.split("[;]");		
		bs.loadBlocks(sc);
		sc.close();
	} //End loadLevel()


Hier die Methoden, mit denen ich den Pfad ermittle (eigentlich nur mit der oberen). Die untere Funktioniert so bei meinem Notizprogramm wunderbar, aber die obere tut bei mir aufm PC auch.
(Klasse Fct):
Java:
public static String getCurrentPath(Object executor) {
		return executor.getClass().getProtectionDomain().getCodeSource().getLocation().toString().replace("file:/", "").replace("BlockGame.jar", "");
	}
	
	public static String computePathOuterJar(Object executor, String fileName) {
		return executor.getClass().getClassLoader().getResource(fileName).toString().replace("file:/", "").replace("/", "\\").replace("rsrc:"+fileName, fileName).replace("jar:", "");
	}

Solltet ihr so nichts an den Methoden sehen können, könntet ihr ja vllt in der Methode zum Laden ein paar Sys.outs einbauen um vllt zu sehen, ob der Pfad falsch geladen wird?

Zum Testen des eigentlichen Problems, könnet ihr auch in BlockStructur.loadBlocks(Scanner s) einfach was verändern und fast alles weglassen, sodass nur noch steht:

Java:
public void loadBlocks(Scanner sc) {
	loadTestBlockStructure();
	probs.determineProbabilities();
	initialized = true;
}

Sollte dann immer noch nichts angezeigt werden, wäre das irgendwie doof^^

Noch ne andere Idee: Habt ihr mal den Ball rumfliegen lassen? Vllt werden ja auch die Bilder nicht geladen^^ Wenns zu komischem Flugverhalten käme, wären die Blöcke wohl da^^

Edit: Zum Verändern, so dass die kurze Version auch aufgerufen wird, muss in loadLevel() natürlich der Grund für die Exception auskommentiert werden^^ :oops:
 
Zuletzt bearbeitet:

Fu3L

Top Contributor
Tut es denn bei keinem?^^ Bzw. könnte jemand die Ausgabe des Pfades einbauen und hier posten? Bei mir tut es ja (leider) ohne Probleme ???:L
 

Marco13

Top Contributor
Was genau die Frage ist, ist noch so klar. Ist es absicht, dass du nicht mit getClass().getResourceAsStream() arbeitest? Das mit den Pfaden sieht karmpfig (und Systemspezifisch bzgl. Win/Linux) aus...
 

Fu3L

Top Contributor
Die Wege etwas außerhalb von .jar Dateien zu laden, sind anscheinend so zahlreich wie alle Programme, die ich bisher geschrieben hab^^ :D

Aber dann habe ich immer noch das Problem beim Speichern, da
Code:
getClass().getResourceAsStream()
mir ja nur einen InputStream bringt... (Falls das Speichern überhaupt auch Probleme verursachte. Lade in den nächsten 5 Minuten mal eine neue Version unter obigem Link hoch zum Testen^^)

Edit: Nochmal die Methode zum Speichern:

Java:
	public boolean save(String name, String[] info) {
		try {
			File f = new File(Fct.getCurrentPath(this) + "/lvl/" + name + ".txt");
			PrintWriter fos = null;
			fos = new PrintWriter(new FileOutputStream(f), true);
			String line = "";
			for(int i = 0; i < info.length; i++) {
				line += info[i];
				line += ";";
			}
			fos.println(line);
			bs.save(fos);
			fos.close();
			return true;
		} catch(Exception ex) {
			return false;
		}	
	} //End save()
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Oha, sorry, ich dachte es ginge NUR ums Lesen. Speichern kann man in eine JAR-Datei natürlich nichts. Das ganze dient also dem Zweck, Dateien im "lvl"-Verzeichnis zu handlen, das in dem Verzeichnis liegt, wo sich auch die JAR befindet? Das müßte eigentlich gehen. Ggf. müßte man da mal ein Mini-Testbeispiel basteln, das NUR eine Datei schreibt und wieder liest....
 

Fu3L

Top Contributor
Oha, sorry, ich dachte es ginge NUR ums Lesen. Speichern kann man in eine JAR-Datei natürlich nichts. Das ganze dient also dem Zweck, Dateien im "lvl"-Verzeichnis zu handlen, das in dem Verzeichnis liegt, wo sich auch die JAR befindet? Das müßte eigentlich gehen. Ggf. müßte man da mal ein Mini-Testbeispiel basteln, das NUR eine Datei schreibt und wieder liest....

Eventuell lag es auch nur am Lesen, ich weiß ja nicht, ob die Level gespeichert wurden bei den anderen und dann nur nicht gelesen wurden... Bei mir funktioniert es ja auf beiden Wegen... Das was du gepostet hast, funktioniert bei folgendem Aufruf auch, wenn es außerhalb des .jar Files liegt:
Java:
sc = new Scanner(this.getClass().getResourceAsStream("../lvl/" + LVL_FILE + ".txt"));

Ich bastel mal eben was zum Test^^

Edit: Das kleine Beispiel funktioniert bei mir einwandfrei:

Java:
public class Test {
	public Test() {
		try {
			File f = new File(this.getCurrentPath() + "/lvl/test.txt");
			PrintWriter fos = null;
			fos = new PrintWriter(new FileOutputStream(f), true);
			String line = "Blabla";
			fos.println(line);
			fos.close();
		} catch(Exception ex) {
			ex.printStackTrace();
		}	
		Scanner sc = null;
		try {
			sc = new Scanner(this.getClass().getResourceAsStream("../lvl/test.txt"));
		} catch(Exception ex) {
			//No such lvl exists:
			ex.printStackTrace();
		}
		String line = sc.nextLine();
		System.out.println(line);
	}
	
	public String getCurrentPath() {
		return this.getClass().getProtectionDomain().getCodeSource().getLocation().toString().replace("file:/", "").replace("BlockGame.jar", "");
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new Test();
	}
}
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Ja, hab's mal schnell getestet. Das mit dem "getResourceAsStream" sollte man AFAIR nicht in Kombination mit ".." verwenden: Dieses implizite "hochwandern" in den Verzeichnissen funktioniert glaub' ich bei JARs nicht unbedingt (kann mich aber auch irren).

Wenn man das entsprechend anpasst zu
Java:
            // WEG sc = new Scanner(this.getClass().getResourceAsStream("../lvl/test.txt"));
            // HIN:
            File f = new File(this.getCurrentPath() + "/lvl/test.txt");
            sc = new Scanner(new FileInputStream(f));
scheint es sowohl aus der IDE (Eclipse, Windows) als auch als JAR an der Kommandozeile zu funktionieren - vorausgesetzt man erstellt in den Verzeichnissen, in denen er da sucht, jeweils das "lvl"-Unterverzeichnis!

Dieses Erstellen des Unterverzeichnisses kann man natürlich auch automatisch machen. Aber trotzdem ist mir noch nicht klar, was die Sache mit dem "getCurrentPath" dort soll. Wenn man keinen Speziellen Pfad angibt, wird doch sowieso der lokale verwendet...!?

Java:
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.util.Scanner;

public class Test {
    public Test() {
        try {
            File d = new File("lvl/");
            if (!d.exists())
            {
            	boolean created = d.mkdir();
            	if (!created)
            	{
            		System.out.println("Something wrong");
            		System.exit(-666);
            	}
            	else
            	{
            		System.out.println("Created dir "+d.getCanonicalPath());
            	}
            }
            File f = new File("lvl/test.txt");
            PrintWriter fos = null;
            fos = new PrintWriter(new FileOutputStream(f), true);
            String line = "Blabla";
            fos.println(line);
            fos.close();
        } catch(Exception ex) {
            ex.printStackTrace();
        }   
        Scanner sc = null;
        try {
            File f = new File("lvl/test.txt");
            sc = new Scanner(new FileInputStream(f));
            String line = sc.nextLine();
            System.out.println(line);
            
        } catch(Exception ex) {
            //No such lvl exists:
            ex.printStackTrace();
        }
    }
    /**
     * @param args
     */
    public static void main(String[] args) {
        new Test();
    }
}
 

Fu3L

Top Contributor
Aber trotzdem ist mir noch nicht klar, was die Sache mit dem "getCurrentPath" dort soll. Wenn man keinen Speziellen Pfad angibt, wird doch sowieso der lokale verwendet...!?

Ich dachte immer es sei von .jar File aus nicht möglich^^ Aber gut, so werde ichs nun umbauen^^ Danke ;)

Danach kann man sich wieder dem eigentlichen Problem zuwenden^^ :eek:

Edit: Habs jezz neu eingebaut udn auch die Kollisionserkennung wieder umgebaut in die gewünschte Form. Bitte testet, ob es nun bei euch alles klappt mit dem Speichern und seht, wie die Kollisionserkennung versagt^^

BlockGameNo1.zip
 
Zuletzt bearbeitet:

Marco13

Top Contributor
Bei "Edit Selected" versucht er offenbar zuerst, eine Datei mit leerem Namen zu öffnen ("lvl/.txt") aber ansonsten scheint's so weit zu klappen.

Die Kollisionserkennung ... eher nicht.... welche Bedeutung haben denn diese "Bunten" Blöcke? ???:L
 

Fu3L

Top Contributor
Jo, das mit der leeren Datei ist mehr oder weniger Absicht, werde das aber nochma überarbeiten^^ Aber dass es speichert und lädt ist schonma super ;)

Welche bunten Blöcke? Im Level
Code:
test
dürften nur weiße sein^^ Und ob ein "normaler" Block nun eine Farbe hat oder nicht, ist auch egal.. Dient nur dazu ansprechende Level zu kreieren ;)
Solltest du Level
Code:
3
meinen, die erst sichtbar werden, wenn man sie einmal getroffen hat: Die sind auch einfach erstmal unsichtbar und danach brauchten die halt noch ein schnell gemachtes sichtbares Aussehen :D

Das violette am Rand ist (sofern es bei dir auftaucht) übrigends die Begrenzung, damits auch bei anderen Seitenverhältnissen gleich spielbar bleibt. (Mich wurmt es bei den alten Varianten dieses Spiels, dass die auf 1024x768 ausgelegt sind und der Ball einfach immer am schwarzen Rand abprallt. Was ein wenig doof ist, da der Hintergrund auch schwarz ist, bei den Varianten, die ich kenn.
 
Zuletzt bearbeitet:

Ähnliche Java Themen


Oben