Planet Survivors – Teil 2

Diesen Monat werden wir das erste Level spielen können. Schauen wir uns erst einige Screenshots an:

Im ersten Level sind noch keine Feinde, aber eine Menge Asteroiden, die für Adrenalin sorgen! Das main.wdl Skript hat einige geringe Änderungen erfahren; aber der interessante Teil des Codes ist in level1.wdl. Schauen wir uns einige Panels und einen Text an:

panel shield_pan
{
bmap = shield_tga;
layer = 10;
pos_x = 732;
pos_y = 555;
digits = 7, 15, 3, figures_font, 1, player.shield;
flags = overlay, refresh, visible, transparent;
}

panel ammo_pan
{
bmap = ammo_tga;
layer = 10;
pos_x = 0;
pos_y = 555;
digits = 12, 15, 3, figures_font, 1, player.ammo;
flags = overlay, refresh, visible, transparent;
}

panel crosshair_pan
{
bmap = pointer2_tga;
layer = 20;
pos_x = 368;
pos_y = 282;
flags = transparent, overlay, refresh;
}

panel score_pan
{
bmap = score_tga;
layer = 10;
pos_x = 300;
pos_y = 5;
flags = overlay, refresh, visible;
}

text score_txt
{
pos_x = 380;
pos_y = 5;
layer = 10;
font = score_font;
string = score_str;
flags = visible;
}

Diese Panels zeigen das Schild (in der unteren rechten Ecke), die Munition (in der unteren linken Ecke), das Fadenkreuz (mittig auf einem 800x600 Bildschirm) und eine Bitmap am oberen Bildschirmrand, die “Score:” darstellt an. Der Text wird die Punktzahl anzeigen (ja, die Zahl wird vorher in einen String konvertiert).

Der Spieler fliegt mit hoher Geschwindigkeit über der Planetenoberfläche, richtig? Falsch! Der Spieler bewegt sich überhaupt nicht (außer in der Zwischensequenz) und der “Planet” unter ihm ist ein Model, das seinen “v” Parameter in einer Schleife ändert:

action planet_level1 // attached to the "planet" (a flat mdl, really)
{
my.passable = on;
fog_color = 1;
camera.clip_far = 100000; // works only with A6, delete this line if you own A5
// clip_range = 100000; // remove the comment if you own A5
camera.z = -100;
camera.fog_start = 50;
camera.fog_end = 20000;
while (ready_to_play == 0)
{
my.v += 3 * time;
wait (1);
}
my.z -= 5000;
my.ambient -= 30;
while (player_is_dead == 0)
{
my.v += 2 * time;
wait (1);
}
}

Wie gesagt, der Planet ist einfach ein Model und das bewegt seine Textur solange das Schiff des Spielers nicht zerstört wird. Die Modeltextur paßt aneinander, daher wirkt das “Level” endlos; es kann gar nicht enden, weil das Schiff des Spielers still steht! Erzählen Sie das aber nicht Ihren Kunden! :)

Ich habe meine Geheimwaffe aktiviert – orangen Nebel mit RGB = 200, 121, 50 und habe schöne Werte für camera.fog_start und camera.fog_end ausgewählt. Ich werde mich übrigens bemühen, dass dieses Projekt sowohl mit A5 als auch mit A6 läuft, aber Sie müssen einige Werte ändern (z.B. camera.clip_far durch clip_range ersetzen, verschiedene Reichweiten für Nebel einstellen, etc.), wenn Sie A5 besitzen. Sehen Sie sich die Screenshots gut an, um zu sehen wie das Level aussehen sollte. Ready_to_play ist eine Variable, die auf 1 gesetzt wird, wenn der Spieler das Schiff kontrolliert (direkt nach der Sequenz am Anfang). Wie Sie sehen wird der “Planet” 5000 Quants nach unten bewegt, wenn das Spiel beginnt und etwas abgedunkelt. Die Textur Bewegung geht mit einer geringeren Geschwindigkeit weiter, solange der Spieler noch lebt.

Ich habe ein ähnliches Model für den Himmel benutzt und bewege seine Textur mit “u”:

action sky_level1
{
my.passable = on;
my.flare = on;
my.ambient = -20;
while (ready_to_play == 0)
{
my.u -= 0.3 * time;
wait (1);
}
ent_remove (my);
}

Der Himmel wird entfernt sobald die Sequenz endet. Die schrecklichste Action, die Sie jemals gesehen haben, sehen Sie hier:

action player_level1
{
player = my;
my.ammo = 200;
my.shield = 100;
my.metal = on;
my.ambient = 50;
my.transparent = on;
my.alpha = 100;
my.enable_impact = on;
my.enable_entity = on;
my.event = player_was_hit;
vec_set(temp, player.x);
vec_sub(temp, camera.x);
vec_to_angle(camera.pan, temp);
track_handle = media_play ("track1.wav", null, 50);
fade_factor = 1000;
my.engine = on;
plasma_jet(65, 63);

Diese Action wird dem Spieler zugewiesen; sie setzt die Munition fest (ammo ist ein anderer Name für skill21) und das Schild (shield = skill22). Das Schiff ist transparent (wir sehen später warum), aber der Alphawert ist im Moment noch 100 (also nicht durchlässig). Das Schiff reagiert auf Zusammenstöße mit anderen Entities (die Asteroiden); das player_was_hit Event wird aufgerufen, wenn es mit einer Entity kollidiert. Mit der vec_to_angle Anweisung richten wir die Kamera von Anfang an auf das Schiff aus, um Fehler zu vermeiden und lassen dann track1.wav ertönen. Ich habe eine Wave Datei genommen, weil nicht alle Versionen mit “media_play” auch mp3 oder ogg Dateien abspielen können, aber wenn möglich sollten Sie die komprimierten Audio Formate wählen.

Erinnern Sie sich an die Partikelfunktion für die Motoren des Schiffes im Menü? Ich habe dieser Funktion einen einfachen fade_factor gegeben und kann nun die Lebenszeit dieser Partikel kontrollieren, indem ich den fade_factor einstelle. Ich habe einen fade_factor von 1000 gewählt, weil die Partikel schnell verblassen sollen; das Schiff ist zu schnell für langsame Partikel. Schließlich setze ich das “Engine” Flag (flag1) und starte die Funktion, die für die Plasmastrahlen an den entsprechenden Vertizes sorgt (plasma_jet() aus der main.wdl, leicht modifiziert).

while (my.x < 3000)
{
my.roll += 1.5 * time;
ent_playsound (my, engine1_wav, 300);
my.x += 52 * time;
vec_set(temp, my.x);
vec_sub(temp, camera.x);
vec_to_angle(camera.pan, temp);
wait (1);
}
ready_to_play = 1;
my.x = -10000;
my.y = 0;
my.z = -100;
my.roll = 0;

crosshair_pan.visible = on;
camera.x = -10230;
camera.y = 0;
camera.z = -50;
camera.pan = 0;
camera.tilt = 0;
camera.fog_start = 15000;
camera.fog_end = 50000;

fade_factor = 100;
snd_play (engage_wav, 70, 0);

Der Code oben startet die Zwischensequent; das Schiff bewegt sich von seiner Startposition (x = - 10.000) bis zu x = 3.000, ändert dabei seinen Roll Winkel und hat Motorengeräusche. Die Kamera folgt dem Schiff die ganze Zeit (wieder mit Hilfe einer vec_to_angle Anweisung). Sobald der Spieler das x = 3.000 Quants Limit erreicht, wird ready_to_play auf 1 gesetzt (der Himmel verschwindet) und das Schiff wird wieder auf –10.000 Quants zurückgesetzt, weil unser “Planet” nicht so groß ist und niemand den Unterschied bemerkt. Das Fadenkreuz wird angezeigt, die Kamera wird auf eine gute Position gesetzt und die Reichweiten für den Nebel werden variiert. Schließlich setzen wir den fade_factor auf 100, weil der Spieler nun still steht (obwohl er aufgrund des Planeten “v” Shiftes so aussieht, als flöge er) und lassen engage.wav ertönen, um den Spieler wissen zu lassen, dass er nun fliegen kann.

while (my.shield > 0)
{
camera.tilt += 10 * mouse_force.y * time;
camera.pan -= 10 * mouse_force.x * time;
if ((camera.pan > 30) && (camera.pan < 330)) // don't allow the player to blow our cover (if it looks back)
{
if (camera.pan < 180)
{
camera.pan = 30;
}
else
{
camera.pan = 330;
}
}
if (camera.tilt < -35)
{
camera.tilt = -35;
}
if (camera.tilt > 25)
{
camera.tilt = 25;
}
my.pan = camera.pan;
my.tilt = camera.tilt;

Solange das Schiff nicht zerstört ist (shield > 0), ändert die Kamera ihre Winkel, wenn die Maus bewegt wird. Der Pan kann dabei von –30 bis 30 Grad verändert werden, während Tilt von –35 bis 25 Grad reicht. Das ist, damit der Spieler nicht nach hinten schauen und die Grenzen des Levels sehen kann. Das Schiff übernimmt Pan und Tilt von der Kamera.

vec_set (temp_var, my.pos); // copy player's position to temp_var
vec_to_screen(temp_var, camera); // now temp_var contains the 2D position (x, y) of the ship
if ((abs(temp_var.x - 400) < 50) || (abs(temp_var.y - 300) < 40)) // the crosshair and the ship tend to overlap?
{
my.alpha = (abs(temp_var.x - 400) + abs(temp_var.y - 300));
}
else
{
if (my.alpha < 99)
{
my.alpha += 0.5 * time;
}
}

if ((mouse_left == 1) && (player.ammo > 0))
{
fire_rocket();
}

Die obigen Zeilen rechnen die 3D Position des Schiffes auf eine 2D Bildschirmposition um und stellen die Transparenz des schiffes ein, wenn das Fadenkreuz das Schiff überlappt; schauen Sie sich die Bilder an, um zu sehen wie es funktioniert.

Diese Methode erlaubt es dem Spieler einen Angreifer / Asteroiden zu sehen, selbst wenn dieser direkt vor dem Schiff ist. Die letzten Codezeilen rufen die Funktion fire_rocket() auf, wenn der Spieler die linke Maustaste drückt und noch Munition hat (ammo > 0).

if ((key_z == on) && (my.y < 100))
{
my.y += 10 * time;
if (my.roll < 30)
{
my.roll += 3 * time;
}
}
if ((key_x == on) && (my.y > -100))
{
my.y -= 10 * time;
if (my.roll > -30)
{
my.roll -= 3 * time;
}
}
if (key_z + key_x == 0)
{
if (my.roll > 1)
{
my.roll -= 1 * time;
}
if (my.roll < -1)
{
my.roll += 1 * time;
}
}
wait (1);
}

Wußten Sie, dass der Spieler sich mit den “Z” (im Deutschen: besser “Y” nehmen!) und “X” Tasten? Dies ist seine einzige Chance, den Feinden auszuweichen; beachten Sie, dass diese Bewegung auch in einem –100 bis +100 Bereich eingeschränkt ist. Das Schiff ändert seinen Roll Winkel wenn es so bewegt wird und stellt ihn langsam zurück, wenn key_z + key_x == 0 (keine der Tasten ist gedrückt).

crosshair_pan.visible = off;
camera.pan = 0;
camera.tilt = -10;
while (my.z > -1990)
{
if (camera.tilt > -30) {camera.tilt -= 0.2 * time;}
my.x += 30 * time;
my.z -= 20 * time;
my.roll += 10 * time;
wait (1);
}
player_is_dead = 1;
my.skill10 = 10;
ent_create (explo13_pcx, my.pos, explode_me);
my.tilt = -50; // choose a weird position for the ship
ent_playsound (my, hit_wav, 5000);
while (1)
{
temp.x = 3 - random(6);
temp.y = 3 - random(6);
temp.z = 4 + random(4);
effect(particle_smoke, 10, my.pos, temp);
wait (1);
}
}

Der letzte Codeteil läuft, wenn der Spieler tot ist (shield <= 0). Wir wählen einen guten Kamerawinkel und können zusehen, wie das Schiff trudelnd an Höhe verliert, während die Kamera langsam weiter nach unten schwenkt, um das Schiff im Auge zu behalten. Wir setzen player_is_dead auf 1, was die Texturbewegung stoppt und erzeugen ein großes Explosionssprite mit der explode_me Funktion. My.tilt = -50 stellt eine seltsame Schiffsposition ein, wir hören die Explosion und lassen Rauch in einer Schleife aufsteigen. Ruhe in Frieden, mutiger Spieler... *schnief*.

Nun, dieses Level enthält keine normalen Gegner, sondern hunderte von Asteroiden. Schauen wir uns an, wie sie funktionieren:

action asteroid_maker
{
var temp_var;
my.passable = on;
my.invisible = on;
while (1)
{
temp.x = my.x;
temp.y = 5000 - random(10000); // -5,000 to 5,000
temp.z = 1000 - random(2000);
if (ready_to_play == 0) // game not started yet?
{
if ((temp.y > -500) && (temp.y < 500)) // the asteroid is too close to the player during the cut scene?
{
temp.y = 500; // then avoid the player until it is able to control the ship
}
}
temp_var = random(1);
if (temp_var < 0.33)
{
ent_create (asteroid1_mdl, temp, move_asteroid);
}
if ((temp_var >= 0.33) && (temp_var < 0.66))
{
ent_create (asteroid2_mdl, temp, move_asteroid);
}
if (temp_var > 0.66)
{
ent_create (asteroid3_mdl, temp, move_asteroid);
}
wait (2);
}
}

Das Model mit dieser Action ist passierbar und unsichtbar. Sein x-Wert in WED ist 10.000, also werden die Asteroiden dort generiert mit zufälligen Werten in y-Richtung (-5000 bis 5000) und z-Richtung (-1000 bis 1000). Falls ready_to_play == 0 (während der Zwischensequenz), werden die Asteroiden, die mit dem Spieler zusammenstoßen könnten auf y = 500 bewegt, wo sie das Schiff nicht beschädigen können. Wir erzeugen eine zufällige Zahl und speichern diese in temp_var; dies wählt eines von 3 verschiedenen Asteroiden-Models. Die Funktion, die für die Bewegung der Asteroiden zuständig ist heißt move_asteroid:

function move_asteroid()
{
var meteor_angle;
var meteor_speed;
my.enable_impact = on;
my.enable_entity = on;
my.event = destroy_asteroid;
my.scale_x = 5 + random(10);
my.scale_y = my.scale_x;
my.scale_z = my.scale_x;
my.skill10 = my.scale_x; // store the scale in skill10 - we will use it to adjust the size of the explosion later
meteor_angle.x = 3 - random(6); // random pan speed
meteor_angle.y = 3 - random(6); // random tilt speed
meteor_angle.z = 3 - random(6); // random roll speed
meteor_speed.x = (-100 - random(80)) * time;
meteor_speed.y = 0;
meteor_speed.z = 0;

Die Asteroiden reagieren auf Zusammenprall mit anderen Entities (andere Asteroiden, das Schiff des Spielers oder die Raketen des Spielers) und starten ihre destroy_asteroid Event Funktion, wenn dies geschieht. Wir geben den Asteroiden eine zufällige Skalierung und speichern diese in skill10; später werden wir damit das Explosionssprite skalieren, weil ein kleiner Asteroid eine kleinere Explosion erzeugen sollte, wohingegen große Asteroiden in riesigen Feuerbällen vergehen sollten. Die Asteroiden drehen sich mit zufälligen Geschwindigkeiten für Pan, Tilt und Roll und benutzen eine negative Geschwindigkeit von –100 bis –180 Quants / Frame.

while (my.x > -21000)
{
my.pan += meteor_angle.x * time;
my.tilt += meteor_angle.y * time;
my.roll += meteor_angle.z * time;
move_mode = ignore_you + ignore_passable;
ent_move (nullvector, meteor_speed);
wait (1);
}
ent_remove (my);
}

Die Asteroiden bewegen sich auf das Schiff des Spielers zu, bis ihr x-Wert geringer ist als –21.000 Quants (falls sie bis dahin kommen) und drehen sich dabei. Sobald diese Grenze überschritten ist, werden sie entfernt. Schauen wir uns nun ihre Event Funktion an:

function destroy_asteroid()
{
if (you.skill40 == 1234)
{
score_value += int(1000 / (my.skill10 + 1));
}
my.x = -30000;
wait (1);
my.event = null;
ent_create (explo13_pcx, my.pos, explode_me);
my.passable = on;
my.invisible = on;
ent_playsound (my, hit_wav, 1000);
sleep (2);
ent_remove(my);
}

Falls ein Asteroid von einer Rakete des Spielers getroffen wird (skill40 == 1234, das werden wir unten sehen), erhöhen wir die Punktzahl, wobei kleinere Asteroiden mehr Punkte geben, weil sie nicht so leicht zu treffen sind; die Größe stand in skill10, richtig? Der getroffene Asteroid (man braucht immer 2 für einen Tango) wird zum x-Wert von –30.000 Quants bewegt, wo er auf einen anderen Asteroiden wartet, der mit ihm zusammenprallt; auf diese Weise gibt es die netten Explosionen am Anfang der Sequenz. Die Event Funktion wird auf Null gesetzt, das Explosionssprite wird erzeugt und ein entsprechendes Geräusch ertönt. Wir halten des Asteroiden noch 2 Sekunden im Level (obwohl er nicht mehr zu sehen und passierbar ist), um das Geräusch nicht zu früh abzuwürgen. Was kommt nun?

function explode_me()
{
my.oriented = on;
my.flare = on;
my.bright = on;
my.ambient = random(100);
my.passable = on;
my.roll = random(360);
my.skill30 = you.skill10;
while (my.frame < 14)
{
if (my.scale_x < my.skill30)
{
my.scale_x += 5 * time;
my.scale_y = my.scale_x;
}
my.frame += 1 * time;

vec_set (temp.x, camera.x);
vec_sub (temp.x, my.x);
vec_to_angle (my.pan, temp); // turn towards the player to make sure that the explo looks great

wait (1);
}
ent_remove (me);
}

Diese Funktion ist für das Explosionssprite zuständig. Dieses ist orientiert und passierbar mit einem zufälligen Ambient Wert. Darüber hinaus hat es einen zufälligen Roll Winkel, weil wir mit einem einzelnen Sprite soviele verschiedene Explosionen wie möglich simulieren wollen, um an Grafikspeicher zu sparen. Wir sichern you.skill10 (die Größe des Asteroiden) im skill30 des Explosionssprites und spielen die Animationsphasen ab, während die Größe erhöht wird bis sie zum Asteroiden paßt. Eine weitere vec_to_angle Anweisung dreht das Sprite zur Kamera, damit der Spieler die Explosion in voller Pracht genießen kann. Das Sprite wird entfernt, sobald das letzte Frame der Animation gespielt wurde.

Schauen wir uns nun an, was geschieht wenn der Spieler die linke Maustaste drückt.

function fire_rocket()
{
proc_kill(4);
while (mouse_left == 1) {wait (1);}
player.ammo -= 1;
snd_play (rocket_wav, 50, 0);
ent_create (rocket_mdl, player.pos, move_rocket);
}

Die erste Codezeile stellt sicher, dass nur eine Instanz der Funktion läuft; darüberhinaus warten wir darauf, dass die linke Maustaste losgelassen wird. Die Munition wird um einen verringert, ein Geräusch ertönt und ein rocket.mdl Model wird an der Position des Spielers erstellt und bekommt die move_rocket Funktion:

function move_rocket()
{
my.enable_entity = on;
my.enable_block = on;
my.event = remove_rocket;
my.pan = camera.pan;
my.tilt = camera.tilt;
my.skill40 = 1234;
my.skill10 = 3; // default explosion scale for the rocket
my.skill20 = 0;
while (my.skill20 < 500)
{
my.skill1 = 500 * time;
my.skill2 = 0;
my.skill3 = 0;
my.skill20 += 1 * time;
ent_move (my.skill1, nullvector);

t emp.x = my.x;
temp.y = my.y;
temp.z = my.z;
effect (particle_trail, 1, temp, normal);
wait (1);
}
remove_rocket();
}

Die Rakete reagiert auf Entities und Level Blocks; ihre Event Funktion heißt remove_rocket. Die Rakete übernimmt Pan und Tilt der Kamera und wird als eine Rakete identifiziert, weil wir ihren Skill40 auf einen seltsamen Wert (1234) setzen. Wir setzen skill10 (Größe eines etwaigen Explosionssprites) auf 3 und nutzen skill20, um die maximale Distanz einzustellen, die eine Rakete fliegen kann. Die Geschwindigkeit der Rakete ist in Skill1 gegeben und der Ursprung des Partikelstrahls ist durch ihre Koordinaten festgelegt.

Die Rakete ist so schnell, dass wir die particle_trail Funktion brauchen, um überhaupt etwas zu sehen; schauen Sie sich den roten Laser im Bild unten an.

function particle_trail()
{
temp.x = 100;
temp.y = 0;
temp.z = 0;
vec_rotate (temp, you.pan);
vec_add (my.vel_x, temp);
my.alpha = 70 + random(30);
my.bmap = ptrail_tga;
my.size = 7;
my.flare = on;
my.bright = on;
my.beam = on; // comment this line if your engine can't use "beam"
my.move = on;
my.lifespan = 30;
my.function = fade_particles;
}

Dies ist die Partikel Funktion, die wir brauchen. Sie hat eine große x-Geschwindigkeit, weil wir “beam” verwenden wollen (löschen Sie die Zeile, wenn Ihre Engine Version es nicht unterstützt). Die Partikel übernehmen die Orientierung der Rakete und werden von der Funktion namens “fade_particles” gesteuert:

function fade_particles()
{
my.alpha -= 100 * time;
if (my.alpha < 0) {my.lifespan = 0;}
}

Die Partikel werden schnell entfernt; es könnte eine gute Idee sein, die “100” zu verringern, wenn Sie keinen Zugriff auf “beam” haben; das könnte den Effekt verbessern. Wir kommen nun ans Ende:

function remove_rocket()
{
my.event = null;
ent_playsound (my, destroyed_wav, 1000);
ent_create (explo13_pcx, my.pos, explode_me);
my.invisible = on;
sleep (2);
ent_remove(me);
}

Die Funktion oben ist das Event für unsere Rakete; sie macht die Rakete unempfänglich gegenüber anderen Events, läßt das destroyed.wav Geräusch ertönen, erzeugt ein Explosionssprite und verbirgt die Rakete für 2 weitere Sekunden, damit das Geräusch noch zu Ende läuft. Die letzte Codezeile entfernt die Rakete.

Schauen wir uns an, was geschieht, wenn der Spieler und ein Asteroid kollidieren:

function player_was_hit()
{
my.event = null; // stop reacting to events for now
my.shield -= you.skill10 * 1.5; // bigger asteroids cause greater damage
my.skill33 = 0;
snd_play (hit_wav, 100, 0);
if (player.shield > 0) // the game isn't over yet?
{
while (my.skill33 < 10)
{
camera.roll = 2 - random(4); // shake the player a bit
my.skill33 += 1 * time;
wait (1);
}
player.roll = 0; // then restore player's roll
}
my.event = player_was_hit;
}

Der Spieler reagiert eine Weile nicht mehr auf Events; das “shield” wird verringert, je nach Größe (skill10) des Asteroiden. Wir setzen skill3 zurück und verwenden es als einen Zähler, während der Spieler durch einen zufälligen Roll Winkel (-2 bis 2) in jedem Frame etwas durchgeschüttelt wird, ehe wir den Roll Winkel wieder zurücksetzen. Das geschieht nur, wenn shield noch größer ist als 0; das Schiff wird nicht geschüttelt, wenn der Spieler abstürzt. Die letzte Codezeile sorgt wieder für Reaktion auf Impact Events.

Falls player.shield <= 0 stürzt das Schiff ab und explodiert; sehen wir uns die Funktion der Rauchpartikel an:

function particle_smoke()
{
my.alpha = 40 + random(50);
my.bmap = smoke_tga;
my.bright = on;
my.flare = on;
my.move = on;
my.beam = on; // if your engine supports it
my.size = 30;
my.function = particle_fade;
}

Dazu gibt es nicht viel zu sagen; vergessen Sie nicht, die “beam” Zeile auszukommentieren, wenn Ihre Version darüber nicht verfügt. Oh und wir nutzen dieselbe particle_fade Funktion, die auch schon von den Plasma Jets genutzt wurde. Sehen wir uns nun die letzte Funktion an:

starter compute_score()
{
while (1)
{
str_for_num(temp_str, score_value); // convert the value to string
if (str_len(temp_str) == 1)
{
str_cpy(score_str, "0000000");
str_cat(score_str, temp_str);
}
if (str_len(temp_str) == 2)
{
str_cpy(score_str, "000000");
str_cat(score_str, temp_str);
}
if (str_len(temp_str) == 3)
{
str_cpy(score_str, "00000");
str_cat(score_str, temp_str);
}
if (str_len(temp_str) == 4)
{
str_cpy(score_str, "0000");
str_cat(score_str, temp_str);
}
if (str_len(temp_str) == 5)
{
str_cpy(score_str, "000");
str_cat(score_str, temp_str);
}
if (str_len(temp_str) == 6)
{
str_cpy(score_str, "00");
str_cat(score_str, temp_str);
}
if (str_len(temp_str) == 7)
{
str_cpy(score_str, "0");
str_cat(score_str, temp_str);
}
if (str_len(temp_str) == 8)
{
str_cat(score_str, temp_str);
}
wait (1);
}
}

Diese Starter Funktion ist recht lang, aber ich wollte die Punktzahl anzeigen wie früher; eine Punktzahl von 1500 wird als “00001500” angezeigt, anstelle von “1500”. Nun wissen Sie auch, warum wir einen String und keinen Digit dafür benötigen. Wir wandeln den Wert (score_value) in einen String um, ermitteln seine Länge und fügen links die erforderliche Anzahl an Nullen ein. Das ist alles!

Ich denke, dass Planet Survivors Potentiel hat. Im Moment sehe ich es als Kombination aus Action / Logikspiel und Rollenspiel, aber wer weiß, was in Zukunft noch geschieht? Ich möchte Nemisis für seine großartigen Ideen und Unterstützung danken, das Gameplay wäre schlechter ohne seine Hilfe. Bis nächsten Monat dann!

Granaten

Viele haben diese Frage auf dem Forum festellt: wo kann ich einen guten Granatwerfercode finden? Suchen Sie nicht weiter, dieser Artikel wird Code für Granaten beinhalten, die beim Aufprall explodieren und alle Entities in Reichweite zerstören. Möchten Sie einige Kettenexplosionen sehen? Ich habe extra für Sie ein Demo Level erstellt!

Beginnen wir mit einer Starter Funktion:

starter init_grenades()
{
while (player == null) {wait (1);}
while (1)
{
if (mouse_left == 1)
{
ent_create (grenade_mdl, player.x, move_grenade);
while (mouse_left == 1) {wait (1);} // wait until the LMB is released
}
sleep (0.1);
}
}

Die Funktion wartet bis der Spieler erstellt wurde und startet dann eine Endlosschleife. Jedes Mal wenn die linke Maustaste gedrückt wird, erzeugt dies eine Granate, die sich mit Hilfe der Funktion move_grenade bewegt:

function move_grenade()
{
my.passable = on;
my.pan = camera.pan;
my.tilt = camera.tilt;
my.enable_entity = on;
my.enable_impact = on;
my.enable_block = on;
my.event = grenade_event;
while (my != null)
{
if (vec_dist (my.x, player.x) > 50) {my.passable = off;}
my.skill1 = 50 * time; // movement speed
my.skill2 = 0; // speed on y
if (my.skill40 < 100) // covered at least 100 quants?
{
my.skill3 = 0; // if not, don't apply gravity yet
}
else // moved away from the player
{
my.skill3 = -5 * (my.skill40 / 100) * time;
}
move_mode = ignore_you + ignore_passable;
my.skill30 = ent_move (my.skill1, nullvector); // get the distance that was covered this frame in skill30
my.skill40 += my.skill30; // store the distance covered so far in skill40
wait (1);
}
}

Diese Funktion ähnelt derjenigen für den Leuchtkörper in der Anfängersektion; die Granate reagiert auf nicht passierbare Entites und Level Blocks und startet ihre “grenade_event” Funktion, wenn eine dieser Kollisionen stattfindet. Die Granate wird unpassierbar, sobald ihr Abstand zum Spieler größer als 50 Quants ist. Die Schwerkraft wird einbezogen, sobald mehr als 100 Quants zurückgelegt wurden, wie im Bild unten; ändern Sie die “-5”, wenn Sie die Schwerkraft anpassen wollen. Ach ja, skill40 enthält die Gesamtstrecke, die von der Granate zurückgelegt wurde und skill30 die Distanz pro Frame.

Was geschieht, wenn die Granate auf etwas trifft? Sehen wir uns die Event Funktion an:

function grenade_event()
{
vec_set (temp, my.pos);
temp.z += 10; // create the explosion sprite 10 quants above the ground
ent_create(explosion_pcx, temp, animated_explosion);
ent_remove (me);
}

Wir speichern die Position der Granate in temp, fügen 10 Quants in z-Richtung hinzu und erstellen dann ein Sprite namens explosion_pcx an dieser Position, mit der Funktion animated_explosion. Die letzte Codezeile entfernt die Granate.

function animated_explosion()
{
my.passable = on;
my.flare = on;
my.ambient = 100;
ent_playsound (my, explosion_wav, 500);
while (my.frame < 7)
{
my.frame += 1 * time;
my.scale_x += 2 * time;
my.scale_y = my.scale_x;
my.scale_z = my.scale_x;
player.roll = 2 - random(4); // shake the player a bit
wait (1);
}
player.roll = 0;
my.invisible = on;
temp.pan = 360;
temp.tilt = 180;
temp.z = 400;
scan_entity (my.x, temp);
sleep (2);
ent_remove (me);
}

Das Explosionssprite ist passierbar, hat sein Flare Flag gesetzt und einen Ambient von 100. Wir lassen an den Koordinaten des Auftreffens ein Explosionsgeräusch ertönen und lassen das Sprite dann in einr Schleife seine Frames durchlaufen.

Dieselbe Schleife skaliert das Explosionssprite in jedem Frame etwas größer für einen dramatischeren Effekt und schüttelt den Spieler ein wenig durch; in Wahrheit wird einfach ein zufälliger Wert zwischen –2 und 2 für den camera.roll gewählt. Sobald wir die Schleife verlassen wird dieser zurückgesetzt und verbergen das Explosionssprite; wir können es noch nicht entfernen, weil das zugehörige Geräusch noch nicht ganz verklungen ist.

Nun ist es Zeit, ein wenig Schaden in der Umgebung anzurichten, also setzen wir einen Scan Kegel von 360 Grad horizontal, 180 Grad Vertikal und 400 Quants Reichweite und starten scan_entity. Das bedeutet jede Entity mit gesetztem enable_scan Flag wird die Event Funktion aufrufen. Die letzten Codezeilen stellen sicher, dass das Geräusch verklungen ist und dann entfernen wir das Sprite.

Wie wäre es mit einigen Entities, die von unserer Granate zerstört werden können? Sehen Sie selbst wie einfach es ist, diese zu erstellen:

action barrel
{
my.enable_scan = on;
my.event = barrel_explodes;
}

Diese Fässer reagieren aufs Scannen und starten dann ihre barrel_explodes Event Funktion.

function barrel_explodes()
{
my.event = null;
wait (1 + random(20)); // wait up to 20 frames
ent_morph (my, barrel_pcx);
my.flare = on;
my.ambient = 100;
ent_playsound (my, explosion_wav, 200);

Die erste Zeile setzt die Event Funktion auf Null; auf diese Weise kann das Event nicht mehr als einmal gestartet werden. Wir warten eine zufällige Zahl von Frames (1..20), weil Kettenreaktionen so viel besser aussehen und morphen dann das Faß in ein Explosionssprite mit gesetztem Flare Flag. Wir brauchen eine helle Explosion, also bekommt das Sprite noch einen Ambient von 100. Schließlich lassen wir ein Geräusch ertönen.

while (my.frame < 10)
{
my.frame += 1 * time;
wait (1);
}
my.invisible = on;
temp.pan = 360;
temp.tilt = 180;
temp.z = 150;
scan_entity (my.x, temp);
sleep (2);
ent_remove (me);
}

Das Explosionssprite durchläuft seine Animation in einer Schleife und wird dann unsichtbar, bevor es einen Scan mit einer kleineren Reichweite (150 Quants) durchführt, weil ein Faß eine Erschütterung der Macht verursachen kann (und sollte!), indem andere Entities in der Nähe mit enable_scan Flag ebenfalls ausgelöst werden. Wir warten noch 2 Sekunden bis das Geräusch wirklich verklungen ist und entfernen dann das unsichtbare Explosionssprite.

Laden Sie mein office.wmp Demo Level, werfen Sie eine Granate und dann können Sie sich zurücklehnen, etwas Popcorn essen und die Kettenreaktion genießen.