Game Design: PONG - simple Kollisionserkennung und KI

    Diese Seite verwendet Cookies. Durch die Nutzung unserer Seite erklären Sie sich damit einverstanden, dass wir Cookies setzen. Weitere Informationen

    • Game Design: PONG - simple Kollisionserkennung und KI

      Hallo zusammen,

      immer wieder werden Spiele veröffentlicht, die aufgrund der simplen und oftmals falsch angewendeten Kollisionserkennung des Game Makers die gleichen Fehler und Probleme aufweisen. So fliegen z.B. Geschosse durch Wände hindurch, oder die Spielfigur hängt plötzlich im Boden fest. Anhand des altbekannten und ebenso simplen Spiels "Pong" möchte ich euch zeigen, wie man das heikle Thema "Kollisionen" angehen sollte. Der positive Nebeneffekt des Ganzen ist, dass ihr hoffentlich einen Einblick in die grundlegende Planung von Spielen (Game Design) bekommt.


      1. Nicht einfach drauflos...

      Ich selbst bin alles andere als ein Profi, was Kollisionen angeht. Es geht hier schlichtweg darum, sich vorher Gedanken zu machen und dadurch einen Weg zu finden, das Problem zu lösen, anstatt blind auf die GM Funktionen zu vertrauen. Wie bereits erwähnt, widmen wir uns dem Spiel "Pong" und gehen nun einfach mal alle Schritte durch:

      Als erstes sollten wir uns die Frage stellen "Was soll das Spiel am Ende können?". Glücklicherweise haben wir uns das einfachste Spiel ausgesucht: bei "Pong" befinden sich auf der linken und rechten Seite des Bildschirms jeweils ein Paddle, das sich auf und ab bewegen lässt. Entweder steuert jeder Spieler eins, oder eine Art KI bedient eins davon. Beide spielen einen Ball hin und her. Wenn der Ball auf einer Seite den Bildschirm verlässt, bekommt der Spieler auf der anderen Seite den Punkt.

      Das sollte natürlich jedem klar sein und vielleicht denkt der Eine oder Andere, dass es völliger Blödsinn ist, dies zu erwähnen. Ich hingegen bin der Meinung, dass es wichtig ist, um sich darüber klar zu werden, wie es weiter geht. Natürlich muss man bei einem Mini-Projekt wie "Pong" kein Design-Dokument anlegen. Bei größeren Spielen hingegen wäre es ratsam.
      Die nächste Frage lautet "Wie setze ich das Spiel bugfrei und möglichst komfortabel um?".


      2. Weg von der Game Maker Kollisionserkennung...

      Wie vielen bekannt sein dürfte, fliegen manche Objekte hin und wieder durch andere durch, obwohl sie beispielsweise abprallen oder zerschellen sollten. Der Grund dafür ist, dass der Game Maker in sogenannten Steps arbeitet. Nehmen wir also mal an, die Paddles hätten nur eine Breite von 1 Pixel und der Ball wäre auch nur 1 Pixel groß. Wenn er sich nun mit einer Geschwindigkeit von 4 Pixeln pro Step bewegt, könnte es passieren, dass er das Paddle einfach "überspringt" und somit keine Kollisionsprüfung stattfinden kann.

      In unserem Fall können wir komplett auf die Kollisionserkennung des Game Maker verzichten. Stattdessen lassen wir den Ball überprüfen, wo er sich befindet. Wenn er weit genug nach links geflogen ist, sodass er auf das linke Paddle auftreffen könnte, wird überprüft, ob er innerhalb der oberen und unteren Kante des Paddles liegt. Ist dies der Fall, wird seine horizontale Geschwindigkeit einfach umgekehrt. Natürlich müssen wir darauf achten, dass er nicht schon hinter dem Paddle ist, und somit ein Abprallen unrealistisch wäre. Ich habe hierfür ein Example angefertigt:

      Pong Example Part I (GMK)
      Pong Example Part I (GM6)

      Ich habe bewusst auf Sprites verzichtet, um zum Einen dir Größen der Objekte anpassbar zu lassen. Zum Anderen soll die Kollisionserkennung ohne Grafiken demonstriert werden. Schauen wir uns das Create Event des Objektes "obj_controller" an. Hier werden alle Einstellungen für die Paddles und den Ball vorgenommen.

      Das eigentliche Spiel läuft komplett im Step Event ab. Im obersten Code-Abschnitt unter dem Kommentar "Zum Spielstart SPACE drücken" wird der Spieler aufgefordert, die Leertaste zu drücken. Anschließend wird die Variable "progress" auf 1 gestellt. Nun läuft das Spiel.

      Der erste Code-Block unter "Spiel läuft" befasst sich mit den Kollisionen zwischen Ball und Paddles. Der oberste Teil davon kehrt seine vertikale Geschwindigkeit um, wenn er am oberen oder unteren Bildrand ankommt.

      Die nächsten beiden Teile sehen komplizierter aus, als sie eigentlich sind. Es geht darum, zu erkennen, ob der Ball auf ein Paddle trifft. Hier die Abfrage zur Kollision mit dem linken Paddle:

      GML-Quellcode

      1. if(x <= dist_to_border+width+radius && x >= dist_to_border && hspeed < 0)

      Die Variable "dist_to_border" gibt den Abstand des Paddles zum linken Bildrand an. "width" ist die Paddlebreite. "radius" stellt den Radius des Balls dar. Hier wird also abgefragt, ob die x-Koordinate des Balls kleiner als die Paddle-Entfernung zum linken Bildrand + Paddlenbreite + Ballradius ist. Außerdem - um die Prüfung nicht stattfinden zu lassen, wenn der Ball hinter dem Paddle ist - wird abgefragt, ob die x-Koordinate größer ist, als die Paddle-Entfernung zum Bildrand, und ob die horizontale Geschwindigkeit nach links geht.
      Die darunterliegende Abfrage tut das gleiche für das rechte Paddle. Hier spielt nur die Distanz zum rechten Rand eine Rolle. Deshalb kommt "room_width" darin vor. Der Vorteil ist, dass sich somit verschiedene Room-Größen einstellen lassen, ohne den Code jedes Mal ändern zu müssen.

      Der anschließende Code-Abschnitt unter "PADDLES" sollte klar sein. In diesem Example steuert jeder Spieler ein Paddle. Der linke Spieler nutzt die Tasten "W" und "S", während der rechte Spieler mit den Cursortasten auf und ab steuert. Keins der Paddles lässt sich aus dem Raum bewegen.

      Unter "Spielregeln" wird überprüft, ob der Ball links oder rechts aus dem Bildschirm fliegt. Verlässt er beispielsweise rechts das Spielfeld, bekommt der linke Spieler einen Punkt. Desweiteren werden in diesem Fall beide Paddle und der Ball wieder auf die Ursprungsposition gesetzt. Nun muss wieder die Leertaste gedrückt werden, um fortzufahren.

      Das Draw Event von "obj_controller" erklärt sich von selbst. Wie ihr seht, befindet sich das ganze Spiel in nur einem Objekt. Das macht in vielen Fällen nicht unbedingt Sinn und das kann natürlich jeder machen, wie es ihm am besten gefällt. Ich persönlich finde es in diesem Fall sehr übersichtlich.

      Wichtig: Diese Kollisionserkennung ist keineswegs perfekt. Sie ist auf dieses Beispiel ausgelegt. Sollten die Paddles und der Ball diese Größe beibehalten, so muss ein etwas größeres Toleranzfeld zur Kollisionserkennung eingerichtet werden.



      3.Wie langweilig...

      Zugegeben, bisher ist das Spiel ziemlich langweilig und ich verspreche auch nicht, dass es ein Kracher wird. Aber ein paar kleine Veränderungen bringt folgendes Example mit:

      Pong Example Part II (GMK)
      Pong Example Part II(GM6)

      Neben den veränderten Grafiken ist noch folgendes passiert: im Create Event ist die Variable "raise_speed" hinzugekommen. Sie wird in jedem Step auf die Geschwindigkeit des Balls addiert, um ihn Stück für Stück schneller werden zu lassen.
      Außerdem prallt der Ball nun nicht mehr einfach nur von den Paddles ab. Seine Flugrichtung ist nun vom Aufprallpunkt am Paddle abhängig. Wenn z.B. der linke Spieler sein Paddle so ausrichtet, dass der Ball recht weit oben aufprallt, so wird er nach der Kollision nach rechts oben fliegen. Dazu existieren zwei Zeilen im Step Event im Bereich der Ball-Kollisions-Überprüfung. Die des linken Paddles schauen wir uns an:

      GML-Quellcode

      1. direction = (y1-y)*2;

      In den Klammern werden ganz einfach die Y-Koordinaten von Paddle und Ball verglichen. Da mir das Ergebnis allerdings zu schwach erscheint, wird es anschließend mit 2 multipliziert. Im Grunde würde also auch "direction = y1-y;" ausreichen, um es zu vereinfachen.


      3. Kein zweiter Spieler da?

      Eigentlich wäre das Tutorial zur simplen Kollisionserkennung hier bereits zuende. Man sieht allerdings sehr selten einen Pong-Clone mit einer KI. Da viele sicher nicht wissen, wie einfach das ist, habe ich noch dieses dritte Example geschrieben.

      Pong Example Part III (GMK)
      Pong Example Part III (GM6)

      Der Trick ist, dass man ein unsichtbares Ballobjekt erzeugt, welches schneller fliegt, als der eigentliche Ball. Sobald es im Bereich des KI-Paddles angelangt ist, bewegt sich dieses Paddle dort hin. Im Example existiert deshalb nun ein weiteres Objekt: "obj_ai_ball". Es stoppt, sobald es sich in der Nähe der Bewegungsbahn des Gegner-Paddles befindet.

      Im "obj_controller" befindet sich nun im Create Event eine neue Variable "dir", welche für die Aktion des KI-Paddles steht. Im Step Event sind anstelle der Bewegungsabfragen für den rechten Spieler nun die KI-Codes getreten:

      GML-Quellcode

      1. // zum "Fake Ball" bewegen
      2. if(instance_exists(obj_ai_ball)) {
      3. if(obj_ai_ball.speed = 0) {
      4. if(obj_ai_ball.y < y2-radius) {dir = 1;}
      5. if(obj_ai_ball.y > y2+radius) {dir = 2;}
      6. }
      7. }

      Es wird also abgefragt, ob der "Fake-Ball" existiert und ob er sich nicht bewegt (er bewegt sich nur dann nicht, wenn er auf der Bahn des rechten Paddles ist). Wenn dies der Fall ist, wird überprüft, ob er oberhalb oder unterhalb des Paddles ist. Je nachdem bewegt es sich an diese Stelle.

      Wie ihr sicher bemerkt habt, ist unter diesem Code-Block ein weiterer ausgeklammert. Wenn er aktiviert ist, bewegt sich das Paddle automatisch zum Mittelpunkt, wenn kein "Fake-Ball" existiert. Dadurch wird das Spiel um einiges schwerer.


      4. das war's!

      Vermutlich ist das alles ziemlich wirr geschrieben. Dennoch hoffe ich, dass dieses Tutorial seinen Zweck erfüllt und sowohl zum Nachdenken anregt, als auch die Grundlagen der eigenen Kollisionserkennung zu legen. Tut euch und euren Projekten den Gefallen und geht sie in Ruhe an. Nehmt euch die Zeit, in einem Textverarbeitungsprogramm (oder wie ich in einer txt-Datei) die wichtigsten Punkte aufzuschreiben. Es hilft ungemein. Nicht alle Projekte sterben aus Motivationsmangel. Schlechte Planung ist oftmals auch ein Grund dafür. Ich wünsche euch viel Spaß und vielleicht sieht man ja demnächst mal einen außergewöhnlichen Pong-Clone!

      Rechtschreibfehler und sonstige Bugs bitte melden. Ich korrigiere es dann.

      Vielen Dank an moolt, der die GMK-Files ins GM6 Format gebracht hat.


      Und hier noch mal alle Tutorials (GMK + GM6) in einem RAR-Archiv:

      Pong Examples

      Wichtig: Dieses Tutorial bietet nur die Grundlage. Ausgiebig getestet habe ich nichts - somit sind also Fehler nicht ausgeschlossen. Ich möchte euch nicht dazu animieren, die Codes zu kopieren. Sie sind wie gesagt nicht perfekt und müssen auf die eigenen Wünsche angepasst werden. Ich möchte hiermit nur euren Gedankengängen einen Anstoß geben.
      █████ ██ █ ████ everything ███ █████ is █████ ████ ████ fine ████ ███ █ ██████ love.
      █████ ███████ ███ your █████ ████ government.

      Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von mauge ()

    • Danke, SDX. Mir ist grad eingefallen, dass viele hier noch viele User mit dem GM6 arbeiten. Wenn jemand Lust und Zeit hat, kann er ja meine Examples mal als gm6 speichern und mir geben. Ich lade sie dann hoch und füge sie in das Tutorial ein.

      Edit: Moolt war so nett und hat die GMKs in GM6-Files umgewandelt. Vielen Dank noch mal dafür! Sie befinden sich im ersten Post. Außerdem beinhaltet das Tutorial am Ende noch mal alle Examples (GMK + GM6) in einem RAR-Archiv.
      █████ ██ █ ████ everything ███ █████ is █████ ████ ████ fine ████ ███ █ ██████ love.
      █████ ███████ ███ your █████ ████ government.

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von mauge ()

    • Wenn Ball und Paddle nun 1 Pixel breit sind und der Ball mit 4px/step fliegt, würde es dann nicht das gleiche Problem wie mit den Sprites geben? ;)

      Trotzdem ganz schön ^^
      So far, Schattenphoenix~
      _____________________________________________________________________________
      "Who needs a stairway to heaven...
      If there is an elevator to hell... ?
      "
      - Vergessen
      "Auch ein perfektes Chaos ist etwas vollkommenes."
      - Jean Genet
    • Danke erst mal, Schattenphoenix.

      Schattenphoenix schrieb:

      Wenn Ball und Paddle nun 1 Pixel breit sind und der Ball mit 4px/step fliegt, würde es dann nicht das gleiche Problem wie mit den Sprites geben? ;)

      Darauf bin ich im Tutorial schon eingegangen:

      mauge schrieb:

      Wichtig: Diese Kollisionserkennung ist keineswegs perfekt. Sie ist auf dieses Beispiel ausgelegt. Sollten die Paddles und der Ball diese Größe beibehalten, so muss ein etwas größeres Toleranzfeld zur Kollisionserkennung eingerichtet werden.

      Die Sprites hab ich nur zur Verdeutlichung weggelassen, um halt wirklich von der grafischen Kollisionserkennung wegzuleiten. Es geht ganz einfach darum, dass man sich bezüglich der Kollisionen selbst Gedanken machen muss. Man muss sie den Anforderungen des Spieles anpassen. Das funktioniert aber meistens nicht, wenn man die GM interne Kollisionserkennung nutzt. Das Tutorial zeigt auch wirklich nur die simpelste Methode.
      █████ ██ █ ████ everything ███ █████ is █████ ████ ████ fine ████ ███ █ ██████ love.
      █████ ███████ ███ your █████ ████ government.
    • O.k. hab ich so nich ganz beachtet, mich machte nur erstmal stutzig, dass du genau das, was du zuvor angesprochen hast, in dem Tut. nicht beseitigt hattest. Aber gut, ich hätte genauer lesen sollen =P

      Was ich übrigens schwer finde, wenn man von der Kollision des Gamemakers weg will, ist die Thematik komplexerer Formen für die Kollision.

      Wenn ich einen Menschen habe, reicht ein Kreis, das geht vielleicht noch, aber komplexere Formen (z.B. ein Alienartiger Endgegner in nem Schmup (Shoot em Up)) sind mit sowas echt schwer... und ich glaube nicht effektiv.

      In so einem fall benutze ich z.B. for-Schleifen zusammen mit place_meeting um bei der Positionierung "feiner" agieren zu können.
      So far, Schattenphoenix~
      _____________________________________________________________________________
      "Who needs a stairway to heaven...
      If there is an elevator to hell... ?
      "
      - Vergessen
      "Auch ein perfektes Chaos ist etwas vollkommenes."
      - Jean Genet
    • Also ich habe mal mit einer DLL angefangen, die überprüft, ob sich zwei Polygone überschneiden. Die Formeln habe ich schon (von meinem Vater), aber ich muss ncoh bischen was machen (Schleifen um die einzelnen Linien und Punkte zu überprüfen). Wenn ihr interesse habt kann ich sie ja mal posten wenn sie fertig ist.
    • Schattenphoenix schrieb:


      In so einem fall benutze ich z.B. for-Schleifen zusammen mit place_meeting um bei der Positionierung "feiner" agieren zu können.

      Über diese Variante mit den Schleifen wollte ich evtl. das nächste Tutorial schreiben. :)

      @maxda: Ich würd mir deine DLL gern anschauen, wenn's soweit ist.
      █████ ██ █ ████ everything ███ █████ is █████ ████ ████ fine ████ ███ █ ██████ love.
      █████ ███████ ███ your █████ ████ government.
    • Benutzer online 1

      1 Besucher