2D Spiele

Top  Previous  Next

In diesem Monat beginnen wir unsere Reise in die Welt der 2D Spiele. Und die beste Art zu starten sind natürlich... "falsche" (fake) 2D Spiele! Das GameStudio beinhaltet eine 2D und eine 3D Engine, die gut zusammenarbeiten, auch wenn es sich um verschiedene Software handelt.

 

Viele Entwickler verwenden die 3D Engine, um gut aussehende 2D Spiele zu erstellen. Schauen Sie sich zum Beispiel das Spiel "Kino One" an, welches in dieser Ausgabe vorgestellt wird und Sie werden sehen was ich meine. Viele dieser Spiele verwenden 3D Level, wie im Bild unten.

 

aum81_workshop1

 

Dieses 2D Spiel namens ExQuest wurde für den Plug & Play Artikel von AUM 78 von mir entwickelt. Wie Sie sehen habe ich ein gewöhnliches 3D Level mit vielen schwarzen Texturen und einem roten Rand verwendet. Der 2D Effekt wird durch eine Kamera erzielt, die sich in der Vogelperspektive  befindet.

 

aum81_workshop2

 

Wie Sie sehen wirkt unsere 3D Welt nun zweidimensional. Dennoch haben wir natürlich noch Zugriff auf alle 3D Features wie polygongenaue Kollisionserkennung, 3D Explosionen, Partikel und so weiter.

 

Kopieren Sie den \2dgames Ordner in Ihr 3DGS Verzeichnis und öffnen Sie dann space1.c und starten Sie das Skript.

 

aum81_workshop3

 

Bewegen Sie die Maus und Sie werden das Schiff bewegen können. Dieses "Spiel" wird niemanden lange interessieren, aber der Code ist auch sehr simpel, also was kann man erwarten?

 

void main()

{

       vec_set(sky_color, vector(100, 1, 1));

       fps_max = 70;

       video_mode = 7;

       video_depth = 32;

       video_screen = 1;

       level_load ("space1.wmb");

       wait (3);

       camera.tilt = -90;

       camera.z = 1000;

       while (!player) {wait (1);}

       while (1)

       {

               camera.x = player.x;

               camera.y = player.y;

               camera.pan = player.pan;

               wait (1);

       }

}

 

Die main() Funktion stellt einen dunkelblauen Hintergrund ein, begrenzt die Framerate auf 70 Frames pro Sekunde und setzt die Auflösung auf 800 x 600 Pixel fest, mit 32 Bit Farbtiefe und im Vollbildmodus. Wir laden das Level und sagen der Kamera, dass sie nach unten weisen soll (tilt = -90); auf diese Weise entsteht der Eindruck eines 2D Spiels. Die Kamera wird in einer Höhe von 1000 Quants plaziert, bewegt sich mit dem Spieler und übernimmt auch dessen "pan" Winkel (dafür sorgt die "while"-Schleife).

 

action player1()

{

       player = my;

       set (my, POLYGON);

       my.ambient = 100;

       VECTOR player_speed;

       while (1)

       {

               player_speed.x = 20 * mouse_force.y * time_step;

               player_speed.y = -15 * mouse_force.x * time_step;

               player_speed.z = 0; // no need to move on the z axis

               c_move(my, player_speed, nullvector, IGNORE_PASSABLE | GLIDE);

               wait (1);

       }

}

 

Die Action des Schiffes ist sehr einfach: polygongenaue Kollisionserkennung wird aktiviert (was in einer "echten" 2D Umgebung schwer zu programmieren wäre) und setzt den Ambient Wert des Models auf 100, so dass es heller wirkt. Der Spieler bewegt sich mit einer Geschwindigkeit, die durch mouse_force.x bzw. mouse_force.y gegeben ist in x- und y-Richtung. Auf der z-Achse findet keine Bewegung statt.

 

aum81_workshop4

 

Ich habe einen blauen Hintergrund verwendet, damit Sie sehen können, wo das Level endet. Momentan kann der Spieler die Grenzen der schwarzen Zoen verlassen. Die folgende Demo verbessert den Code, berücksichtigt die Levelgrenzen und fügt weitere Features hinzu.

 

Öffnen Sie das space2.wmp Level und starten Sie dieses mit dem space2.c Skript. Folgendes sollten Sie dann sehen:

 

aum81_workshop5

 

Dieses Mal findet die Bewegung des Spielers unter Berücksichtigung von Beschleunigungs- und Reibungseffekten statt, beachtet die Levelgrenzen und wir haben außerdem einen schönen Sterneneffekt. Und all dies mit weniger als 20 neuen Codezeilen - das nenne ich produktiv!

 

void main()

{

       vec_set(sky_color, vector(1, 1, 1)); // set a dark, black background color

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

       video_mode = 7; // run in 800 x 600 pixels

       video_depth = 32; // 32 bit mode

       video_screen = 1; // start in full screen mode

       level_load ("space2.wmb"); // load the level

       wait (3); // wait until the level is loaded

       camera.tilt = -90; // make the camera look downwards

       camera.z = 1000; // and then set the proper height of the camera

       while (!player) {wait (1);} // wait until the player model is loaded

       while (1) // keep the camera above the player

       {

               camera.x = player.x;

               camera.y = player.y;

               camera.pan = player.pan; // the camera has the same pan angle with the player

               wait (1);

       }

}

 

Ich habe nur die erste Codezeile der main() Funktion geändert und die Hintergrundfarbe auf schwarz festgelegt. Auf diese Weise gehen Level und Hintergrund fließend ineinander über.

 

action player1() // attach this action to the ship model

{

       player = my; // I'm the player

       set (my, POLYGON); // use polygon-based collision detection

       my.ambient = 100; // make the ship look brighter

       VECTOR player_speed, star_pos;

       var temp_x, temp_y;

       while (1)

       {

               temp_x = 15 * mouse_force.y;

               my.skill1 = temp_x * time_step + maxv(1 - time_step * 0.1, 0) * my.skill1;

            player_speed.x = (5 + my.skill1) * time_step;

               temp_y = -10 * mouse_force.x;

               my.skill2 = temp_y * time_step + maxv(1 - time_step * 0.1, 0) * my.skill2;

            player_speed.y = my.skill2 * time_step;

               player_speed.z = 0;

               my.y = clamp(my.y, -450, 450); // limit y to -450...+450 quants

               c_move(my, player_speed, nullvector, IGNORE_PASSABLE | GLIDE);

 

               star_pos.x = my.x - 500 + random(1000);

               star_pos.y = my.y - 500 + random(1000);

               star_pos.z = 0;

               effect(starfield, 1, star_pos.x, nullvector);

               wait (1);

       }

}

 

Die Action für das Spielermodel hat sich sifgnifikant geändert; es gibt nun Beschleunigungseffekte (ändern Sie ggf. die Werte 15 und -10) und Reibung (dies ist der Wert 0.1). Das Schiff bewegt sich von allein (5 ist die Grundgeschwindigkeit) und die y-Position liegt stets zwischen -450 und +450 Quants.

 

Die letzten Codezeilen in der Schleife erzeugen in jedem Frame eine zufällige Position für einen Partikelstern nah am Spieler und wenige Quants darunter.

 

function starfield(PARTICLE *p)

{

       p.alpha = 5 + random(50);

       p.bmap = star_tga;

       p.size = 2 + random(1); // generate stars with random sizes

       p.flags |= (BRIGHT | TRANSLUCENT);

       p.event = fade_stars;

}

 

function fade_stars(PARTICLE *p)

{

       p.alpha -= 0.5 * time_step; // fade out the stars

       if (p.alpha < 0)

       {

               p.lifespan = 0;

       }

}

 

An der Partikelfunktion ist nichts Besonderes; die kleinen Partikel (Sterne) werden mit zufälliger Größe erzeugt und existieren für ein paar Frames. Ändern Sie den Wert 0.5, wenn Sie die Geschwindigkeit manipulieren wollen, mit der sie verblassen.

 

Zeit, einige weitere Features hinzuzufügen. Öffnen Sie space3.wmp und starten Sie das Level mit space3.c als Skript. Anfangs sieht es ebenso aus wie die vorige Demo, aber warten Sie einen Augenblick und der Spieler wird von Asteroiden umgeben sein. Versuchen Sie, ihnen auszuweichen - ich habe es nicht geschafft. ;-)

 

aum81_workshop6

 

Einer der größten Vorteile der Verwendung eines 3D Levels für eine 2D Anwendung ist die polygongenaue Kollisionserkennung. Sehen Sie sich das Bild unten an: Die Explosion wurde korrekt ausgelöst, kaum dass die Schiffe sich berührt haben. So etwas wäre sehr rechenintensiv in einer reinen 2D Umgebung.

 

aum81_workshop7

 

Dies war natürlich nur der Beginn der Explosion. Ich habe den Effekt einer einfachen Kollision aufgewertet, des Effektes wegen.

 

aum81_workshop8

 

Sind Sie schon gespannt auf den Code? Hier kommt er!

 

action player1() // attach this action to the ship model

{

       my.emask |= (ENABLE_IMPACT | ENABLE_ENTITY);

       my.event = destroy_player;

       var init_z;

       player = my; // I'm the player

       init_z = my.z;

       set (my, POLYGON); // use polygon-based collision detection

       my.ambient = 100; // make the ship look brighter

       VECTOR player_speed, star_pos;

       var temp_x, temp_y;

       while (1)

       {

               my.z = init_z;

               temp_x = 15 * mouse_force.y; // 15 gives the forward movement speed

               my.skill1 = temp_x * time_step + maxv(1 - time_step * 0.1, 0) * my.skill1; // 0.1 gives the friction

            player_speed.x = (5 + my.skill1) * time_step; // 5 = default movement speed

               temp_y = -10 * mouse_force.x; // 10 gives the sideway movement speed

               my.skill2 = temp_y * time_step + maxv(1 - time_step * 0.1, 0) * my.skill2; // 0.1 gives the friction

            player_speed.y = my.skill2 * time_step;

               player_speed.z = 0; // no need to move on the z axis

               my.y = clamp(my.y, -450, 450); // limit y to -450...+450 quants

               c_move(my, player_speed, nullvector, IGNORE_PASSABLE | GLIDE);

 

               star_pos.x = my.x - 500 + random(1000); // x = -500...+500

               star_pos.y = my.y - 500 + random(1000); // y = -500...+500

               star_pos.z = 0;

               effect(starfield, 1, star_pos.x, nullvector);

               wait (1);

       }

}

 

Die Action für den Spieler hat sich etwas geändert. Er reagiert nun auf Kollisionen mit anderen Entities und hat eine Event Funktion (destroy_player), die gestartet wird, wenn dies geschieht. Wir speichern die ursprüngliche Höhe des Spielers; die wird als Referenz für die anderen Entities genutzt, die mit dem Spieler kollidieren können.

 

function destroy_player()

{

       wait (1);

       ent_create (explosion_pcx, my.x, explosion_sprite); // display the explosion sprite

       wait (-0.3);

       set(my, INVISIBLE);

       wait (-2);

       ent_remove(my);

}

 

Die Funktion destroy_player() startet nach einer Kollision; sie erzeugt ein Explosionssprite, macht das Schiff des Spielers erst unsichtbar und entfernt es dann komplett.

 

function explosion_sprite()

{

       my.scale_x = 2;

       my.scale_y = my.scale_x;

       set (my, PASSABLE);

       set (my, TRANSLUCENT);

       set (my, NOFOG);

       set (my, BRIGHT);

       my.ambient = 100;

       my.red = 255;

       my.lightrange = 100;

       my.tilt = 90;

       while (my.frame < 20) // play all the animation frames

       {

               my.frame += 2 * time_step;

               my.scale_x += 0.1 * time_step; // increase the scale of the explosion every frame

               my.scale_y = my.scale_x;

               my.lightrange = 300 - random(100); // make it generate light on a range of 200...300 quants

               wait(1);

       }

       ent_remove(my); // and then remove it

}

 

Die Funktion explosion_sprite() erstellt ein helles Sprite, das seine Animationsphasen durchläuft, dabei vergrößert wird und als dynamische Lichtquelle fungiert.

 

action asteroid()

{

       while (!player) {wait (1);}

       my.z = player.z; // make sure that the player can collide with the asteroids

       my.ambient = 100;

       set(my, POLYGON);

       my.pan = random(360);

       my.tilt = random(360);

       my.roll = random(360);

       while (1)

       {

               my.pan += 2 * time_step;

               my.tilt += 2 * time_step;

               my.roll += 2 * time_step;

               wait (1);

       }

}

 

Der erste Feind ist ein Asteroid, der die gleiche Höhe wie der Spieler bekommt und sich um alle 3 Achsen dreht. Jeder Asteroid startet in einer zufälligen Orientierung, so dass sie nicht alle gleich aussehen.

 

action enemy()

{

       my.ambient = 100;

       while (!player) {wait (1);}

       my.z = player.z; // make sure that the player can collide with the enemies

       while (vec_dist(player.x, my.x) > 1000) {wait (1);} // wait until the player comes close to this enemy

       while (1)

       {

               c_move(my, vector(15 * time_step, 0, 0), nullvector, IGNORE_PASSABLE | GLIDE); // 15 = enemy movement speed

               wait (1);

       }

}

 

Die Asteroiden stehen still bis der Spieler näher als 1000 Quants kommt. Wenn dies geschieht, bewegen sie sich mit einer Geschwindigkeit von 15 * time_step auf ihn zu. Das Level ist recht klein und enthält nur wenige Feinde. Sie können es gern vergrößern, wenn Sie wollen.

 

Ich habe dem Spieler keine Waffensysteme gegeben und keine Feinde eingeführt, die schießen können, weil ich die Dinge zunächst so einfach wie möglich halten wollte. Natürlich können Sie aber ganz normalen Waffencode verwenden - das funktioniert hier sicher!