[Paddles]: Das Kollisionssystem im Detail.

Hallo Leute,

es ist nun gute drei Wochen her, seit ich den Source von Paddles freigegeben habe.
Ich war mir durchaus bewusst, dass der Source weder kommentiert ist, noch von seiner Form jemals geeignet war, überhaupt eigentlich an die Öffentlichkeit zu kommen.

Jedenfalls, durch die Diskussion in meinem letzten Tutorial zu BlitzDelta wurde durch den User Barsack der Wunsch groß, das System etwas besser zu verstehen bzw. allgemein zu Kollision was zu haben. Da ich allgemein zu Kollision noch selbst einige Informationen zusammensuchen muss (andernfalls könnte ich nur rudimentär etwas zu dem Thema nennen), kann dies noch etwas dauern, bis ich da mal was fertig habe.
Was ich allerdings bieten kann, ist eine nachfolgende Erklärung des Kollisionssystems aus Paddles.

Und damit würde ich sagen, fangen wir dann auch am besten an.

Vorneweg muss ich noch eine Sache anmerken: das System basiert weitgehend auf dem PaddleBattle Sample aus dem AppHub. Da dieses allerdings auf XNA basiert, war es für mich erst einmal nicht brauchbar. Aus diesem Grund wurde es von mir übernommen, etwas angepasst und auch leicht verändert an meine Bedürfnisse.
Aber nun zum eigentlichen Thema, der Erklärung.

Erst einmal der vollständige Sourcecode noch einmal zum Überblick (gegenüber der Originalausführung gekürzt, da Unwichtiges entfernt):

Das System selbst nutzt BoundingVolumes, in diesem Fall nur BoundingRectangles. Zudem ist das System nicht nur darauf ausgelegt, den Ball einfach nur abprallen zu lassen, sondern es simuliert gewissermaßen eine Wölbung des Paddles und gibt dem Ball eine andere Flugkurve, je nachdem, wo der Ball auf das Paddle auftrifft.

Es fängt an mit der Methodendeklaration. Da der Code aus der Player.cs stammt und der technisch interessantere ist (der Code für die Ballkollision ist einfach nur eine Prüfung, ob sich Spielfeld und Ball berühren bzw schneiden und dann erfolgt entsprechend des Kollisionsortes die passende Aktion), verlangt diese Methode erst einmal den Ball, der auf Kollision mit dem Paddle geprüft werden soll.

Nachdem dies getan ist, beginnt alles mit einer simplen Prüfung. Und zwar soll der nachfolgende Code ja nur ausgeführt werden, wenn der Ball aktuell mit dem Schläger kollidiert (andernfalls hätten wir komische Ergebnisse).
Dies wird in Zeile 2 durch die Contains()-Methode des Rectangles geprüft. Diese gibt ein ContainmentType-Enum zurück bzw. einen Wert daraus. Für unseren Fall genügt Partial weil wir ja zumindest haben wollen, dass der Ball etwas mit dem Schläger kollidiert. Da es prinzipiell möglich sein könnte, dass der Ball komplett im Schläger steckt (was logischerweise aber eher unmöglich ist), prüfe ich nicht nur, ob der gegebene Wert Partial lautet, sondern auf alles, was gleich oder mehr als Partial ist (was dann nur noch Full sein kann).
Sollte eine Kollision stattfinden (also Contains() gibt Partial zurück), so beginnt der eigentliche Kollisionscode.

In Zeile 3 berechnen wir erst einmal die Länge unseres Geschwindigkeitsvektors und speichern diesen für spätere Verwendung in einer Variablen vom Typ float.
Danach holen wir uns in Zeile 4 noch das Vorzeichen des Geschwindigkeitsvektors über MathHelper.Sign, allerdings nur von der X-Achse (Y-Achse wird ja anders geregelt von der Kollision her). Wenn wir diese beiden Werte haben, sind wir bestens gerüstet für die weiteren Berechnungen.

In Zeile 7-9 werden erst einmal weitere wichtige Werte erhalten bzw. berechnet. Dies wären einerseits die Zentren von Ball sowie Schläger, sowie der Offset, bestehend aus der Differenz von den Zentren von Ball und Paddle, geteilt durch die halbe Höhe des Kollisionsrechtecks des Schlägers selbst. Damit können wir bestimmen, wo der Ball auf den Schläger aufgetroffen ist, entweder oberhalb oder unterhalb des Zentrums.

Mit diesen Werten berechnen wir in Zeile 11 einen Winkel. Dazu nehme ich das Quadrat des eben errechneten Offsets, multipliziert mit 70°, die ich zuvor in Radianten umrechne. Zum Schluss multipliziere ich das Ganze dann noch mit dem Vorzeichen des Offsets (dieses fällt ja zunächst einmal durch das Quadrieren weg und muss somit erneut eingerechnet werden).

Durch diesen erhaltenen Winkel kann ich dann die neue Geschwindigkeit des Balles berechnen (und damit auch den Effekt einer gewölbten Oberfläche simulieren). Dazu nehme ich in Zeile 13 (der Befehl wurde zur besseren Lesbarkeit lediglich auf mehrere Zeilen umgebrochen) für die neue Geschwindigkeit auf der X-Achse den Kosinus des eben errechneten Winkels und versehe den noch mit dem Vorzeichen, das der Ball vorhin hatte (somit fliegt der Ball erst einmal unverändert weiter, abgesehen davon, dass er nun schneller/langsamer sein könnte als zuvor). Für die Y-Achse nehme ich den gleichen Winkel, nur errechne ich hier den Sinus dafür, nehme aber sonst keine Veränderung vor.
Der Ball hat somit seinen neuen Geschwindigkeitsvektor erhalten und fliegt nun im Vergleich zu vorher anders, als nun nach der Kollision.

Zeile 17-19 kümmern sich dann um die Umkehrung des Balls. Dazu wird geprüft, in welche Richtung der Ball fliegt. Ist das Vorzeichen des Vektors auf der X-Achse negativ, also kleiner 0, so fliegt er nach links, ansonsten nach rechts (folglich ist das Vorzeichen positiv). Außerdem vergleiche ich die X-Koordinate der Zentren von Ball und Schläger passend zur Geschwindigkeit. Fliegt der Ball nach links, muss folglich das Zentrum des Balles kleiner sein, als das Zentrum des Balles selbst. Für den Flug nach rechts gilt es entsprechend umgekehrt.
Sollte alles passen, so wird die Geschwindigkeit auf der X-Achse einfach umgekehrt, damit der Ball entsprechend die andere Richtung einschlägt.

In Zeile 21 skaliere ich den neuen Geschwindigkeitsvektor dann schließlich wieder auf seine ursprüngliche Länge zurück (falls der Ball schnell war, so ist er es auch weiterhin nach der Kollision, nur ist seine Flugbahn dann eben verändert).

Damit ist die Kollision schon vollständig abgeschlossen. Was nun noch folgt, ist die Kollisionsauflösung. Würde ich diese unterlassen, so könnte es durchaus vorkommen, dass der Ball (genügend Penetrationstiefe vorausgesetzt, d.h der Ball steckt tief genug im Schläger) nochmals eine Kollision auslöst, was dann zur Endlosschleife führt, da der Ball niemals aus dem Schläger austritt. Folglich würde der Ball für ewig im Schläger feststecken und das Spiel unmöglich geworden.
Um die Auflösung kümmern sich die Zeilen 23-26.
Zunächst wird wieder einmal unterschieden, in welche Richtung der Ball nun fliegt, nachdem wir die Kollision nach unseren Wünschen behandelt haben. Abhängig vom Ergebnis der Prüfung fällt dann auch unsere Vorgehensweise aus.

Fliegt der Ball nach links, so hatte er gerade eine Kollision mit dem rechten Schläger. Seine Position auf der X-Achse bestimmt sich nun aus der X-Koordinate der linken Seite des Schlägers, vermindert um die Breite des Basisrechtecks (in meinem Fall einfach die Breite der Grafik). Tatsächlich ist es so, dass ich für die Bestimmung der Position des Kollisionsrechteck eine gewisse Vorgehensweise habe, damit ich die Grafik z.b so gestalten kann, dass auch Teile enthalten sein könnten, die nicht zur Kollision zählen dürfen, so z.B. Schatten oder ähnliche Dinge. Aus diesem Grund wird hier das Basisrechteck herangezogen (die genaue Bestimmung der CollisionArea sowie der BaseCollisionArea kann man der Sprite.cs entnehmen, die ebenfalls soweit dem Basissample entnommen wurde.). Der Ball befindet sich nun außerhalb des Schlägers und kann ungehindert seine Reise fortsetzen, ohne erneut eine Kollision zu bewirken.
Für den Fall, dass der Ball nach links fliegt, ist die Vorgehensweise nicht viel anders. Hier nehme ich die rechte Seite des Kollisionsrechtecks des Schlägers und ziehe die X-Position des Basisrechtecks des Balls ab. Und schon ist auch hier die Kollision aufgelöst und alles in bester Ordnung.

 

Das war auch schon das Kollisionssystem an sich. Es ist eher simpel, dennoch ordentlich genug, um etwas Schwung in das Spiel reinbringen zu können.
Wer sich die Originaldatei angesehen hat aus meinem Source, der wird feststellen, dass da noch mehr Code drin ist. Es handelt sich hierbei um den Kollisionscode für den Fall, dass die Schläger nicht rechts bzw. links sind, sondern oben/unten (war ursprünglich dafür gedacht, dass ich ein 4-Spieler-Pong entwickeln wollte). Der Code ist weitgehend der gleiche, er wurde nur an die entsprechenden Gegebenheiten angepasst, so sind z.B X und Y vertauscht usw. Auf diesen Code werde ich nicht weiter eingehen, das Verständnis sollte normalerweise nun vorhanden sein, da sich beide Parts ja wie gesagt weitgehend gleichen.

Ich hoffe, ich habe alles verständlich genug erläutert und jedem damit Einblick gegeben, wie ich meine Kollisionen genau gehandhabt habe. Wer selbst ein Kollisionssystem brauch, kann meines ja als Basis nehmen und die nicht benötigten Parts dann entfernen. Das wichtigste ist im Grunde stets die Contains()-Methode und danach folgt der spezifische Code, den ihr für eure Kollisionsbehandlung und -auflösung braucht.
Für den Fall, dass ich mal einen Artikel über Kollision schreiben werde, wird diese Art der Kollision auch enthalten sein (nur vereinfacht sowie verallgemeinert), ebenso wie Per-Pixel-Kollision (falls gut mit Delta machbar, wie gesagt, ich muss selbst erst auf Informationssuche gehen) und andere Kollisionsmethoden, falls es noch welche gibt.

Ansonsten war es das mit diesem Artikel, ich hoffe, es hat euch gefallen.
Als kleinen Bonus dafür, dass ihr den ganzen Artikel gelesen habt, habe ich eine kleine Sache für euch. Und zwar werdet ihr das nächste Mal, bevor ich irgendwelche Informationen publik mache, sei es ein Artikel oder ein Screenshot oder eine andere Sache,  vor der offiziellen Veröffentlichung informiert und dürft damit als einer der Ersten erfahren, was da neues kommt. Was es genau ist, steht fest, sobald ich wieder etwas tue.
Nennt mir dazu einfach folgendes Codewort in eurem Kommentar und hinterlasst eine gültige Mailadresse im Kommentar (bitte nicht als Nachricht reinschreiben, sondern im entsprechenden Feld im Kommentarformular), damit ich euch auch erreichen kann.

Das hier ist das Codewort: PaddlesKollision

Seht es als einen Bonus dafür, dass ihr den ganzen Artikel aufmerksam gelesen habt.

 

Ansonsten sehen wir uns beim nächsten Artikel wieder. Bleibt dran!

 

~internetfreak

2 Gedanken zu „[Paddles]: Das Kollisionssystem im Detail.

  1. Barsack

    Sorry, kommt diesmal etwas später ^^

    Erstmal danke, dass du dir die Mühe gegeben hast, einen Artikel zu schreiben, nur weil ich des nicht verstehe.
    Zu dem Artikel, wieder toll erklärt. Fand besonders gut dass die Zeilenangaben im Text schwarz markiert wurden, dies half einen falls man etwas nicht versteht schnell den Text dazu zu finden. Jedoch könntest du die „CollisionArea“ genauer erklären können. Okay, die meisten verstehen des auch schon selber, könnte aber halt auch anders sein 😀

    Aber ansonsten nochmal danke, werde des demnächst in meinem Code verwenden,
    sollten Probleme auftauchen melde ich mich natürlich ^^

    MFG Barsack

    Antworten
    1. internetfreak Beitragsautor

      Die CollisionArea ist einfach das Rechteck, das zur Kollisionserkennung eingesetzt wird. Bei Paddles nutze ich das System, was auch das Sample aus dem AppHub verwendet. Bei diesem System wird einfach ein Rechteck aufgespannt, welches den Bereich innerhalb der Grafik abdeckt, der für die Kollision relevant ist. Dann wird es noch mit der Position verschoben.
      Ist etwas schwer zu erklären so, schau in die Sprite.cs im Paddles Source, falls du mehr wissen willst, ansonsten frag einfach nochmals nach.
      Im Allgemeinen sollte es reichen, wenn du dir vorstellst, dass CollisionArea = BoundingRectangle ist.

      Zum Artikel, es stimmt zwar, dass du es nich verstanden hast, aber ich dachte auch, das könnte für andere nützlich sein, immerhin ist das auch zusätzliches Wissen und dank des Source von Paddles kann man das auch nachvollziehen.

      Hoffe, es is soweit alles klar.

      Antworten

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.