|
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.
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.
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.
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.
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:
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. ;-)
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.
Dies war natürlich nur der Beginn der Explosion. Ich habe den Effekt einer einfachen Kollision aufgewertet, des Effektes wegen.
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!
|