Weitere 3d-Anfänger Tutorials gibt's hier:

Vorwort: Dieses Tutorial befasst sich mit 3D Kollision, ein unverzichtbarer Bestandteil in fast jedem Spiel. Vorweg möchte ich schon sagen, dass nicht alles vorbildlich gelöst wurde und dass mein Beispiel durchaus Bugs enthält/enthalten kann. Dieses Tutorial erklärt nur die absoluten Grundlagen und soll nur einen groben Eindruck schaffen, wie 3D Kollision funktioniert.
Theorie: Da es keine eingebauten Funktionen zur 3D Kollision gibt, muss man das ganze selbst erledigen. Dies kann man am geschicktesten mit einer Bounding Box machen. Dies ist eine Box, die nicht visuell existiert, sondern nur im Code. Wenn das Spieler Objekt später mit ihr in Berührung kommt, findet Kollision statt. Sie wird praktisch über das gelegt, was im Draw Code gezeichnet wird.
In Grafik 1 sieht man die bounding Box wie man sich sie vorstellen kann. 2 stellt dagegen dar, wie sie einen gleichgroßen Block umschließt, d.h. es findet auch genau da Kollision statt, wo der Block gezeichnet wird. Auch komplexere Gebilde, wie z.B. ein Cylinder in Abbildung 3, lassen sich von einer bounding Box umschließen, die Kollision wäre jedoch nicht so perfekt wie es in Abb. 2 der Fall ist.
Wenn du jetzt noch nicht richtig mitgekommen bist, mach dir keine Sorgen. Die Theorie kann viel beim Verständnis helfen, ist jedoch nicht zwingend erforderlich und wird sich im Folgenden selbst erklären.
Für dieses Tutorial benutze ich eine abgeänderte Variante des Examples meines Anfänger Tutorials. Am besten du lädst es dir gleich hier oder im Anhang runter. Überall wo sich ein "//-->" befindet, wird später Code zugefügt, du solltest es also später wieder entfernen. (Davon betroffen sind obj_block und obj_player)
In dem Example befinden sich alle Resoucen, welche wir im Folgenden brauchen werden und Ich habe alles für dieses Tutorial angepasst. So sieht die Kamera nun schräger auf das Geschehen und sämtlicher Code zum Bewegen wurde vorerst aus dem Player Objekt entfernt. Auch das Create Event vom Camera Objekt würde entschlackt, da viele Zeile überflüssig waren und nur zum Erklären geeignet sind.
Schritt 1: Vorbereitung
Bevor wir nun mit den Kollisionen anfangen, müssen noch einige Vorbereitungen getroffen werden. Zuerst fügen wir zwei deklarieren wir zwei neue Variablen im Create Event von obj_player.
Auch wenn diese Variablen eindeutig scheinen, möchte ich sie lieber näher erklären, um Missverständnisse vorzubeugen:
z ist immer die Höhe der unteren Fläche des Blocks. height dagegen ist vollkommen unabhängig von z. Sie ist die Höhe des Objekts ohne von z beeinflusst zu werden. Die absolute Höhe der oberen Fläche ist daher immer height + z .
Zuerst erlauben wir unserem Spieler, sich zu bewegen. Dafür schreiben wir folgendes in das noch leere Step Event von obj_player.
Drückt der Spieler die linke Pfeiltaste, erhöht sich die direction jeden step um 7. Andersrum gilt dies für die rechte Pfeiltaste. Ähnlich funktionieren die beiden folgenden Zeilen: Wenn die Pfeiltaste-oben gedrückt ist, wird dem Objekt der speed 1 zugewiesen. Sollte keine Taste gedrückt werden ist der speed = 0, das Objekt bewegt sich nicht.
Unser Block kann sich nun bewegen, theoretisch. Wenn man das Ganze nun startet, wird man festellen, dass es nicht einfach ist, den Block in die gewünschte Richtung zu lenken. Dies liegt daran, dass ich die Rotation des Blocks entfernt habe, es dreht sich also nicht mehr in die Richtung in die er sich auch bewegt. Den genauen Grund werde ich später nocheinmal aufgreifen. Damit man jedoch vernünftig Koordinieren kann, kann man z.B. einen Pfeil auf den Block setzen, welcher die Richtung anzeigt.
obj_player - Draw Event:
Wie Rotationen funktionieren solltest du bereits wissen. Durch "d3d_draw_floor" lassen wir die Fläche zeichnen, auf der unser Pfeil angezeigt wird.
Diesen habe ich in spr_arrow bereits vorbereitet. Sachen wie Farbe oder Blendmode sind natürlich optional.
Wir sollten nun einen Pfeil haben, welcher sich einen Pixel über dem Block befindet und sich mit der direction des Objekts dreht.
Im Code sollte noch die Zeile
stehen. Hier müssen wir die Z Koordinaten abändern.
Der Block wird nun auf z und z+height gezeichnet, d.h. die Höhe unseres Blocks ist nun variabel, wenn wir z ändern.
Die Vorbeireitungen sind damit abgeschlossen, wir können nun endlich mit Kollision anfangen. Das derzeitige Resultat gibt's hier oder im Anhang.
Schritt 2: 2D Kollision
Da wir nichts überstürzen wollen, fangen wir klein an, mit 2D Kollision. Die Z-Achse lassen wir erstmal außen vor.
Das erste was wir machen müssen ist, obj_player und obj_block das spr_collision zuweisen, welches sich bereits im Example befindet.
Dies wird vorerst unsere "zweidimensionale bounding box", oder anders: Eine Bounding Box mit unendlicher Höhe.
Für die 2D Kollision brauchen wir nun obj_block.
In das Kollisions Event mit obj_player schreiben wir:
Das "with(other)" bewirkt, dass der folgende Code so ausgeführt wird, als stände er in obj_player.
xprevious ist die vorherige x Koordinate des Spielers. x = xprevious bewirkt also, dass obj_player nicht mehr durch den Block durchlaufen kann.
Kollidiert man nun mit einem Block, kann man nicht an ihm "entlanggleiten", man bleibt also stecken. Um dies zu verhindern, kommt folgender Code unter die Zeile "y=yprevious;":
In der ersten Zeile wir überprüft, ob keine Kollision mit other, in diesem Fall ist es obj_block, besteht. x wird solange um hspeed erhöht, bis durch
"place_meeting(x+hspeed, [...]" ausgegeben wird, dass im nächsten step Kollision stattfindet.

Gleiches gilt für den folgenden Code, welcher für die 2 anderen Richtungen verwantwortlich ist.
Schritt 3: 3D Kollision
Jetzt wo wir bereits 2D Kollision haben, kommt nun noch die Z Achse dazu. Auch dafür sind wieder ein paar Vorbereitungen zu treffen:
In obj_player fügen wir wieder eine Variable, diesmal z_floor, hinzu.
Mit dieser Variable können wir später ermitteln, ob sich unter obj_player ein Block befindet und bekommen gegebenfalls seinen z+height Wert zurückgegeben.
Im step Event ergänzen wir folgende Zeilen:
Mit der ersten Zeile erhöhen wir den z-Wert beim Drücken der Taste Y um 1.
Mit X verringern wir diesen um 1, unter der Vorraussetzung, dass z größer als z_floor ist.
Zur Erinnerung: z_floor haben wir gerade als 0 deklariert, momentan kann der Block also nicht tiefer als der Boden sein.
Nun begeben wir uns wieder in das Kollisions Event von obj_block und ergänzen im bestehenden Code folgende Abfrage:
Die Abfrage lässt sich durch folgende Grafik erklären.
Kollision findet also nur statt, wenn der z-Wert von obj_player kleiner als die Höhe des Blocks ist.
Mit obj_block sind wir soweit fertig, weiter gehts wieder bei obj_player:
Ein Problem welches jetzt noch besteht ist, dass der Spieler von oben in einen Block sacken kann. Dafür ergänzen wir unseren Code im step Event:
Alles anzeigen
Die Funktion "instance_place()" ersetzt das Kollisions Event. Der Rückgabewert ist die ID vom jeweiligen obj_block, welche wir in die Variable "obj" speichern.
Danach wird abgefragt, ob eine Kollision stattfindet. Ist der Variable obj nämlich eine ID zugewiesen, ist der Wert positiv, also true. In diesem Fall wird die Variable z_floor mit height des anderen Objekts gleichgesetzt.
Zur Erinnerung: Folgende Zeile verhindert, dass z kleiner als z_floor werden kann:
Die letzte Zeile sorgt für Sicherheit. Sollte es passieren, dass z kleiner als z_floor ist, wird dies sofort berichtigt.
Schritt 4: Fertigstellung
Beim testen wird euch sicher aufgefallen sein, dass euer Spieler manchmal direkt in einem Block startet. Um dies zum Schluss noch zu verhindern, können wir folgendes noch im create Event von obj_block ergänzen:
Damit haben wir nun eine grundlegende 3D Kollision, auch wenn sie noch viele Ecken und Kanten hat. Wenn ihr das Muster verstanden habt, wird es euch nicht schwer fallen die Idee zu erweitern. Das Resultat gibt's hier oder im Anhang.
Theorie: Da es keine eingebauten Funktionen zur 3D Kollision gibt, muss man das ganze selbst erledigen. Dies kann man am geschicktesten mit einer Bounding Box machen. Dies ist eine Box, die nicht visuell existiert, sondern nur im Code. Wenn das Spieler Objekt später mit ihr in Berührung kommt, findet Kollision statt. Sie wird praktisch über das gelegt, was im Draw Code gezeichnet wird.

In Grafik 1 sieht man die bounding Box wie man sich sie vorstellen kann. 2 stellt dagegen dar, wie sie einen gleichgroßen Block umschließt, d.h. es findet auch genau da Kollision statt, wo der Block gezeichnet wird. Auch komplexere Gebilde, wie z.B. ein Cylinder in Abbildung 3, lassen sich von einer bounding Box umschließen, die Kollision wäre jedoch nicht so perfekt wie es in Abb. 2 der Fall ist.
Genung Theorie, jetzt kommt die Praxis:
Wenn du jetzt noch nicht richtig mitgekommen bist, mach dir keine Sorgen. Die Theorie kann viel beim Verständnis helfen, ist jedoch nicht zwingend erforderlich und wird sich im Folgenden selbst erklären.
Für dieses Tutorial benutze ich eine abgeänderte Variante des Examples meines Anfänger Tutorials. Am besten du lädst es dir gleich hier oder im Anhang runter. Überall wo sich ein "//-->" befindet, wird später Code zugefügt, du solltest es also später wieder entfernen. (Davon betroffen sind obj_block und obj_player)
In dem Example befinden sich alle Resoucen, welche wir im Folgenden brauchen werden und Ich habe alles für dieses Tutorial angepasst. So sieht die Kamera nun schräger auf das Geschehen und sämtlicher Code zum Bewegen wurde vorerst aus dem Player Objekt entfernt. Auch das Create Event vom Camera Objekt würde entschlackt, da viele Zeile überflüssig waren und nur zum Erklären geeignet sind.
Schritt 1: Vorbereitung
Bevor wir nun mit den Kollisionen anfangen, müssen noch einige Vorbereitungen getroffen werden. Zuerst fügen wir zwei deklarieren wir zwei neue Variablen im Create Event von obj_player.
Auch wenn diese Variablen eindeutig scheinen, möchte ich sie lieber näher erklären, um Missverständnisse vorzubeugen:

z ist immer die Höhe der unteren Fläche des Blocks. height dagegen ist vollkommen unabhängig von z. Sie ist die Höhe des Objekts ohne von z beeinflusst zu werden. Die absolute Höhe der oberen Fläche ist daher immer height + z .
Zuerst erlauben wir unserem Spieler, sich zu bewegen. Dafür schreiben wir folgendes in das noch leere Step Event von obj_player.
Drückt der Spieler die linke Pfeiltaste, erhöht sich die direction jeden step um 7. Andersrum gilt dies für die rechte Pfeiltaste. Ähnlich funktionieren die beiden folgenden Zeilen: Wenn die Pfeiltaste-oben gedrückt ist, wird dem Objekt der speed 1 zugewiesen. Sollte keine Taste gedrückt werden ist der speed = 0, das Objekt bewegt sich nicht.
Unser Block kann sich nun bewegen, theoretisch. Wenn man das Ganze nun startet, wird man festellen, dass es nicht einfach ist, den Block in die gewünschte Richtung zu lenken. Dies liegt daran, dass ich die Rotation des Blocks entfernt habe, es dreht sich also nicht mehr in die Richtung in die er sich auch bewegt. Den genauen Grund werde ich später nocheinmal aufgreifen. Damit man jedoch vernünftig Koordinieren kann, kann man z.B. einen Pfeil auf den Block setzen, welcher die Richtung anzeigt.

obj_player - Draw Event:
GML-Quellcode
- d3d_transform_set_identity();
- d3d_transform_add_rotation_z(direction);
- d3d_transform_add_translation(x,y,z+height+1);
- draw_set_color(c_yellow);
- draw_set_blend_mode(bm_add);
- d3d_draw_floor(-3,-3,0,+3,+3,0,sprite_get_texture(spr_arrow,-1),1,1);
- draw_set_blend_mode(bm_normal);
- draw_set_color(c_white);
- d3d_transform_set_identity();
Wie Rotationen funktionieren solltest du bereits wissen. Durch "d3d_draw_floor" lassen wir die Fläche zeichnen, auf der unser Pfeil angezeigt wird.
Diesen habe ich in spr_arrow bereits vorbereitet. Sachen wie Farbe oder Blendmode sind natürlich optional.
Wir sollten nun einen Pfeil haben, welcher sich einen Pixel über dem Block befindet und sich mit der direction des Objekts dreht.
Im Code sollte noch die Zeile
stehen. Hier müssen wir die Z Koordinaten abändern.
Der Block wird nun auf z und z+height gezeichnet, d.h. die Höhe unseres Blocks ist nun variabel, wenn wir z ändern.
Die Vorbeireitungen sind damit abgeschlossen, wir können nun endlich mit Kollision anfangen. Das derzeitige Resultat gibt's hier oder im Anhang.
Schritt 2: 2D Kollision
Da wir nichts überstürzen wollen, fangen wir klein an, mit 2D Kollision. Die Z-Achse lassen wir erstmal außen vor.
Das erste was wir machen müssen ist, obj_player und obj_block das spr_collision zuweisen, welches sich bereits im Example befindet.
Dies wird vorerst unsere "zweidimensionale bounding box", oder anders: Eine Bounding Box mit unendlicher Höhe.
Für die 2D Kollision brauchen wir nun obj_block.
In das Kollisions Event mit obj_player schreiben wir:
Das "with(other)" bewirkt, dass der folgende Code so ausgeführt wird, als stände er in obj_player.
xprevious ist die vorherige x Koordinate des Spielers. x = xprevious bewirkt also, dass obj_player nicht mehr durch den Block durchlaufen kann.
Kollidiert man nun mit einem Block, kann man nicht an ihm "entlanggleiten", man bleibt also stecken. Um dies zu verhindern, kommt folgender Code unter die Zeile "y=yprevious;":
In der ersten Zeile wir überprüft, ob keine Kollision mit other, in diesem Fall ist es obj_block, besteht. x wird solange um hspeed erhöht, bis durch
"place_meeting(x+hspeed, [...]" ausgegeben wird, dass im nächsten step Kollision stattfindet.

Gleiches gilt für den folgenden Code, welcher für die 2 anderen Richtungen verwantwortlich ist.
Schritt 3: 3D Kollision
Jetzt wo wir bereits 2D Kollision haben, kommt nun noch die Z Achse dazu. Auch dafür sind wieder ein paar Vorbereitungen zu treffen:
In obj_player fügen wir wieder eine Variable, diesmal z_floor, hinzu.
Mit dieser Variable können wir später ermitteln, ob sich unter obj_player ein Block befindet und bekommen gegebenfalls seinen z+height Wert zurückgegeben.
Im step Event ergänzen wir folgende Zeilen:
Mit der ersten Zeile erhöhen wir den z-Wert beim Drücken der Taste Y um 1.
Mit X verringern wir diesen um 1, unter der Vorraussetzung, dass z größer als z_floor ist.
Zur Erinnerung: z_floor haben wir gerade als 0 deklariert, momentan kann der Block also nicht tiefer als der Boden sein.
Nun begeben wir uns wieder in das Kollisions Event von obj_block und ergänzen im bestehenden Code folgende Abfrage:
Die Abfrage lässt sich durch folgende Grafik erklären.

Kollision findet also nur statt, wenn der z-Wert von obj_player kleiner als die Höhe des Blocks ist.
Mit obj_block sind wir soweit fertig, weiter gehts wieder bei obj_player:
Ein Problem welches jetzt noch besteht ist, dass der Spieler von oben in einen Block sacken kann. Dafür ergänzen wir unseren Code im step Event:
GML-Quellcode
Die Funktion "instance_place()" ersetzt das Kollisions Event. Der Rückgabewert ist die ID vom jeweiligen obj_block, welche wir in die Variable "obj" speichern.
Danach wird abgefragt, ob eine Kollision stattfindet. Ist der Variable obj nämlich eine ID zugewiesen, ist der Wert positiv, also true. In diesem Fall wird die Variable z_floor mit height des anderen Objekts gleichgesetzt.
Zur Erinnerung: Folgende Zeile verhindert, dass z kleiner als z_floor werden kann:
Die letzte Zeile sorgt für Sicherheit. Sollte es passieren, dass z kleiner als z_floor ist, wird dies sofort berichtigt.
Schritt 4: Fertigstellung
Beim testen wird euch sicher aufgefallen sein, dass euer Spieler manchmal direkt in einem Block startet. Um dies zum Schluss noch zu verhindern, können wir folgendes noch im create Event von obj_block ergänzen:
Damit haben wir nun eine grundlegende 3D Kollision, auch wenn sie noch viele Ecken und Kanten hat. Wenn ihr das Muster verstanden habt, wird es euch nicht schwer fallen die Idee zu erweitern. Das Resultat gibt's hier oder im Anhang.