Planet Survivors

Diesen Monat wird der Spieler sich mit einer leicht modifizierten Action bewegen, welche nun auch Trägheit und Reibung beachtet, sich über ein paar Leben- und Ammo-Packs freuen und auch auf den ersten Gegner auf dem Planten treffen: einen Giganten der tödliche Projektile abfeuert. Ich werde die Änderungen am Movement Code nicht großartig beschreiben, weil sie wirklich einfach sind und ich ähnliche Codes schon in meinen anderen AUM’s verwendet habe.

action health_pack
{
my.passable = on;
my.metal = on;
while (player_1st == null) {wait (1);}
while (vec_dist(player_1st.x, my.x) > 70)
{
my.pan += 3 * time;
wait (1);
}
snd_play (pickedup_wav, 50, 0);
ent_remove (my);
players_shield += 20;
players_shield = min(200, players_shield);
}

action ammo_pack
{
my.passable = on;
my.metal = on;
while (player_1st == null) {wait (1);}
while (vec_dist(player_1st.x, my.x) > 70)
{
my.pan += 3 * time;
wait (1);
}
snd_play (pickedup_wav, 50, 0);
ent_remove (my);
players_ammo += 50;
players_ammo = min(500, players_ammo);
}

Die Actions der Leben- und Ammo-Packs sind gleich; die Munition ist passable und hat ihr „metal“ Flag gesetzt; das Script wird warten bis „player_1st“ erstellt ist und dann wiederum solange, bis der Player näher als 70 Quants zu der Box kommt. Gleichzeitig wird in einem Loop der pan Winkel geändert, damit die Box sich dreht. Wenn der Player sich dann der Box annähert wird ein „pickedup_wav“ Sound gespielt, die Box verschwindet und „player_ammo“ wird um 50 Schuss erhöht. Die letzte Zeile Script limitiert die Munition auf maximal 500 Schuss. Nebenbei, „player_shield“ ist limitiert auf 200.

Sehen wir uns nun den Code an, welcher die große Ameise bewegt:

action ant1
{
var ant_speed;
var ant_gun1;
var ant_gun2;
my.skill20 = random(100); // choose a random "stand" frame for each ant
my.lightred = 0; // my.red for A6, etc
my.lightgreen = 255;
my.lightblue = 0;
my.light = on;
my.lightrange = 0;
my.skill48 = 100; // the ant has 100 health points
my.enable_impact = on;
my.enable_entity = on;
my.event = damage_ant;
while (player_1st == null) {wait (1);}

Die Ameise wählt einen zufälligen Wert für my.skill20 zu Beginn aus; skill20 kontrolliert die Animationsframes, wenn wir also 10 Ameisen in unserem Level platzieren werden ihre „stand“ Animationen nicht alle gleich sein, was natürlich viel besser aussieht. Jede Ameise glüht Grün und hat 100 Lebens Punkte, welche in ihrem skill48 gespeichert sind. Die Ameisen reagieren auf „Impact“ (Kollision) mit anderen Entities und starten dann ihre „damage_ant“ Funktion. Die letzte Zeile Code lässt die Ameisen solange warten, bis der Player geladen wurde.

while (my.skill48 > 0) // as long as I'm alive
{
if ((vec_dist (my.x, player_1st.x) < 5000) && (players_shield > 0))
{
vec_set(temp, player_1st.x);
vec_sub(temp, my.x);
vec_to_angle(my.pan, temp); // the ant rotates toward the player
temp.tilt = 0;
temp.roll = 0;
temp.pan = -my.pan;
vec_rotate(normal, temp);
temp.tilt = -asin(normal.x);
temp.roll = -asin(normal.y);
my.tilt += 0.1 * ang(temp.tilt - my.tilt); // play with 0.1
my.roll += 0.1 * ang(temp.roll - my.roll); // play with 0.1

Solange die Ameise am Leben ist, prüft sie ob der Player näher als 5000 Quants ist und ob er noch lebt. Wenn beide Bedingungen wahr sind, dreht sich die Ameise in Richtung des Players, und dann drehen wir die Normale in eine Richtung welche das Gegenteil ihrer pan Winkel ist. Das erlaubt uns dann die tilt und roll Winkel zu berechnen; wir benutzen diese Werte um eine richtige Ausrichtung der Ameise auf einem Terrain zu haben. Wie Sie sicher gesehen haben, wird der tilt und roll Wert langsam erhöht bzw. verringert; verringern Sie „0.1“ um eine weichere Winkel Anpassung zu erreichen.

ant_speed.x = 25 * time;
ant_speed.y = 0;
vec_set (temp, my.x);
temp.z -= 1000;
trace_mode = ignore_me + use_box;
ant_speed.z = -trace (my.x, temp);
ent_move(ant_speed, nullvector);
ent_cycle("walk", my.skill19); // play "walk"
my.skill19 += 8 * time; // "walk" animation speed
my.skill19 %= 100; // loop the animation

Die Ameise wird auf den Spieler zu laufen mit einer Geschwindigkeit von 25*time und wird ihre Höhe anhand der Terrainhöhe unter ihr bestimmen. Die „walk“ Animation Geschwindigkeit ist durch 8*time festgelegt.

if (vec_dist (my.x, player_1st.x) < 2000) // if the player is closer than 2000 quants attack him
{
my.skill20 = 0; // reset the "attack" frames
while (my.skill20 < 100)
{
ent_cycle("attack", my.skill20); // play "attack"
my.skill20 += 10 * time; // "attack" animation speed
if ((my.skill20 > 40) && ((total_frames % 10) == 1))
{
vec_for_vertex (ant_gun1, my, 273);
ent_create (antsbullet_mdl, ant_gun1, ant_hits_player);
}
if ((my.skill20 > 40) && ((total_frames % 10) == 5))
{
vec_for_vertex (ant_gun2, my, 282);
ent_create (antsbullet_mdl, ant_gun2, ant_hits_player);
}
wait (1);
}
}
}

Wenn der Player näher als 200 Quants kommt, wird die Ameise ihre Angriffs-Frames resetten (skill20) und die Angriffs-Frames von Beginn an durch erhöhen des Wertes von Skill20 abspielen. Wenn die Ameise mehr als 40% ihrer Angriffs-Frames abgespielt hat, wird sie 6 Schüsse die Sekunde von jeder ihrer Waffen abfeuern (ant_gun1 scheißt nach 1,11,21,31, … Frames und ant_gun2 schießt nach 5, 15, 25, 35, … Frames).

else // the player is farther than 5000 quants away
{
ent_cycle("stand", my.skill20); // play "stand"
my.skill20 += 2 * time; // "stand" animation speed
my.skill21 %= 100;
}
wait (1);
}
my.skill20 = 0;
while (my.skill20 < 90) // the enemy is dead
{
ent_cycle("death", my.skill20); // play "death"
my.skill20 += 7 * time; // "death" animation speed
wait (1);
}
my.alpha = 100;
my.transparent = on;
while (my.alpha > 0)
{
my.alpha -= 2 * time;
wait (1);
}
ent_remove (my);
}

Sollte der Player dann wieder flüchten und weggehen, wird die Ameise wieder ab 5000 Quants Entfernung zum Player auf ihre “stand” Animation umschaltet und diese in einer Schleife spielen.
Wenn wir dann zu der Zeile gehen, wo skill20 wieder zurückgesetzt wird, ist die Ameise tot und wir spielen ihre „death“ Animation ab und faden ihren Körper danach aus. Die letzte Zeile entfernt dann das Model aus dem Level.

function damage_ant()
{
if (you.skill47 == 1) // if this is a bullet fired by player's gun
{
my.skill48 -= 10; // lose 10 health points
ent_playsound (my, antangry_wav, 3000);
}
}

Die obige Funktion wird aufgerufen, wenn einer der Schüsse des Players die Ameise trifft. Die erste Zeile prüft ob die Ameise mit einer Kugel kollidiert ist, oder nicht (sie kann auch mit einer anderen Ameise, dem Spieler oder sonst wem kollidieren) und wenn das wahr ist, zieht sie 10 Lebens-Punkte ab und spielt den „antangry.wav“ Sound ab.

function ant_hits_player()
{
my.skill47 = 5; // that's an bullet fired by the ant
my.pan = you.pan; // the bullet and the ant have the same pan
my.tilt = you.tilt; // and tilt
my.enable_entity = on; // the bullet is sensitive to other entities
my.enable_impact = on;
my.enable_block = on; // and to level blocks
my.event = remove_bullet;
my.skill30 = 0;
while (my != null)
{
my.skill1 = 100 * time; // bullet speed
my.skill2 = 0;
if (my.skill30 < 1500)
{
my.skill3 = 0; // keep the bullet running in a straight line at the beginning
}
else
{
my.skill3 -= 1 * time; // make it descend after a while
}
move_mode = ignore_you + ignore_passable + ignore_push; // ignores the ant (its creator = you)
my.skill30 += ent_move(my.skill1, nullvector); // store the distance covered by ant's bullet in skill30
wait (1);
}
}

Diese Funktion bewegt die Kugeln der Ameise auf den Player hin. Ich habe mich dazu entschieden, die meisten Schüsse der Ameise ins leere gehen zu lassen, denn sie hat trotzdem eine noch extrem hohe Feuer- und Trefferrate. Das erklärt auch, warum die Kugeln die gleichen tilt und angle Werte haben wie die tilt und angle Werte der Ameise. Die Kugeln können mit anderen Entities und auch Level-Blocks/Terrain kollidieren und die dazugehörende Funktion ist „remove_bullet“. Wir setzten dafür skill30 zurück und unterbrechen den while-Loop, welcher läuft, solange die Kugel existiert. Die Kugel bewegt sich mit einer Geschwindigkeit von 100*time. Solange sie nicht eine Entfernung von 1500 Quants zurückgelegt hat, ist die Flugbahn eine gerade Linie. Ab dem Überschreiten von 1500 Quants bekommt sie einen kleinen Negativen Z-Wert, was dazu führt, das sie über kurz oder lang den Boden treffen wird. Die Ameise, welche die Kugel abgeschossen hat, wird bei der Kollision nicht beachtet und speichert den result-Wert aus ihrer Bewegung in ihrem skill30.

function remove_antsbullet()
{
my.event = null;
ent_playsound (my, anthit_wav, 1000);
sleep (0.4);
my.passable = on;
my.alpha = 100;
my.transparent = on;
while (my.alpha > 0)
{
my.alpha -= 2 * time;
wait (1);
}
ent_remove (my);
}

Nun kümmern wir uns um die Events der Kugeln. Dafür ist die obige Funktion zuständig. Sie stoppt die Kugeln bei Kollision, spielt einen „anthit_wav“ Sound, wartet kurz und macht die Kugel passable und transparent, senkt ihren Alpha Wert in einer Schleife und entfernt die Kugel.

function hurt_player1st()
{
if (you.skill47 == 5)
{
my.event = null;
snd_play(playerhurt_wav, 50, 0);
players_shield -= 5;
sleep (0.5);
my.event = hurt_player1st;
}
players_shield = max(0, players_shield); // don't allow players_shield to go below 0
while (players_shield <= 0)
{
if (camera.roll < 70)
{
camera.roll += 5 * time;
}
wait (1);
}
}

Die letzte Funktion verletzt den Player wenn ihn eine der Kugeln trifft, welche die Ameise abschießt. Zuerst prüfen wir ob die Entity, die den Player trifft von der Ameise kommt oder nicht (skill45 = 5). Wenn das der Fall ist spielen wir einen „playerhurt_wav“ Sound ab, ziehen der Panzerung des Players ein paar Punkte ab und machen den Player „unverwundbar“ für weitere Schüsse für 0.5 Sekunden, bevor wir das Event wieder prüfen. Des Weiteren werden wir nicht zulassen, das die Panzerung des Spielers unter 0 geht (health = -15 würde nicht so gut auf einem Panel aussehen, oder? ;-) ) und dann ändern wir den roll Winkel der Kamera in einer Schleife um es dem Spieler ein bisschen unangenehm zu machen und ihm mitzuteilen, das sein Player nun tot ist.

Flammenwerfer

Wenn Sie eine Waffe benötigen, welche einen Strahl von Partikeln erzeugt, dann sind Sie hier genau richtig!

action flame_thrower
{
my.metal = on;
while (player == null) {wait (1);} // wait until the player is loaded
while (vec_dist (my.x, player.x) > 60) // wait until the player has come close enough
{
my.pan += 5 * time;
wait (1);
}
snd_play(gotflames_wav, 60, 0);
my.passable = on;
while (1)
{
my.x = player.x + 20 * cos(player.pan) + 13 * sin(player.pan);
my.y = player.y + 20 * sin(player.pan) - 13 * cos(player.pan);
my.z = player.z + 12;
my.pan = camera.pan;
my.tilt = camera.tilt;
vec_for_vertex (flame_start, my, 14); // the flames will start at the coordinates given by the 14th vertex on the gun
vec_set(flame_end, nullvector); // reset flame_end
flame_end.x = flame_range.x; // add 400 quants to it
vec_rotate(flame_end, camera.pan); // rotate it depending on player's angles
vec_add(flame_end.x, camera.x); // now set the correct flame_end position in 1st person mode (use player.x instead of camera.x for 3rd person)
if(key_ctrl == on) // Ctrl was pressed?
{
create_flames(); // fire!
}
wait (1);
}
}

Die Waffe wird solange warten, bis der Player ihr näher als 60 Quants gekommen ist. Ab dem Moment wird die Waffe passable und 20 Quants vor dem Player, 13 Quants nach Rechts und 12 Quants über dem Nullpunkt des Players platziert, unter Beachtung der Position und / oder der Winkel des Players. Experimentieren Sie ein bisschen mit den Werten 20, 13 und 12 im 1st Person Mode, um, das optimale Ergebnis zu bekommen. Die Waffe wird immer die gleichen pan und tilt Winkel wie die Kamera haben.

Ich habe eine Variable mit dem Namen „flame_start“ definiert und wir benutzen vec_for_vertex um die Position des 14ten Vertexes jede Frame dort hinein zu kopieren. Die zweite Variable „flame_end“ wird auf Null gesetzt und wir addieren den Wert von „flame_range“ (eine Variable welche den Wert von 400 Quants hat) hinzu, drehen „flame_end“ und erstellen einen Vektor mit dem Ausmaß von 400 Quants und seine Richtung festgelegt durch die Winkel des Players. Zuletzt addieren wir die Kamera Position zu dem Vektor; damit haben wir den richtigen „flame_end“ Punkt, welcher (überraschender Weise) der Punkt ist, an dem die Flammen enden werden.

Jedes Mal wenn Sie nun „Strg“ drücken, wird die Funktion „create_flames“ gestartet:

function create_flames()
{
vec_sub(flame_end.x, flame_start.x);
temp = vec_length(flame_end);
flame_end.x = (flame_end.x * 3) / temp;
flame_end.y = (flame_end.y * 3) / temp;
flame_end.z = (flame_end.z * 3) / temp;
while(temp > 0)
{
effect(particle_flames, 1, flame_start.x, nullvector);
vec_add(flame_start.x, flame_end.x);
temp -= 3;
}
}

Diese Funktion holt sich die Distanz zwischen „flame_end“ und „flame_start“, teilt sie in kleine Teile auf, erstellt Partikel bis „flame_start“ den gleichen Wert wie „flame_end“ hat. Wenn Sie mal eine Ansicht von der Seite auf unsere Partikel haben möchten, was mit heutiger Technik kein Problem ist :), würden Sie so etwas sehen:

Ich benutze hier viel mehr Partikel für diesen Effekt und habe die „3“ mit „20“ in der Funktion „create_flames“ ausgetauscht, so das der Effekt mehr als 7 mal so viele Partikel wie im Original benutzt.

function particle_flames()
{
temp.x = random(2) - 1;
temp.y = random(2) - 1;
temp.z = random(2) - 1;
vec_set(my.vel_x, temp);
my.move = on;
my.bmap = fire_tga;
my.flare = on;
my.bright = on;
my.alpha = 100;
my.lifespan = 100;
my.size = 15;
my.function = fade_flames;
}

function fade_flames()
{
my.alpha -= 75 * time;
my.size += 10 * time;
if(my.alpha < 0) {my.lifespan = 0;}
}

Der Rest des Scripts ist Ihr Standart Partikel Effekt; wenn Sie denken das der Feuer-Effekt ein bisschen Verzögert und Aussetzt, dann verkürzen Sie die Lebenszeit des Partikels. Oh, und wenn Sie „flame_start“ auf die Waffe des Gegners setzten und „flame_end“ auf Ihre Position, können Sie mal testen, wie sich das so anfühlt! ;-)
Und für die unter uns, welche zu bequem sind, um meine Scripts zu testen, gibt es hier ein kleines Video. Sie benötigen Windows Media Player 9 oder neuer dafür.