|
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?
Starten wir das erste Beispiel (2dcollision1.c) - hierbei handelt es sich um das einfachste Demo, das den fortgeschrittenen Kollisionserkennungsmechanismus verwendet.
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:
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:
Ü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.
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:
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!
|