Diesen Monat geht die Action weiter und wir lernen, wie man drei neue Gegnertypen ins Spiel einbaut, mit dabei auch ein Endgegner. Bevor wir damit anfangen eine kleine Liste von Änderungen am bisherigen Code:
-
Die Geschosse des Spielers werden entfernt, wenn ihr x-Wert größer
ist als 700 Quants; auf diese Weise ist es ihm nicht möglich, Feinde zu
töten, die noch weit weg sind (er muß warten, bis sie auf dem
Schirm erscheinen). Sehen Sie sich die Funktion move_bullet() in level2.wdl
an, um
zu sehen, wie ich das gemacht habe.
- Das Level ist jetzt größer (40.000 Quants), weil wir neue Feinde
haben und es sonst nicht genug Platz gab.
Schauen wir uns die Action an, die zu dem ersten der neuen Feinde gehört:
action shuttle2
{
my.ambient = 30;
while (player == null) {wait (1);}
my.z = player.z; // set the same height for the enemy ships and the
player
var shuttle_speed;
my.skill10 = 1;
my.skill40 = 4; // I'm a ship
my.enable_impact = on;
my.event = destroy_them;
shuttle_speed.x = -2 * time;
shuttle_speed.y = 0;
shuttle_speed.z = 0;
while ((my.x > -300) && (my.skill10 >= 0))
{
if ((vec_dist (my.x, player.x) < 800) && (total_frames
% 90 == 1))
{
if (my.x > player.x) // still got the chance to hit the player?
{
snd_play (fire1_wav,
40, 0);
vec_set (temp.x,
my.x);
temp.x -= 30;
temp.y -= 83;
ent_create (bolt_mdl,
temp.x, move_bullet2);
vec_set (temp.x,
my.x);
temp.x -= 30;
temp.y += 83;
ent_create (bolt_mdl,
temp.x, move_bullet2);
}
}
move_mode = ignore_you + ignore_passable;
ent_move (nullvector, shuttle_speed);
wait (1);
}
ent_remove (my);
}
Dieses Shuttle wartet bis der Spieler im Level erscheint und gleicht dann seine Höhe an die des Spielers an um sicherzugehen, dass es den Spieler trifft bzw. ihn sogar rammen kann, wenn dieser es zulässt. Wir setzen skill40 auf 4 für das Shuttle, weil wir eine Art ID brauchen, die dann in der destroy_them Event Funktion verwendet wird.
Das schiff wird sich bewegen bis sein x-Wert geringer ist als –300 Quants (dann kann der Spieler es nicht mehr sehen) sofern skill10 größer oder gleich 0 ist. Wir benötigen 2 Schuß, um ein solches Shuttle zu zerstören. Wenn das Shuttle näher als 800 Quants am Spieler ist, könnte es alle 1,5 Sekunden feuern, weil die Framerate auf 60 Fps festgesetzt ist und wir total_frames % 90 prüfen, also ein Ereignis, dass alle 90 / 60 = 1,5 Sekunden eintritt. Ich sagte das Schiff „könnte“ alle 1,5 Sekunden feuern, weil es dies nur tut, wenn sein x-Wert größer ist als der des Spielers (es hat eine Position oberhalb vom Spieler). Ist dies der Fall, ertönt Fire1.wav, wir wählen eine vorübergehende Position für den Laserstrahl, erstellen diesen und wiederholen dies für den zweiten Laserstrahl. Diese Strahlen werden von der move_bullet2() Funktion angetrieben. Oh und bevor ich Ihnen diese Funktion zeige, beachten Sie bitte, dass die Shuttles passierbare Entities ignorieren und entfernt werden, wenn ihr x-Wert unter –300 fällt oder wenn sie zwei Mal getroffen wurden.
function move_bullet2()
{
var bullet_speed;
my.passable = on;
my.enable_impact = on;
my.enable_entity = on;
my.event = destroy_bullet;
my.pan = you.pan;
bullet_speed.x = 50 * time;
bullet_speed.y = 0;
bullet_speed.z = 0;
my.ambient = 100;
my.bright = on;
my.light = on;
my.red = 50;
my.green = 255;
my.blue = 50;
while (my != null) // as long as the bullet exists
{
if (you != null)
{
if(vec_dist (my.x, you.x) > 50)
{
my.passable =
off;
}
}
my.roll += 200 * time;
move_mode = ignore_you + ignore_passable;
ent_move (bullet_speed, nullvector);
wait (1);
}
}
Das Geschoß ist zunächst passierbar und wird erst dann “fest”, wenn die Distanz zur erzeugenden Entity (dem Shuttle) größer wird als 50 Quants. Die Strahlen rotieren mit einer Geschwindigkeit von 200 * time; manipulieren Sie diesen Wert, wenn Sie möchten.
Das zweite Feindschiff ist etwas interessanter: es erzeugt eine Partikelkugel, die weiter und weiter wächst und alle Entities (Spieler, Feinde, Asteroiden) in der Umgebung vernichtet. Schauen wir uns den Code an:
action shuttle3
{
my.ambient = 30;
while (player == null) {wait (1);}
ent_create (sphere_mdl, my.x, move_sphere);
my.z = player.z; // set the same height for the enemy ships and the
player
var shuttle_speed;
my.skill10 = 1;
my.skill40 = 1; // I'm a ship
my.enable_impact = on;
my.event = destroy_them;
shuttle_speed.x = -2 * time;
shuttle_speed.y = 0;
shuttle_speed.z = 0;
while ((my.x > -300) && (my.skill10 == 1))
{
move_mode = ignore_you + ignore_passable;
ent_move (nullvector, shuttle_speed);
wait (1);
}
ent_remove (my);
}
Das zweite Schiff erzeugt eine sphere.mdl Entity (die Kugel) an seiner Position und gibt diesem die move_sphere Funktion. Auch dieses Schiff bewegt sich, bis sein x-Wert unter –300 fällt, vorausgesetzt es überlebt so lange (also falls my.skill10 auf 1 stehen bleibt).
function
move_sphere()
{
proc_late();
var index;
var vertices;
my.passable = on;
my.invisible = on;
vertices = ent_vertices(my);
while (you != null)
{
vec_set (my.x, you.x);
if (you.x < 800)
{
if (my.scale_x == 1)
{
snd_play (scanning_wav,
60, 0); // this sound will be played only once
}
my.scale_x += 1 * time;
my.scale_x = min(30, my.scale_x);
// limit the scale to 30
my.scale_y = my.scale_x;
my.scale_z = my.scale_x;
temp.pan = 360;
temp.tilt = 360;
temp.z = my.scale_x * 37; // 37
= experimental value, must fit the size of the
particle sphere
scan_entity (camera.x, temp);
}
Die Kugel ist unsichtbar und passierbar; wir ermitteln die Anzahl der Vertizes und gehen dann in eine While-Schleife, die läuft, solange das Schiff (you) existiert. Die Schleife setzt die Kugel auf die gleiche Position wie das Schiff. Falls Letzteres einen x-Wert unter 800 Quants aufweist und die Kugel eine Größe (scale) von 1 hat (was nur ein Mal geschieht), wird das unheimliche scanning.wav Geräusch abgespielt. Die Kugel wird größer, bis der scale einen Wert von 30 erreicht und führt in jedem Frame scan_entity Anweisungen aus, jedes Mal mit größerer Reichweite. Ändern Sie ggf. den Wert 37 – ich habe ihn experimentell ermittelt und er muß auf die Größe des Models passen.
index = 0;
while (index < vertices) // while without wait!
{
vec_for_vertex (temp, my, index);
effect (particle_effect3, 10,
temp, normal);
index += 1;
}
my.pan += 3 * time;
my.tilt += 3 * time;
my.roll += 3 * time;
wait (1);
}
ent_remove (my);
}
Der optische Effekt stammt aus den obigen Zeilen: wir verwenden eine schnelle Schleife (ohne wait!) und gehen alle Vertizes der Kugel durch und dann erstellen wir dort Partikel mit Hilfe der particle_effect Funktion. Die ganze Zeit über dreht sich die Kugel, indem wir pan, tilt und roll ändern.
function particle_effect3()
{
temp.x = 5 - random (10);
temp.y = 0;
temp.z = 0;
vec_add (my.vel_x, temp);
my.bmap = glow_tga;
my.alpha = 80;
my.size = 5;
my.bright = on;
my.move = on;
my.lifespan = 1;
my.function = destroy_particles;
}
function destroy_particles()
{
my.alpha -= 10 * time;
if (my.alpha < 0)
{
my.lifespan = 0;
}
}
Hier gibt es nichts wirklich Interessantes zu sehen. Die Partikel werden zerstört, sobald ihr Alpha Wert unter 0 fällt. Sind Sie bereit, auf den Endgegner zu treffen?
action
boss_level2
{
my.ambient = -30;
while (player == null) {wait (1);}
my.z = player.z; // set the same height for the enemy ships and the
player
var boss_speed;
my.skill10 = 1;
my.skill20 = 100;
my.skill40 = 3; // I'm the boss
boss_speed.x = -2 * time;
boss_speed.y = 0;
boss_speed.z = 0;
while (my.x > 650)
{
move_mode = ignore_you + ignore_passable;
ent_move (nullvector, boss_speed);
wait (1);
}
my.enable_impact = on;
my.event = destroy_them;
boss_speed.x = 0;
boss_speed.z = 0;
while (my.skill20 > 0)
{
if (player.y > my.y)
{
my.y += 5 * time;
}
else
{
my.y -= 5 * time;
}
Der Boss bewegt sich auf den Spieler zu, bis sein x-Wert 650 unterschreitet, dann wird er stoppen und reagiert fortan auch auf die Geschosse des Spielers (impact). Der Boss versucht, seinen y-Wert an den des Spielers anzugleichen; er bewegt sich je nach dessen Position nach links oder rechts.
if (((my.y - player.y) < 200) && (total_frames
% 40 == 1))
{
if (player.invisible == on) {break;}
vec_set (temp.x, my.x);
temp.x -= 120;
snd_play (fire1_wav, 40, 0);
ent_create (laserboss_mdl, temp.x,
move_bullet3);
wait (1);
vec_set (temp.x, my.x);
temp.x -= 40;
temp.y += 145;
snd_play (fire1_wav, 40, 0);
ent_create (laserboss_mdl, temp.x,
move_bullet3);
wait (1);
vec_set (temp.x, my.x);
temp.x -= 40;
temp.y -= 145;
snd_play (fire1_wav, 40, 0);
ent_create (laserboss_mdl, temp.x,
move_bullet3);
}
wait (1);
}
ent_remove (my);
Falls der Boss auf der y-Achse nah genug ist, beginnt er alle 40 Frames zu feuern, also etwa alle 40 / 60 = 0,66 Sekunden. Wenn der Spieler unsichtbar ist (er wurde zerstört), verlässt der Boss die Schleife und wird entfernt. Lebt der Spieler noch, werden 3 Feuerpunkte ermittelt (sie entsprechen den 3 Waffen des Endgegners) und dort werden 3 laserboss.mdl Entities erzeugt, welche die move_bullet3 Funktion zugewiesen bekommen.
sleep (3);
my = null;
level_load (level3_wmb);
wait (3);
camera.pan = 0; // set the correct camera position and angles
camera.tilt = -90;
camera.roll = 0;
camera.x = 330;
camera.y = 0;
camera.z = 1000;
}
Hier wurde der Boss zerstört, also ist das Level
zu Ende. Wir warten noch 3 Sekunden und laden dann das nächste Level (Sie
werden es nächsten Monat sehen), warten bis dies geschehen ist und setzen
dann eine neue Position und einen neuen Winkel für die Kamera. Schauen wir
uns die Funktion für die Geschosse des Endgegners an:
function move_bullet3() // used by the boss at the end of the level
{
var bullet_speed;
my.passable = on;
my.enable_impact = on;
my.enable_entity = on;
my.event = destroy_bullet;
my.pan = you.pan;
bullet_speed.x = 35 * time;
bullet_speed.y = 0;
bullet_speed.z = 0;
my.ambient = 100;
my.bright = on;
my.light = on;
my.red = 50;
my.green = 255;
my.blue = 50;
while (my != null) // as long as the bullet exists
{
if (you != null)
{
if(vec_dist (my.x, you.x) > 50)
{
my.passable = off;
}
}
if (vec_dist(player.x, my.x) > 400) // not
too close
to
the player?
{
vec_set(temp, player.x); // rotate the bullet towards player's ship
vec_sub(temp, my.x);
vec_to_angle(my.pan, temp);
}
move_mode = ignore_you + ignore_passable;
ent_move (bullet_speed, nullvector);
wait (1);
}
}
Der einzige Teil, der vielleicht erklärungsbedürftig ist, ist die Schleife, die solange läuft wie das Geschoß existiert. Wenn der Endgegner noch lebt und die Distanz kleiner als 50 Quants ist, dann ist das Geschoß passierbar, sonst nicht. Sind die Geschosse nicht zu nah am Spieler (mehr als 400 Quants), drehen sie sich in Richtung des Spielers und werden somit zu Lenkgeschossen, die es dem Spieler schwerer machen.
function destroy_them()
{
snd_play (explo1_wav, 50, 0);
if (my.skill40 == 1) // a ship was hit?
{
my.skill10 = 0;
if (you.skill40 == 99) // if it was player's bullet
{
score_value += 100;
// get 100 points for a ship
}
effect(particles_explo2, 30, my.x, nullvector);
}
if (my.skill40 == 2) // hit one of the asteriods?
{
my.skill10 = 0;
if (you.skill40 == 99) // if it was player's bullet
{
score_value += 50; //
get 50 points for an asteroid
}
effect(particles_explo1, 20, my.x, nullvector);
}
Die Funktion, die alle Gegner zerstört hat sich ziemlich geändert, daher habe ich mich entschlossen, sie Ihnen nochmal zu zeigen. Falls skill40 auf 1 steht, hat der Spieler ein Raumschiff getroffen, also wird skill10 auf 0 gesetzt, was das Schiff zerstört. Wir prüfen, ob das Schiff von einem Geschoß es Spielers getroffen wurde (you.skill40 == 99) und nur wenn dies der Fall ist, wird die Punktzahl um 100 erhöht. Schließlich gibt es noch einen Partikeleffekt für die Explosion. Das Gleiche geschieht, wenn ein Asteroid getroffen wird; der Spieler erhält keine Punkte, wenn der Asteroid von einem der Feinde zerstört wurde.
if (my.skill40 == 3) // hit the boss at the end of the level?
{
my.skill10 = 0;
if (you.skill40 == 99) // if it was player's bullet
{
score_value += 100;
// get 100 points for a shot
}
my.skill20 -= 10; // decrease the shield for the boss
if (my.skill20 <= 0)
{
snd_play (bossexplo_wav,
100, 0);
effect(particles_explo3,
100, my.x, nullvector); // similar to the other particle
explosions
}
}
if (my.skill40 == 4) // shuttle2 was hit?
{
my.skill10 -= 1;
if (my.skill10 < 0)
{
if (you.skill40 == 99)
// if it was player's bullet
{
score_value
+= 200; // get 200 points for shuttle2
}
effect(particles_explo2,
50, my.x, nullvector);
}
}
}
Wenn der Spieler den Endgegner trifft (skill40 == 3), erhält er 100 Punkte für jeden Treffer, weil der Endgegner erst zerstört ist, wenn der Spieler ihn 10 Mal getroffen hat. Falls der Spieler schließlich eines der Shuttles erwischt, erhält er 200 Punkte, weil diese auch zwei Mal getroffen werden müssen.
action
player_level3
{
while (1)
{
my.roll += 4 * time;
wait (1);
}
}
Die obige
Action demonstriert, dass das dritte Level geladen wird, wenn der Endgegner
am Ende des zweiten zerstört wird; keine Angst, wir werden nächsten
Monat eine nützlichere Action parat haben, in einer neuen und (hoffentlich)
aufregenden Umgebung.
Tödliche Spinnen
Diese unheimlichen Kreaturen werden in Ihrem Level umherlaufen, Hindernissen ausweichen und jede Entity, die mit ihnen kollidiert, beißen (außer anderen Spinnen natürlich).
action deadly_spider
{
var spider_speed;
var in_front;
my.enable_entity = on;
my.enable_impact = on;
my.event = hurt_them;
while(1)
{
spider_speed.x = 8 * time;
spider_speed.y = 0;
spider_speed.z = 0;
spider_speed *= time;
in_front.x = my.x + 40 * cos(my.pan);
in_front.y = my.y + 40 * sin(my.pan);
in_front.z = my.z;
if (content(in_front) == content_solid)
{
my.skill40 = my.pan;
my.skill41 = 30 + random(90);
while (my.pan < my.skill40 +
my.skill41) // rotate 30..120 degrees
{
my.pan += 5 *
time;
wait (1);
}
}
Die Spinnen reagieren auf andere Entities; ihre Event Funktion heißt hurt_them. Die eigentliche Bewegung findet in einer While(1)-Schleife statt, mit einer Geschwindigkeit, die durch spider_speed.x gegeben ist. Ich habe eine lokale Variable namens in_front eingeführt und ihre Koordinaten 40 Quants vor der Position der Spinne festgelegt; falls an der Stelle in_front eine massive Wand (oder eine Map Entity) ist, dann sichern wir den ursprünglichen Pan der Spinne und addieren einen zufälligen Winkel (30 bis 120 Grad) in einer Schleife, die diesen Wert langsam erhöht. Dieser Trick wird die Spinne (hoffentlich) in eine Richtung drehen, die nicht durch Hindernisse blockiert ist.
else // free to move
{
ent_cycle("run", my.skill20);
// play the "run" animation
my.skill20 += 10 * time;
my.skill20 %= 100; // loop
move_mode = ignore_passable; //
ignore passable entities
result = ent_move (spider_speed,
nullvector);
if (result == 0) // got stuck?
{
spider_speed.x
*= -1; // then reverse the movement direction
my.skill40 = my.pan;
my.skill41 = 30
+ random(90);
while (my.pan < my.skill40
+ my.skill41) // rotate 30..120 degrees
{
my.pan
+= 5 * time;
ent_cycle("run",
my.skill20); // play reversed "run" animation
my.skill20
-= 10 * time;
my.skill20
%= 100; // loop
move_mode
= ignore_passable; // ignore passable entities
ent_move
(spider_speed, nullvector);
wait
(1);
}
spider_speed.x *= -1;
// restore the initial speed
}
}
wait (1);
}
wait (1);
}
Wenn die Spinne sich bewegen kann, läuft ihre “run” Animation und passierbare Entities unterwegs werden einfach ignoriert. Wir speichern das Ergebnis der Bewegung (die zurückgelegte Strecke) in „result“ und prüfen dann den Wert; hängt die Spinne fest, ist dieser gleich 0, also multiplizieren wir spider_speed.x mit –1 und kehren damit die Bewegungsrichtung um. Es wird wieder ein zufälliger Winkel erzeugt, der langsam zum ursprünglichen Winkel addiert wird und dann bewegen wir die Spinne rückwärts, wobei auch die „run“ Animation rückwärts läuft; wieder werden passierbare Entities ignoriert. Am Ende der Schleife mulitplizieren wir spider_speed.x wieder mit –1 und stellen damit die alte Geschwindigkeit wieder her.
Schauen wir uns nun noch die Event Funktion an.
function hurt_them()
{
proc_kill(4);
if (you != null) // collided with an entity (the player, a monster,
etc)?
{
snd_play (gotcha_wav, 50, 0);
you.skill9 -= 10; // if "health" for that
entity is stored in its skill9
}
}
Diese Funktion läuft, wenn die Spinne mit etwas kollidiert. Wenn das eine Entity war (you != null), ertönt das gotcha.wav Geräusch und wir verringern skill9 der gebissenen Entity. Wenn Ihr Spieler oder die Gegner ihre Gesundheit in einem anderen Skill speichern, ändern Sie dies bitte entsprechend ab. Die Action player_walk_fight verwendet skill9, also wird der Spieler in meiner Demo wohl an Spinnenbissen sterben.