2D Spiele

Top  Previous  Next

In diesem Monat haben wir gleich drei Projekte. Wir beginne mit 2dspace1.c:

 

aum82_workshop1

 

Damit können wir nicht gerade angeben, aber irgendwie muss man ja anfangen, oder? Bewegen Sie das Schiff mit Hilfe der Pfeiltasten; wie Sie sehen, können Sie sich auch diagonal fortbewegen.

 

#include <acknex.h>

#include <default.c>

 

BMAP* ship_tga = "ship.tga";

 

PANEL* ship_pan =

{

       bmap = ship_tga;

       layer = 15;

       pos_x = 300;

       pos_y = 250;

       flags = VISIBLE;

}        

 

function main()

{

       fps_max = 60; // limit the frame rate to 60 fps

       video_mode = 8; // run at 1024 x 768 pixels

       video_screen = 1;

       vec_set(screen_color, vector(0, 0, 0)); // make the background color black

}

 

Wir beginnen mit der Definition der Bitmap, die für unser ship_pan Panel verwendet wird. Die main() Funktion begrenzt die Framerate auf 60 fps, stellt die Auflösung auf 1024 x 768 im Vollbildmodus und setzt eine schwarze Hintergrundfarbe.

 

function ship_startup()

{

       VECTOR ship_speed;

       var keys_pressed;

       ship_pan.center_x = ship_pan.size_x * 0.5; // set the rotation point at the center of the panel

       ship_pan.center_y = ship_pan.size_y * 0.5;

       while (1)

       {

               ship_speed.x = 15 * (key_cur - key_cul) * time_step;

               ship_speed.y = 15 * (key_cud - key_cuu) * time_step;

               ship_pan.pos_x += ship_speed.x;

               ship_pan.pos_y += ship_speed.y;

               keys_pressed = 0;

               if (key_cur) keys_pressed += 1;

               if (key_cul) keys_pressed += 2;

               if (key_cud) keys_pressed += 4;

               if (key_cuu) keys_pressed += 8;

 

Die ersten Codezeilen definieren einen Vektor und eine Variable und definieren dann das Rotationszentrum des Schiffes als Mitte der Bitmap. Wir verwenden eine einzige Bitmap für alle 8 Ausrichtungen und drehen das Panel mit Hilfe des Codes. Die meisten anderen 2D Engines benötigen eine separate Bitmap für jeden Winkel, wie im Bild zu sehen:

 

aum82_workshop13 aum82_workshop4

 

Es ist gut zu wissen, dass wir uns diese Mühe nicht machen müssen, nicht wahr? Zurück zu unserer Funktion: Das Schiff wird gesteuert, indem seine pos_x und pos_y Werte verändert werden, je nachdem wie lange die Pfeiltasten gedrückt werden (15 ist der Wert für die Geschwindigkeit). Ich habe eine Variable "keys_pressed" definiert; diese wird in jedem Frame auf 0 gesetzt und ändert dann ihren Wert je nachdem, welche Tasten gedrückt werden. Ich habe Zweierpotenzen verwendet, um eindeutige Tastenkombinationen zuordnen zu können. Ein Wet wie (z.B.) 9 kann nur durch Addition von 1 (key_cur) und 8 (key_cuu) erreicht werden, also kann ich problemlos feststellen, welche Tasten gedrückt wurden.

 

               if (keys_pressed == 1) // only the cursor right key is pressed?

               {

                       ship_pan.angle = 180;

               }

               if (keys_pressed == 2) // only the cursor left key is pressed?

               {

                       ship_pan.angle = 0;

               }

               if (keys_pressed == 4) // only the cursor down key is pressed?

               {

                       ship_pan.angle = 90;

               }

               if (keys_pressed == 8) // only the cursor up key is pressed?

               {

                       ship_pan.angle = 270;

               }

               if (keys_pressed == 5) // the right and down keys are pressed?

               {

                       ship_pan.angle = 135;

               }

               if (keys_pressed == 6) // the left and down keys are pressed?

               {

                       ship_pan.angle = 45;

               }

               if (keys_pressed == 9) // the right and up keys are pressed?

               {

                       ship_pan.angle = 225;

               }

               if (keys_pressed == 10) // the left and up keys are pressed?

               {

                       ship_pan.angle = 315;

               }

               wait (1);

       }

}

 

Der Rest meines Codes tut genau das: Er prüft, welche Tasten gedrückt wurden und setzt den korrekten Winkel für das Panel. Nun wollen wir dieses Beispiel verbessern. Öffnen Sie 2dspace2.c und starten Sie das Skript.

 

aum82_workshop2

 

Das sieht schon mehr nach einem Spiel aus: Ein schöner Hintergrund, das Schiff bewegt sich weicher mit Hilfe von Beschleunigung und Reibung und es gibt einen grauen Alien Container, der sich rot verfärbt und für Punktabzug sorgt, wenn der Spieler mit ihm kollidiert.

 

aum82_workshop3

 

Der Container bewegt sich mit der Zeit immer schneller, es wird also immer schwerer ihm auszuweichen. Schauen wir uns den Code an, der für ihn zuständig ist.

 

#include <acknex.h>

#include <default.c>

 

var players_score = 0;

 

BMAP* ship_tga = "ship.tga";

BMAP* enemy1_tga = "enemy1.tga";

BMAP* enemy2_tga = "enemy2.tga";

 

FONT* arial_font = "Arial#20";

 

Zunächst haben wir da einige Bitmap Definitionen für das Schiff und den grauen / roten Container. Wir definieren auch eine Schriftart für die Anzeige der Punkte.

 

PANEL* score_pan =

{

       layer = 20;

       digits (10, 10, "Score: %.f", arial_font, 1, players_score);

       flags = visible;

}

 

PANEL* background_pan =

{

       bmap = "background.tga"; // this bitmap has 1024 x 768 pixels

       layer = 10;

       pos_x = 0;

       pos_y = 0;

       flags = VISIBLE;

}        

 

PANEL* ship_pan =

{

       bmap = ship_tga;

       layer = 15; // the ship panel appears over the background panel

       pos_x = 300;

       pos_y = 250;

       flags = VISIBLE;

}        

 

PANEL* enemy1_pan =

{

       bmap = enemy1_tga;

       layer = 15;

       pos_x = 100;

       pos_y = 150;

       flags = VISIBLE;

}        

 

function main()

{

       fps_max = 60; // limit the frame rate to 60 fps

       video_mode = 8; // run at 1024 x 768 pixels

       video_screen = 1;

       vec_set(screen_color, vector(0, 0, 0)); // make the background color black

}

 

Dann haben wir ein Panel für die Punkte (ein einfaches "digits" Element), für den Hintergrund, den Spieler und den feindlichen Container. Die main() Funktion tut nichts Besonderes.

 

function ship_startup()

{

       VECTOR ship_speed;

       var hspeed = 0; // stores the horizontal speed

       var vspeed = 0; // stores the vertical speed

       var keys_pressed;

       ship_pan.center_x = ship_pan.size_x * 0.5; // set the rotation point at the center of the panel

       ship_pan.center_y = ship_pan.size_y * 0.5;

       while (1)

       {

               vec_set(ship_speed.x, accelerate (hspeed, 9 * (key_cur - key_cul), 0.3)); // 9 gives the acceleration

               vec_set(ship_speed.y, accelerate (vspeed, 9 * (key_cud - key_cuu), 0.3)); // 0.3 gives the friction

               ship_pan.pos_x += ship_speed.x;

               ship_pan.pos_y += ship_speed.y;

 

Dieses Mal verwenden wir "accelerate" um unser Schiff fort zu bewegen und berücksichtigen auch Reibungseffekte. Ändern Sie die Werte 0.3 und 9, um dieses Verhalten zu beeinflussen. Der Rest des Codes verwendet die gleiche keys_pressed Variable um dem Schiffspanel den korrekten Winkel zu geben, wie im letzten Beispiel.

 

               keys_pressed = 0;

               if (key_cur) keys_pressed += 1;

               if (key_cul) keys_pressed += 2;

               if (key_cud) keys_pressed += 4;

               if (key_cuu) keys_pressed += 8;

               if (keys_pressed == 1) // only the cursor right key is pressed?

               {

                       ship_pan.angle = 180;

               }

               if (keys_pressed == 2) // only the cursor left key is pressed?

               {

                       ship_pan.angle = 0;

               }

               if (keys_pressed == 4) // only the cursor down key is pressed?

               {

                       ship_pan.angle = 90;

               }

               if (keys_pressed == 8) // only the cursor up key is pressed?

               {

                       ship_pan.angle = 270;

               }

               if (keys_pressed == 5) // the right and down keys are pressed?

               {

                       ship_pan.angle = 135;

               }

               if (keys_pressed == 6) // the left and down keys are pressed?

               {

                       ship_pan.angle = 45;

               }

               if (keys_pressed == 9) // the right and up keys are pressed?

               {

                       ship_pan.angle = 225;

               }

               if (keys_pressed == 10) // the left and up keys are pressed?

               {

                       ship_pan.angle = 315;

               }

 

Beachten Sie bitte die "clamp" Anweisung: Sie verhindert, dass der Spieler die Grenzen des Bildschirmes verlässt. Der große "if" Teil testet, ob das Schiff mit dem Gegner zusammengestoßen ist. Falls dies der Fall ist, ändern wir das Bitmap des Containers zu dem roten und ziehen pro Frame 10 Punkte ab. Ansonsten setzen wir die Bitmap wieder auf grau.

 

               ship_pan.pos_x = clamp(ship_pan.pos_x, 0, 940);

               ship_pan.pos_y = clamp(ship_pan.pos_y, 0, 680);

 

               if ((((ship_pan.pos_x + 67 >= enemy1_pan.pos_x) && (ship_pan.pos_x + 67 <= enemy1_pan.pos_x + 37)) ||

               ((enemy1_pan.pos_x + 37 >= ship_pan.pos_x) && (enemy1_pan.pos_x + 37 <= ship_pan.pos_x + 67)))

               && (((ship_pan.pos_y + 67 >= enemy1_pan.pos_y) && (ship_pan.pos_y + 67 <= enemy1_pan.pos_y + 37)) ||

               ((enemy1_pan.pos_y + 37 >= ship_pan.pos_y) && (enemy1_pan.pos_y + 37 <= ship_pan.pos_y + 67))))

               {

                       enemy1_pan.bmap = enemy2_tga;

                       players_score -= 10; // the player and the enemy have collided here

               }

               else        

               {

                       enemy1_pan.bmap = enemy1_tga;

               }

               wait (1);

       }

}

 

Kollisionserkennung in 2D ist ein Schlüsselkonzept der 2D Programmierung, also sollten wir uns die Zeit nehmen, dies genauer unter zu betrachten. Zunächst gibt es keine magische Funktion, die testet ob zwei Objekte kollidiert sind; eine solche müssen wir selbst schreiben. Schauen wir uns ein Beispiel an.

 

aum82_workshop5

 

Falls die beiden Objekte kollidiert sind, dann überlappen sich ihre Bitmaps. Falls wir ihre Pixel einzeln testeten, ginge dabei wertvolle Rechenzeit verloren und daher verwenden wir eine deutlich schnellere Methode, die von 99% der Spielentwicklern bevorzugt wird: Bounding Box Kollisionserkennung.

 

Stellen wir uns vor, dass unsere Sprites in Wahrheit Rechtecke sind (es sind ja auch rechteckige Sprites). Das heißt allerdings auch, dass der Code auch Kollisionen registriert, die keine sind, falls der Spieler und die Gegner nicht rechteckig sind, wie im Bild zu sehen.

 

aum82_workshop6

 

Dies ist allerdings ein vergleichsweise kleines Übel und es gibt einen Weg, den Effekt zu minimieren, wie Sie in der folgenden Demo sehen werden. Bevor wir sie anschauen, überlegen wir zunächst wie wir überprüfen können, dass zwei 2D Objekte miteinander kollidiert sind. Betrachten wir zunächst die x-Achse.

 

aum82_workshop7

 

Nur der Gegner in der Mitte ist mit dem Spieler kollidiert. Dies ist der Fall, weil die pos_x seines Panels näher an der pos_x des Spielers liegt. Wir müssen auch die Größe der Bitmaps berücksichtigen, wir könnten also zum Beispiel so prüfen, ob eine Kollision stattgefunden hat:

 

((ship.pos_x + ship_width >= enemy.pos_x) && (ship.pos_x + ship_width < enemy.pos_x + enemy_width)

 

Die obere Zeile prüft, ob die rechte Seite des Spielers mit dem Gegner zusammengestoßen ist oder nicht. Wir müsen auch eine Kollision auf der linken Seite des Panels in Betracht ziehen.

 

((enemy.pos_x + enemy_width >= ship.pos_x) && (enemy.pos_x + enemy_width <= ship.pos_x + ship_width))

 

Da beides eine Kollision auslösen kann, verbinden wir diese mit dem OR Operator.

 

((ship.pos_x + ship_width >= enemy.pos_x) && (ship.pos_x + ship_width < enemy.pos_x + enemy_width) ||

((enemy.pos_x + enemy_width >= ship.pos_x) && (enemy.pos_x + enemy_width <= ship.pos_x + ship_width)))

 

Und fertig ist der Code, der Kollisionen entlang der x-Achse erkennt.

 

if ((((ship_pan.pos_x + 67 >= enemy1_pan.pos_x) && (ship_pan.pos_x + 67 <= enemy1_pan.pos_x + 37)) ||

((enemy1_pan.pos_x + 37 >= ship_pan.pos_x) && (enemy1_pan.pos_x + 37 <= ship_pan.pos_x + 67)))

 

Das ist schon alles! Ein ähnlicher Mechanismus wird für die y-Achse verwendet.

 

aum82_workshop8

 

(((ship.pos_y + ship_width >= enemy.pos_y) && (ship.pos_y + ship_width <= enemy.pos_y + enemy_width)) ||

((enemy.pos_y + enemy_width >= ship.pos_y) && (enemy.pos_y + enemy_width <= ship.pos_y + ship_width)))

 

Wir können sicher sein, dass der Gegner mit dem Spieler kollidiert ist, wenn die Statements auf beiden Achsen zugleich wahr sind und dies ist genau, was der Code der Demo prüft.

 

if ((((ship_pan.pos_x + 67 >= enemy1_pan.pos_x) && (ship_pan.pos_x + 67 <= enemy1_pan.pos_x + 37)) ||

((enemy1_pan.pos_x + 37 >= ship_pan.pos_x) && (enemy1_pan.pos_x + 37 <= ship_pan.pos_x + 67)))

&& (((ship_pan.pos_y + 67 >= enemy1_pan.pos_y) && (ship_pan.pos_y + 67 <= enemy1_pan.pos_y + 37)) ||

((enemy1_pan.pos_y + 37 >= ship_pan.pos_y) && (enemy1_pan.pos_y + 37 <= ship_pan.pos_y + 67))))

{

       // the ship and the enemy have collided here        

}

 

Die letzte Funktion der zweiten Demo kontrolliert den Gegner.

 

function enemy1_startup()

{

       var speed_x = 10;

       var speed_y = 5;

       var sense_x = 1;

       var sense_y = 1;

       while (1)

       {

               enemy1_pan.pos_x += speed_x * sense_x * time_step;

               if ((enemy1_pan.pos_x < 0) || (enemy1_pan.pos_x > 987))

               {

                       players_score += 10 + integer(random(50));

                       sense_x *= -1;

                       speed_x += 1 + random(1);

                       speed_x = minv(speed_x, 50);

               }

               enemy1_pan.pos_y += speed_y * sense_y * time_step;

               if ((enemy1_pan.pos_y < 0) || (enemy1_pan.pos_y > 730))

               {

                       sense_y *= -1;

                       speed_y += speed_x / 2 - random(speed_x);

                       speed_y = minv(speed_y, 50);

               }

               enemy1_pan.pos_x = clamp(enemy1_pan.pos_x, 0, 988);

               enemy1_pan.pos_y = clamp(enemy1_pan.pos_y, 0, 731);

               wait (1);

       }

}

 

Wir verwenden verschiedene Variablen, um die Geschwindigkeit in x und y-Richtung zu kontrollieren und Variablen für die Ausrichtung (sense_x und sense_y). Falls der Gegner den rechten oder linken Rand des Bildschirms erreicht, addieren wir eine zufällige Zahl zu den Punkten des Spielers und ändern die Richtung des Gegners, indem wir seine Geschwindigkeit mit -1 multiplizieren. Wir addieren auch +1 bis +2 zu seiner Geschwindigkeit hinzu und das bei jeder Kollision. Natürlich müssen wir die Geschwindigkeit beschränken - ich habe einen Wert von 50 gewählt.

 

Das gleiche geschieht in y-Richtung. Falls der Spieler den oberen oder unteren Rand trifft, ändert er die Richtung und erhöht die Geschwindigkeit. Die letzten beiden Codezeilen stellen sicher, dass der Gegner nicht aus den Bildschirmgrenzen bewegt wird.

 

Ich kann den Workshop in diesem Monat unmöglich beenden, ohne einen Invaders Klon zu präsentieren. Laden Sie 2dspace3.c und starten Sie das Skript.

 

aum82_workshop9

 

Es handelt sich um eine verbesserte Version mit 50 Gegnern, die Geschosse auf den Spieler feuern, einem scrollenden Hintergrund und so weiter.

 

aum82_workshop10

 

Der Spieler stirbt, wenn mindestens ein Gegner den unteren Bildschirmrand erreicht, genau wie im Original.

 

aum82_workshop11

 

Trotzdem ist das Spiel leicht zu schaffen. Wenn Sie alle Gegner besiegen, erhalten Sie 1000 Punkte.

 

aum82_workshop12

 

Hier ist der Inhalt des Skriptes.

 

#include <acknex.h>

#include <default.c>

 

var players_score = 0;

var players_health = 100;

var i = 0; // used as an index for the enemies

 

BMAP* ship_tga = "ship.tga";

BMAP* shiphit_tga = "shiphit.tga";

BMAP* pbullet_tga = "pbullet.tga";

BMAP* enemy_tga = "enemy.tga";

BMAP* explo1_tga = "explo1.tga";

BMAP* explo2_tga = "explo2.tga";

BMAP* explo3_tga = "explo3.tga";

BMAP* explo4_tga = "explo4.tga";

 

FONT* arial_font = "Arial#16b";

 

#define player_width 50

#define player_height 60

#define enemy_width 51

#define enemy_height 64

#define bullet_width 5

#define bullet_height 11

#define ebullet_width 7

#define ebullet_height 11

#define bullets_freq 0.993

 

Dieses Mal haben wir zwei Bitmaps für das Schiff des Spielers: Die normale Bitmap und die "getroffen" Bitmap. Die Gegner verwenden eine einzige Bitmap und wir haben vier verschiedene für die Explosionsframes. Ich habe die Breite und Höhe der Bitmaps für den Spieler, der Gegner und der Geschosse definiert. Die Werte für den Spieler sind kleiner als die echte Bitmapgröße - auf diese Weise ist die Kollisionserkennung genauer. Die letzte "define" Zeile gibt an, wie schnell die Gegner feuern. Ein Wert von 1 unterbindet das Feuern komplett, kleinere Werte sorgen für häufigeres Feuer. Ändern Sie den Wert 0.993 wenn Sie möchten.

 

SOUND* engage_wav = "engage.wav";

SOUND* plfire_wav = "plfire.wav";

SOUND* enemyexplo_wav = "enemyexplo.wav";

SOUND* enemyfire_wav = "enemyfire.wav";

SOUND* playerhit_wav = "playerhit.wav";

 

PANEL* enemies[50];

PANEL* enemy_bullets[50];

PANEL* temp_bullet;

 

Der obige Code definiert die Sound Effekte und einige Panel Pointer. Die erste Panel Pointer Definition generiert ein Array von 50 Panel Pointern, welche für die Gegner bestimmt sind: enemies[0] ist der Pointer zum ersten Gegner, enemies[1] enthält den Pointer des zweiten Gegners und so weiter.

 

Das zweite Array wird von den Geschossen verwendet, welche die Gegner benutzen. enemy_bullets[0] wird hierbei für Geschosse des ersten Gegners genutzt und so weiter. Die letzte Definition schließlich ist für die Geschosse des Spielers gedacht.

 

PANEL* score_pan = // displays the score

{

       layer = 30;

       digits (10, 0, "Score: %.f", arial_font, 1, players_score);

       flags = VISIBLE;

}

 

PANEL* background1_pan = // displays the black starry background

{

       bmap = "background.tga"; // this bitmap has 1024 x 768 pixels

       layer = 10;

       pos_x = 0;

       pos_y = 0;

       flags = VISIBLE;

}        

 

PANEL* background2_pan = // displays the black starry background

{

       bmap = "background.tga"; // the bitmap is identical with the one above

       layer = 10;

       pos_x = 0;

       pos_y = -769;

       flags = VISIBLE;

}        

 

PANEL* ship_pan = // displays player's ship

{

       bmap = ship_tga;

       layer = 30; // the ship panel appears over the background panel

       pos_x = 500;

       pos_y = 700;

       flags = VISIBLE;

}        

 

PANEL* health_pan = // displays the red horizontal health bar

{

       bmap = "health.tga";

       pos_x = -12;

       pos_y = 0;

       layer = 25;

       flags = VISIBLE;

}

 

Weiterhin definieren wir ein Panel für die Punktzahl, zwei Panels für den Hintergrund, ein Panel für das Schiff des Spielers und noch eines für den roten Gesundheitsbalken am oberen Bildschirmrand.

 

function main()

{

       randomize(); // generate random series of numbers at each game run

       fps_max = 60; // limit the frame rate to 60 fps

       video_mode = 8; // run at 1024 x 768 pixels

       video_screen = 1; // start in full screen mode

       vec_set(screen_color, vector(0, 0, 0)); // make the background color black

       media_loop("soundtrack.wav", NULL, 30); // start the soundtrack

       background(); // initialize the scrolling background stars

       players_ship(); // start player's function

       create_aliens(); // create the enemies

}

 

Die main() Funktion tut das Übliche und startet dann den Soundtrack, der in einer Schleife läuft und drei Funktionen aufruft, die sich um Hintergrundmusik, Geräusche des Spielers und er Gegner kümmert.

 

function background()

{

       while (1)

       {

               background1_pan.pos_y += 3 * time_step;

               background2_pan.pos_y = background1_pan.pos_y - 769;

               if (background1_pan.pos_y > 768)

               {

                       background1_pan.pos_y -= 768;

               }

               health_pan.scale_x = maxv(0.01, players_health / 100);

               wait (1);

       }

}

 

Die Funktion background() kümmert sich um den scrollenden Hintergrund. Ich habe zwei Panels verwendet, sie sich abwärts bewegen, indem ihr pos_y Wert vergrößert wird. Das zweite Panels ist immer 769 Pixel hinte dem ersten. Sobald das erste Panel verschwunden ist, wird es wieder nach oben bewegt, wodurch das Scrolling ohne "Nahtstelle" wirkt. All dies findet in einer Endlosschleife statt, in der ich zugleich das Gesundheitspanel des Spielers skalieren, abhängig von der verbleibenden Lebensenergie (players_health).

 

function players_ship()

{

       VECTOR ship_speed;

       var hspeed = 0; // stores the horizontal speed

       while (players_health > 0)

       {

               vec_set(ship_speed.x, accelerate (hspeed, 9 * (key_cur - key_cul), 0.3));

               ship_pan.pos_x += ship_speed.x;

               ship_pan.pos_x = clamp(ship_pan.pos_x, 0, 964);

               if ((key_space) && (!temp_bullet))

               {

                       snd_play(plfire_wav, 100, 0);

                       temp_bullet = pan_create("bmap = pbullet.tga; NULL;", 35);

                       // player_width is a bit smaller than normal, so we add a few more pixels to pos_x

                       temp_bullet.pos_x = ship_pan.pos_x + player_width / 2 + bullet_width / 2;

                       temp_bullet.pos_y = 695;

                       temp_bullet.flags |= VISIBLE;

               }

               if (temp_bullet)

               {

                       if (temp_bullet.pos_y > 5)

                       {

                               temp_bullet.pos_y -= 10;

                       }

                       else

                       {

                               reset(temp_bullet, VISIBLE);

                               temp_bullet = NULL;

                       }

               }

               wait (1);

       }

 

Die Funktion, die das Schiff des Spielers kontrolliert verwendet eine Schleife, die so lange läuft wie der Spieler am Leben ist und mit Beschleunigung und Reibung arbeitet. Dieses Mal bewegt sich das Schiff nur horizontal, eine einzige "clamp" Anweisung genügt also. Falls der Spieler die Leertaste drückt und der "temp_bullet" Pointer leer (NULL) ist (der Spieler hat also noch nicht gefeuert), erstellen wir ein Panel namens temp_bullet, plazieren es an der richtigen Position und machen es sichtbar.

 

Falls das Geschoss existiert und noch nicht am oberen Bildschirmrand angekommen ist (sein pos_y Wert ist größer als 5), bewegen wir es um 10 Pixel pro Frame nach oben. Sofern es oben angekommen ist, verbergen wir es und setzen den Pointer zurück, damit der Spieler bei Bedarf erneut feuern kann.

 

       snd_play(enemyexplo_wav, 100, 0); // let's play a few explosion sounds

       ship_pan.bmap = explo1_tga; // and change the ship bitmap to several explosion bitmaps

       wait (6);

       ship_pan.bmap = explo2_tga;

       wait (6);

       ship_pan.bmap = explo3_tga;

       wait (6);

       snd_play(enemyexplo_wav, 100, 0);

       ship_pan.bmap = explo4_tga;

       wait (6);

       snd_play(enemyexplo_wav, 100, 0);

       reset(ship_pan, VISIBLE); // now let's hide the ship

       ship_pan.pos_y = -1000; // and move it outside the screen (so that no collisions are triggered for it anymore)

}

 

Hier ist der Spieler tot, also ertönen einige Explosionsgeräusche und wir ersetzen die Bitmap des Schiffes mit den Explosionen. Das Schiff wird verborgen und dann 1000 Pixel über dem Bildschirm platziert. Auf diese Weise können die Gegner es nicht mehr treffen.

 

function create_aliens()

{

       var temp1 = -52;

       var temp2 = 50;

       while (i < 50)

       {

               wait (-1.5);

               enemies[i] = pan_create("bmap = enemy.tga; flags = VISIBLE;", 20);

               enemies[i].pos_x = temp1;

               enemies[i].pos_y = temp2;

               start_enemy(i);

               enemy_collisions(i);

               i += 1;

       }

}

 

Die create_aliens() Funktion erstellt die Gegner. Alle Panels werden bei x = -52 und y = 50 erstellt und zwar alle 1,5 Sekunden. Ändern Sie diesen Wert, falls Sie möchten, dass die Gegner näher beieinander oder weiter voneinander entfernt erscheinen. Unsere Funktion ruft start_enemy(i) und enemy_collisions(i) ebenfalls auf. Dadurch ruft der erste Gegner die Funktion start_enemy(0) und enemy_collisions(0), der zweite enemy_start(1) und enemy_collisions(1) auf und so weiter.

 

function start_enemy(i)

{

       var enemy_y;

       var bullet_fired = 0;

       while(is (enemies[i], VISIBLE))

       {

               while((enemies[i].pos_x < 900) && (is (enemies[i], VISIBLE)))

               {

                       enemies[i].pos_x += 1;

                       if ((enemy_bullets[i] == NULL) && (random(1) > bullets_freq))

                       {

                               create_enemy_bullets(i);

                       }

                       wait (1);

               }

               snd_play(engage_wav, 100, 0);

               enemy_y = enemies[i].pos_y;

 

Die start_enemy() Funktion enthält vier innere Schleifen und läuft so lange wie der Gegner sichtbar ist. Die erste Schleife läuft bis das Panel des Gegners 900 Pixel in x Richtung erreicht hat und addiert 1 Pixel pro Frame. Falls der Gegner noch kein Geschoss abgefeuert hat und random(1) einen hohen Wert liefert, dann schießt er und wir rufen die Funktion create_enemy_bullets(i) auf. Sie können sich sicherlich denken, dass jeder Gegner auf diese Weise seine eigene Geschossfunktion erhält.

 

Die letzten beiden Codezeilen werden ausgeführt, wenn die erste Schleife endet. Ein Geräusch ertönt, um den Spieler zu warnen, dass der Gegner sich nach unten bewegt und dann speichern wir die y Position des Gegners in der Variablen enemy_y.

 

               while((enemies[i].pos_y < enemy_y + 80) && (is (enemies[i], VISIBLE)))

               {

                       if (enemies[i].pos_y > 680)

                               players_health = 0;

                       enemies[i].pos_y += 3;

                       wait (1);

               }

 

Die zweite Schleife ist hier zu Ende. Diese bewegt den Gegner um etwa 80 Quants nach unten, wenn er noch lebt (also sichtbar ist). Falls ein Gegner den unteren Rand des Bildschirms erreicht (pos_y > 680), wird die Gesundheit des Spielers auf 0 gesetzt. Ansonsten werden pro Frame 3 Pixel zu pos_y addiert, bis die 80 Pixel zurückgelegt sind.

 

               while((enemies[i].pos_x > 50) && (is (enemies[i], VISIBLE)))

               {

                       enemies[i].pos_x -= 1;

                       if ((enemy_bullets[i] == NULL) && (random(1) > bullets_freq))

                       {

                               create_enemy_bullets(i); // then create a bullet!

                       }

                       wait (1);

               }

 

Zeit für die dritte Schleife. Diese bewegt den Gegner nach links, indem pro Frame ein Pixel von der x Position abgezogen wird, solange diese größer als 50 ist. Wieder wird ein Geschoss erzeugt, wenn es noch keines gibt und random(1) einen Wert liefert, der größer ist als bullets_freq.

 

               snd_play(engage_wav, 100, 0);

               enemy_y = enemies[i].pos_y;

               while((enemies[i].pos_y < enemy_y + 80) && (is (enemies[i], VISIBLE)))

               {

                       if (enemies[i].pos_y > 680)

                               players_health = 0;

                       enemies[i].pos_y += 3;

                       wait (1);

               }

       }

       enemies[i].pos_y = -10000; // the enemy is dead here, so move it outside the screen

}

 

Die letzte Schleife bewegt den Gegner wieder 80 Quants nach unten, indem jeweils 3 Pixel hinzuaddiert werden. Wie oben explodiert der Spieler, wenn der Gegner den Rand des Bildschirms erreicht. Die letzte Codezeile bewegt getötete Gegner 10000 Pixel nach oben, wo die Kugeln des Spielers sie nicht mehr finden können.

 

function create_enemy_bullets(i)

{

       var ebullet_speed = 5 + random(5);

       enemy_bullets[i] = pan_create("bmap = ebullet.tga; NULL;", 25);

       enemy_bullets[i].pos_x = enemies[i].pos_x + enemy_width / 2 - ebullet_width / 2;

       enemy_bullets[i].pos_y = enemies[i].pos_y + 15;

       enemy_bullets[i].flags |= VISIBLE;

       snd_play(enemyfire_wav, 90, 0); // play the enemyfire_wav sound effect

       while (enemy_bullets[i] != NULL)

       {

               if (enemy_bullets[i].pos_y < 780)

               {

                       enemy_bullets[i].pos_y += ebullet_speed;

               }

               else // reached the bottom of the screen?

               {

                       reset(enemy_bullets[i], VISIBLE);

                       break; // get out of the while loop

               }

 

Jeder Gegner startet eine eigene Instanz von create_enemy_bullets(i), wenn er ein Geschoss abfeuern will. Diese Geschosse haben eine zufällige Geschwindigkeit. Ihre Panels werden zur Laufzeit erzeugt, unterhalb der Bitmaps der jeweiligen Gegner. Ein Sound ertönt und dann läuft die Schleife so lange die Geschosse existieren.

 

Falls der untere Rand nicht erreicht wurde (pos_y ist kleiner als 780), wird pos_y um den zufälligen Wert erhöht, der in ebullet_speed gespeichert ist. Ansonsten wird das Panel verborgen und die Schleife beendet.

 

               if ((((enemy_bullets[i].pos_x + ebullet_width >= ship_pan.pos_x) && (enemy_bullets[i].pos_x + ebullet_width <= ship_pan.pos_x + player_width)) ||

                       ((ship_pan.pos_x + player_width >= enemy_bullets[i].pos_x) && (ship_pan.pos_x + player_width <= enemy_bullets[i].pos_x + ebullet_width)))

                       && (((enemy_bullets[i].pos_y + ebullet_height >= ship_pan.pos_y) && (enemy_bullets[i].pos_y + ebullet_height <= ship_pan.pos_y + player_height)) ||

                       ((ship_pan.pos_y + player_height >= enemy_bullets[i].pos_y) && (ship_pan.pos_y + player_height <= enemy_bullets[i].pos_y + ebullet_height))))

               {

                       players_health -= 10;

                       snd_play(playerhit_wav, 90, 0);

                       ship_pan.bmap = shiphit_tga;

                       reset(enemy_bullets[i], VISIBLE);

                       wait (3);

                       ship_pan.bmap = ship_tga;

                       break; // get out of the while loop

               }

               wait (1);

       }

       enemy_bullets[i] = NULL;

}

 

Der "if" Teil prüft ob der Spieler mit dem Geschoss kollidiert ist oder nicht. Falls dies der Fall ist, wird die Gesundheit des Spielers um 10 reduziert, das "playerhit_wav" Geräusch ertönt und das Bitmap des Spielers wird für 3 Frames durch "shiphit_tga" ersetzt. Das Geschoss verschwindet (es hat seine Schuldigkeit getan), das Bitmap des Spielers wird wieder hergestellt und wir verlassen die Schleife. Die letzte Codezeile setzt den Pointer auf das Geschoss zurück auf NULL, damit der Gegner erneut feuern kann.

 

function enemy_collisions(i)

{

       while (1)

       {

               if (temp_bullet)

               {

                       if (is (enemies[i], INVISIBLE)) {break;}

                       if ((((temp_bullet.pos_x + bullet_width >= enemies[i].pos_x) && (temp_bullet.pos_x + bullet_width <= enemies[i].pos_x + enemy_width)) ||

                       ((enemies[i].pos_x + enemy_width >= temp_bullet.pos_x) && (enemies[i].pos_x + enemy_width <= temp_bullet.pos_x + bullet_width)))

                       && (((temp_bullet.pos_y + bullet_height >= enemies[i].pos_y) && (temp_bullet.pos_y + bullet_height <= enemies[i].pos_y + enemy_height)) ||

                       ((enemies[i].pos_y + enemy_height >= temp_bullet.pos_y) && (enemies[i].pos_y + enemy_height <= temp_bullet.pos_y + bullet_height))))

                       {

                               players_score += 20; // add 20 points to player's score

                               reset(temp_bullet, VISIBLE); // let's hide player's bullet

                               temp_bullet = NULL; // and allow the player to fire a new bullet

                               snd_play(enemyexplo_wav, 100, 0); // play the enemyexplo_wav sound effect

                               enemies[i].bmap = explo1_tga; // change the enemy bitmap to several explosion frames

                               wait (3);

                               enemies[i].bmap = explo2_tga;

                               wait (3);

                               enemies[i].bmap = explo3_tga;

                               wait (3);

                               enemies[i].bmap = explo4_tga;

                               wait (3);

                               reset(enemies[i], VISIBLE); // and then hide the enemy panel

                       }

               }

               wait (1);

       }

}

 

Die letzte Funktion prüft ob das Geschoss des Spielers mit dem Gegner kollidiert ist. Falls ein Geschoss des Spielers existiert und der Gegner sichtbar (also nicht tot) ist, dann erhöhen wir die Punktzahl des Spielers um 20, verbergen das Geschosspanel, setzen den Pointer zurück, wodurch der Spieler erneut feuern kann, spielen den enemyexplo_wav Sound ab und zeigen schließlich die vier Explosionsbitmaps, wodurch das Bitmap des Gegners ersetzt wird. Die letzte Codezeile verbirgt das Panel des Gegners.

 

Der Workshop war länger als erwartet, aber nun sind Sie bereit, Ihre eigenen 2D Weltraumshooter zu schreiben. Im nächsten Monat werden wir uns anderen Aspekten der 2D Programmierung widmen, also bleiben Sie uns treu!