2D Spiele

Top  Previous  Next

Diesen Monat kümmern wir uns um anspruchsvollere 2D-Kollisionsmechanismen. Mit einfacher Kollision, die mit rechteckigen Objekten gut funktioniert, haben wir uns ja schon befaßt. Was aber machen wir, wenn wir es mit einem Player zu tun haben, der in einem Level, das so aussieht, wie unten abgebildet, herumlaufen und dabei nicht ins Wasser tappen soll?

 

aum84_workshop1

 

Starten wir das erste Beispiel (2dcollision1.c) - hierbei handelt es sich um das einfachste Demo, das den fortgeschrittenen Kollisionserkennungsmechanismus verwendet.

 

aum84_workshop2

 

Bewegen Sie den Player mit den rechts- / links-Cursortasten. Sie werden sehen, der Player folgt exakt der roten Linie. Nun schauen wir uns den Code mal genau an:

 

#include <acknex.h>

#include <default.c>

 

BMAP* level_tga; // pointer to the level bitmap

BMAP* player_png = "player.png";

 

PANEL* my_player;

 

PANEL* level_pan =

{

       layer = 10;

       pos_x = 0;

       pos_y = 0;

       bmap = "landscape.tga";

       flags = SHOW;

}

 

function main()

{

       fps_max = 70;

       video_mode = 7; // 800 x 600 pixels

       video_depth = 32; // 32 bit mode

       video_screen = 1; // start in full screen mode

}

 

Soweit nichts Neues, betrachten wir uns die Funktion, die den Player antreibt.

 

function player_startup()

{

       my_player = pan_create("bmap = player_png;", 100);

       my_player.pos_x = 325;

       my_player.pos_y = 425;

       my_player.flags |= SHOW;

       while (1)

       {

               my_player.pos_x += 2 * (key_cur - key_cul);

               my_player.pos_x = clamp(my_player.pos_x, -15, 750);

               wait (1);

       }

}

 

Die Funktion player_startup() erstellt ein Panel und weist ihm den Namen "my_player" zu. Wir setzen die Anfangsplayerwerte pos_x und pos_y und machen ihn dann sichtbar. Die "while"-Schleife bewegt den Player indem sie seinen pos_x-Wert verändert und sicherstellt, dass pos_y im Bereich von 15...750 Pixeln bleibt.

 

function collisions_startup()

{

       wait (3); // wait until the video functions are available

       var coords_x;

       var coords_y;

       var format;

       var pixel;

       COLOR pixel_color;

       level_tga = bmap_create("landscape.tga");

       while (1)

       {

               coords_x = my_player.pos_x + bmap_width(player_png) / 2;

               coords_y = my_player.pos_y + bmap_height(player_png) - 15; // play with 15

               format = bmap_lock (level_tga, 0);

               pixel = pixel_for_bmap(level_tga, coords_x, coords_y);

               pixel_to_vec (pixel_color, NULL, format, pixel); // store the color of the pixel in pixel_color

               bmap_unlock (level_tga);

               // detected a red pixel on the color map bitmap?

               if (pixel_color.red == 255)

               {

                       my_player.pos_y += 1;

               }

               else // this isn't a red pixel on the color map?

               {

                       my_player.pos_y -= 1;

               }

               wait (1);

       }

}

 

Die obige Funktion birgt das ganze Geheimnis der Kollision. Sie wartet bis die Videofunktionen zur Verfügung stehen und erstellt dann eine Bitmap, die sie level_tga nennt. Das nun ist genau die Bitmap, die als Level für unser Demo dient. Die "while"-Schleife macht einige interessante Sachen:

 

- Zuallererst werden die Variablen coords_x und coords auf eine Position von 15 Quants unterhalb der Playerfüße gesetzt. Um die passenden Werte zu berechnen, verwenden wir Breite und Höhe der Player-Bitmap.

 

- Dan verriegeln wir die Bitmap level_tga und bekommen die über die Variablen coords_x und coords_y vorgegebene Farbe des Pixels, welche wir im pixel_color FARB-Vektor speichern. (Der Farbvektor ähnelt einem regulären Vektor, benutzt aber .blue, .green und .red anstelle von .x, .y, .z).

 

- Ist die Rotkomponente von pixel_color reines Rot (pixel_color.red ==255), berühren die Füsse des Players die rote Linie, also bewegen wir ihn um 1 Pixel nach oben. Im anderen Fall, wenn wir eben keinen roten Pixel unterhalb der Playerfüsse ausmachen, schieben wir ihn um 1 Pixel nach unten.

 

Mehr braucht es nicht für fortgeschrittenen Kollisionsmechanismus. Sie prüfen einfach die Farbe des Pixels beim Player und das war´s! Sie werden vermutlich bemerkt haben, dass unser Player ein bischen hüpft? Das geschieht, weil der Code in 70 Mal pro Sekunde (was dem Wert von fps_max, der in der Main-Funktion gesetzt ist, entspricht) hoch und runter schiebt. Die Lösung dieses Problems ist einfach und wurde ins Demo 2dcollision2.c eingefügt - dort hüpft der Player nicht mehr.

 

Der Code ist fast genau derselbe wie der des vorigen Demos, schauen wir mal, was sich geändert hat:

 

function collisions_startup()

{

       wait (3); // wait until the video functions are available

       var coords1_x, coords1_y, coords2_x, coords2_y, format, pixel1, pixel2;

       var player_offset = 15;

       COLOR pixel1_color, pixel2_color;

       while (1)

       {

               coords1_x = my_player.pos_x + bmap_width(player_tga) / 2;

               coords1_y = my_player.pos_y + bmap_height(player_tga) - player_offset;

               coords2_x = my_player.pos_x + bmap_width(player_tga) / 2;

               coords2_y = my_player.pos_y + bmap_height(player_tga) - player_offset - 2;

               format = bmap_lock (level_tga, 0);

               pixel1 = pixel_for_bmap(level_tga, coords1_x, coords1_y);

               pixel_to_vec (pixel1_color, NULL, format, pixel1); // store the color of the pixel1 in pixel_color

               // detected a red pixel on the color map bitmap?

               if (pixel1_color.red == 255)

               {

                       my_player.pos_y += 1;

               }

               else // this isn't a red pixel on the color map?

               {

                       pixel2 = pixel_for_bmap(level_tga, coords2_x, coords2_y);

                       pixel_to_vec (pixel2_color, NULL, format, pixel2); // store the color of the pixel in pixel_color

                       // we didn't detect a red pixel 2 pixels below the first one? then let's move downwards, towards it!

                       if (pixel2_color.red != 255)

                       {

                               my_player.pos_y -= 1;

                       }

               }

               bmap_unlock (level_tga);

               wait (1);

       }

}

 

Diesmal verwenden wir zwei "Sensoren" zum Verfolgen der Pixelfarbe. Ein Sensor befindet sich 15 Pixel (-player_offset) unterhalb der Füsse des Players und der andere liegt 17 Pixel (-player_offset - 2) unterhalb der Playerfüsse. Hat der erste Sensor einen roten Pixel ausgemacht, schiebt er den Player nach oben - genau wie vorher auch. Hat er aber keinen roten Pixel erkannt, wird die Kontrolle vom zweiten Sensoren übernommen und der schiebt den Player nur dann nach unten, wenn auch er keinen roten Pixel ekennt. Auf diese Weise können wir sicher sein, dass der Player nicht rauf und runter hüpft und er solange der roten Spur folgt, bis einer seiner Sensoren irgendwann auf einen roten Pixel stößt.

 

Wie übersetzen wir diesen Code jetzt dahingehend, dass er in einem Level mit Wasser, Häusern usw. funktioniert? Öffnen Sie 2dcollision3.c und starten sie es:

 

aum84_workshop3

 

Hier nun kann sich der Player frei im Level bewegen und kollidiert mit den roten Bereichen, die im ersten Bild dieses Workshops Wasser darstellen. Diesmal verwenden wir vier Sensoren - sehen Sie selbst:

 

function collisions_startup()

{

       wait (3); // wait until the video functions are available

       var coords1_x, coords1_y, coords2_x, coords2_y, coords3_x, coords3_y, coords4_x, coords4_y;

       var format, pixel1, pixel2, pixel3, pixel4;

       COLOR pixel1_color, pixel2_color, pixel3_color, pixel4_color;

       while (1)

       {

               // check player's feet

               coords1_x = my_player.pos_x + bmap_width(player_tga) / 2;

               coords1_y = my_player.pos_y + bmap_height(player_tga) - 2; // play with 2

               format = bmap_lock (level_tga, 0);

               pixel1 = pixel_for_bmap(level_tga, coords1_x, coords1_y);

               pixel_to_vec (pixel1_color, NULL, format, pixel1); // store the color of the pixel1 in pixel1_color

               if (pixel1_color.red == 255) // detected a red pixel below player's feet?

               {

                       my_player.pos_y -= 2;

               }

               // check player's head

               coords2_x = my_player.pos_x + bmap_width(player_tga) / 2;

               coords2_y = my_player.pos_y + 20; // play with 20

               pixel2 = pixel_for_bmap(level_tga, coords2_x, coords2_y);

               pixel_to_vec (pixel2_color, NULL, format, pixel2); // store the color of the pixel2 in pixel2_color

               if (pixel2_color.red == 255) // detected a red pixel above player's head?

               {

                       my_player.pos_y += 2;

               }

               // check player's left side

               coords3_x = my_player.pos_x + 2 ; // play with 2

               coords3_y = my_player.pos_y + bmap_height(player_tga) / 2;

               pixel3 = pixel_for_bmap(level_tga, coords3_x, coords3_y);

               pixel_to_vec (pixel3_color, NULL, format, pixel3); // store the color of the pixel3 in pixel3_color

               if (pixel3_color.red == 255) // detected a red pixel on the left side of the player?

               {

                       my_player.pos_x += 2;

               }

               // check player's right side

               coords4_x = my_player.pos_x + bmap_width(player_tga) - 2; // play with 2

               coords4_y = my_player.pos_y + bmap_height(player_tga) / 2;

               pixel4 = pixel_for_bmap(level_tga, coords4_x, coords4_y);

               pixel_to_vec (pixel4_color, NULL, format, pixel4); // store the color of the pixel4 in pixel4_color

               if (pixel4_color.red == 255) // detected a red pixel on the right side of the player?

               {

                       my_player.pos_x -= 2;

               }

               bmap_unlock (level_tga);

               wait (1);

       }

}

 

Die vier Sensoren verfolgen die Positionen, die 2 Pixel unterhalb der Füsse des Players, 20 Pixel über seinem Kopf und 2 Pixel seitlich rechts und links von ihm liegen. Diesmal benutzen wir verschiedene Werte für die Sensoren (spielen Sie ruhig damit herum), denn dem Kopf des Players wollen wir erlauben, das Wasser zu berühren - wie in diesem Bild:

 

aum84_workshop4

 

Übrigens können Sie jedwede Farbe für die Kollisionserkennungsmaske benutzen und alle RGB-Werte lassen sich folgendermassen prüfen:

 

if ((pixel_color.red == 155) && (pixel_color.green == 125) && (pixel_color.blue == 15)) // check for RGB = 155, 125, 15

 

Ich habe ein reines Rot gewählt weil ich markante Kollisionslinien bzw. -Bereiche haben wollte. Ach ja, und diesmal ermöglicht es seine Funktion dem Player auch, sich vertikal zu bewegen.

 

function player_startup()

{

       my_player = pan_create("bmap = player_tga;", 100);

       my_player.pos_x = 325;

       my_player.pos_y = 325;

       my_player.flags |= SHOW;

       while (1)

       {

               my_player.pos_x += 2 * (key_cur - key_cul);

               my_player.pos_y += 2 * (key_cud - key_cuu);

               wait (1);

       }

}

 

Alles ist prima aber irgendwie spüre ich so eine negative Stimmung bei einigen meiner Leser: müssen wir unsere Level hässlich machen und in diesen dummen Farben malen, damit wir die Kollision zum Funktionieren kriegen? Keine Angst, denn das Demo 2dcollision4.c bringt uns einen Schritt weiter und führt die 2D-Kollisionserkennung auf der Grundlage von Alpha ein.

 

aum84_workshop5

 

Ja! Es ist unser gutes altes 2D-Level in seiner ganzen Schönheit und mit funktionierender Kollisionserkennung! Unsere pixel_to_vec-Anweisung ist in der Lage, die Farbe des Pixels UND seinen Transparenz- (Alpha-) Wert zurückzuliefern. Ich habe eine tga-Datei mit einem Alphakanal für unser Level erstellt und sie levelmask.tga genannt:

 

aum84_workshop6

 

Wie Sie sehen habe ich die Wasserbereiche selektiert und als Alphakanal gespeichert. Es sind die weißen Bereiche im Fenster "Load From Alpha" meines Malprogramms (Paint Shop Pro). Die schwarzen Bereiche stehen für die Gebiete, die der Player begehen kann (Gras, Sand, Stein etc.). Da diese Bitmap nicht korrekt auf dem Bildschirm dargestellt werden kann (die transparenten Teile wären nicht sichtbar), verwende ich eine weitere Bitmap namens levelok.tga für das Panel, das das Level zeigt - es enthält keinerlei transparente Bereiche.

 

Und so sieht der neue Code jetzt aus:

 

function collisions_startup()

{

       wait (3); // wait until the video functions are available

       var coords1_x, coords1_y, coords2_x, coords2_y, coords3_x, coords3_y, coords4_x, coords4_y;

       var format, pixel1, pixel2, pixel3, pixel4, alpha1, alpha2, alpha3, alpha4;

       COLOR pixel1_color, pixel2_color, pixel3_color, pixel4_color;

       while (1)

       {

               // check player's feet

               coords1_x = my_player.pos_x + bmap_width(player_tga) / 2;

               coords1_y = my_player.pos_y + bmap_height(player_tga) - 2; // play with 2

               format = bmap_lock (level_tga, 0);

               pixel1 = pixel_for_bmap(level_tga, coords1_x, coords1_y);

               pixel_to_vec (pixel1_color, alpha1, format, pixel1); // store the color of the pixel1 in pixel1_color

               if (alpha1 != 0) // detected a white area of the alpha channel below player's feet?

               {

                       my_player.pos_y -= 2;

               }

               // check player's head

               coords2_x = my_player.pos_x + bmap_width(player_tga) / 2;

               coords2_y = my_player.pos_y + 20; // play with 20

               pixel2 = pixel_for_bmap(level_tga, coords2_x, coords2_y);

               pixel_to_vec (pixel2_color, alpha2, format, pixel2); // store the color of the pixel2 in pixel2_color

               if (alpha2 != 0) // detected a white area of the alpha channel above player's head?

               {

                       my_player.pos_y += 2;

               }

               // check player's left side

               coords3_x = my_player.pos_x + 2 ; // play with 2

               coords3_y = my_player.pos_y + bmap_height(player_tga) / 2;

               pixel3 = pixel_for_bmap(level_tga, coords3_x, coords3_y);

               pixel_to_vec (pixel3_color, alpha3, format, pixel3); // store the color of the pixel2 in pixel2_color

               if (alpha3 != 0) // detected a white area of the alpha channel on the left side of the player?

               {

                       my_player.pos_x += 2;

               }

               // check player's right side

               coords4_x = my_player.pos_x + bmap_width(player_tga) - 2; // play with 2

               coords4_y = my_player.pos_y + bmap_height(player_tga) / 2;

               pixel4 = pixel_for_bmap(level_tga, coords4_x, coords4_y);

               pixel_to_vec (pixel4_color, alpha4, format, pixel4); // store the color of the pixel2 in pixel2_color

               if (alpha4 != 0) // detected a white area of the alpha channel on the right side of the player?

               {

                       my_player.pos_x -= 2;

               }

               bmap_unlock (level_tga);

               wait (1);

       }

}

 

Wie Sie sehen prüfen wir nun die von pixel_to_vec zurückgelieferten alpha1-...alpha4-Werte. Stossen wir auf die weißen Bereiche des Alphakanals, bewegen wir den Player davon weg indem wir seine pos_x- und pos_y-Werte verändern. Dies bedeutet, dass unsere Level-Bitmaps irgendeine Form oder Farbe oder sonstwas haben kann - die auf Alpha basierende Bitmap-Maske kümmert sich darum.

 

Hier haben Sie es also: voll funktionsfähige 2D-Kollision, bereit in Ihr Projekt integriert zu werden. Nächtsen Monat strengen wir uns beim Erstellen riesiger, auf Kacheln basierender 2D-Welten an. Also, bleiben Sie dran!