Planet Survivors - Teil 4

In diesem Monat begegnen wir den ersten beiden Feinden: zwei Raumschiffe (es geht ja kaum ohne) und einige Asteroiden. Bevor ich beginne ein Wort der Warnung: dieses Projekt wird ein wahrer Cocktail verschiedener Spiele.

Ich denke an Sie, daher habe ich mich entschieden, ein eigenständiges Skript für das neue Level zu machen, damit Sie nicht das ganze vorherige Spiel durchlaufen müssen, um es zu testen. Der Code für das Skript steht am Anfang der level2.wdl Datei; stellen Sie sicher, dass diese Datei eingebunden ist, wenn Sie das neue Level spielen wollen.

Schauen wir uns zunächst den Code für den netten Sterneneffekt an:

starter particle_generator
{
var particle_start[3];
wait (3); // wait until the level is loaded
camera.pan = 0; // now set the new camera position and angles
camera.tilt = -90;
camera.roll = 0;
camera.x = 330;
camera.y = 0;
camera.z = 1000;
while(1)
{
particle_start.x = 1000;
particle_start.y = 1000 - random(2000);
particle_start.z = -500 + random(200);
effect (particle_init, 1, particle_start, normal);
wait (1);
}
}

Wir definieren eine Variable namens particle_start; das wird der Startpunkt für den Partikeleffekt sein. Wie Sie sehen hat die Kamera eine feste Position und blickt von oben auf das Schiff des Spielers (tilt = -90); mit Hilfe des Sternenfeldes wird die Illusion erzeugt, dass sich das Schiff des Spielers bewegt. Die Sterne werden übrigens von der particle_init Funktion generiert:

function particle_init()
{
temp.x = -(2 + random (15)) * starfield_speed;
temp.y = 0;
temp.z = 0;
vec_add (my.vel_x, temp);
my.bmap = star_tga;
my.alpha = 40 + random(60); // play with this value
my.size = 5 + random(5);
my.bright = on;
my.move = on;
my.lifespan = 500;
my.function = size_particles;
}

function size_particles()
{
my.size += 0.01 * time;
}

Sie haben eine zufällige, negative Geschwindigkeit in x-Richtung. Ändern Sie starfield_speed, um die Gesamtgeschwindigkeit zu ändern. Ich nutze die star.tga Datei für die Sterne mit einem zufälligen Transparenzfaktor und einer zufälligen Größe. Die Funktion size_particles vergrößert die Partikel einfach, solange sie zu sehen sind. Es ist Zeit, sich die Action des Spielers anzusehen:

action player_level2
{
player = my;
my.ambient = -100;
my.enable_impact = on;
my.enable_entity = on;
my.event = damage_player;
var speed_factor_x;
var speed_factor_y;

Der Spieler reagiert auf Kollisionen mit anderen Entities; seine “damage_player” Funktion wird aufgerufen, sobald eine solche stattfindet. Schließlich definieren wir noch zwei lokale Variablen für die Geschwindigkeit des Spielers in x und y Richtung.

while (players_shield > 0)
{
if (key_cul == on)
{
if (speed_factor_y < 10) {speed_factor_y += 5 * time;}
}
else
{
if (speed_factor_y > 0) {speed_factor_y -= 2 * time;}
}
if (key_cur == on)
{
if (speed_factor_y > -10) {speed_factor_y -= 5 * time;}
}
else
{
if (speed_factor_y < 0) {speed_factor_y += 2 * time;}
}
if (abs(speed_factor_y) > 1)
{
if (((speed_factor_y > 0) && (my.y < 500)) || ((speed_factor_y < 0) && (my.y > -500)))
{
my.y += speed_factor_y;
}
}

Solange der Spieler lebt (players_shield > 0) kann er den speed_factor_y langsam erhöhen, indem die linke Pfeiltaste gedrückt und festgehalten wird; die Geschwindigkeit wird langsam wieder verringert, wenn die Taste losgelassen wird. Das gleiche gilt für die rechte Pfeiltaste. Warum brauchen wir Variablen für die Geschwindigkeit? Hauptsächlich, weil die Bewegung des Schiffes viel weicher wirkt, wenn wir die Geschwindigkeit langsam angleichen. Falls speed_factor_y größer als 1 oder kleiner als –1 ist, dann bewegen wir das Schiff um den entsprechenden Wert. Diese Bewegung ist allerdings auf der y-Achse durch –500 bis 500 Quants beschränkt.

if (key_cuu == on)
{
if (speed_factor_x < 10) {speed_factor_x += 5 * time;}
}
else
{
if (speed_factor_x > 0) {speed_factor_x -= 2 * time;}
}
if (key_cud == on)
{
if (speed_factor_x > -10) {speed_factor_x -= 5 * time;}
}
else
{
if (speed_factor_x < 0) {speed_factor_x += 2 * time;}
}
if (abs(speed_factor_x) > 1)
{
if (((speed_factor_x > 0) && (my.x < 650)) || ((speed_factor_x < 0) && (my.x > 0)))
{
my.x += speed_factor_x;
}
}

Dasselbe geschieht auf der x-Achse. Der Spieler vergrößert oder verringert speed_factor_x mit Hilfe der Pfeiltasten. Ist dieser seine Position zwischen 0 und 650 Quants, kann er sich mit der entsprechenden Geschwindigkeit bewegen.

if ((key_space == on) || (mouse_left == on))
{
player_fires();
}
wait (1);
}
}

Wenn der Spieler die Leertaste oder die linke Maustaste drückt, wird die Funktion player_fires() aufgerufen:

function player_fires()
{
proc_kill(4);
while ((key_space == on) || (mouse_left == on)) {wait (1);} // wait until the player releases the keys
snd_play (fire2_wav, 40, 0);
vec_set (temp.x, my.x);
temp.x += 70; // the laser is generated in front of the ship
ent_create (laser2_mdl, temp, move_bullet1);
players_ammo -= 1;
}

Die erste Zeile stellt sicher, dass nur eine Instanz dieser Funktion läuft. Wir warten bis die Taste losgelassen wird, lassen das fire2.wav Geräusch ertönen, berechnen eine Position 70 Quants vor dem Spieler und erzeugen dort eine Laser2.mdl Entity mit der Funktion move_bullet1(). Die letzte Zeile verringert die Munition des Spielers, weil er ja geschossen hat.

function damage_player()
{
wait (1);
snd_play (playerhit_wav, 70, 0);
players_shield -= 10;
if (players_shield < 0)
{
players_shield = 0;
my.invisible = on;
my.passable = on;
effect(particles_explo2, 30, my.x,nullvector);
my.event = null;
}
}

Diese Funktion ist das Event des Spielers. Wird er von einem Geschoß, einem gegnerischen Schiff oder einem Asteroiden getroffen, dann ertönt das playerhit.wav Geräusch, wir verringern den player_shield Wert und setzen diesen auf 0, falls er unter diesen Wert sinkt – in dem Fall wird das Schiff unsichtbar und passierbar gesetzt, das Event wird auf Null gestellt und wir erzeugen eine Partikelexplosion.

function particles_explo2()
{
my.bmap = explo2_tga;
my.bright = on;
my.transparent = on;
my.alpha = 100;
my.move = on;
temp.x = 1 - random(2);
temp.y = 1 - random(2);
temp.z = 0;
vec_normalize (temp.x, (1 + random(1)));
vec_add (my.vel_x, temp.x);
my.size = 2;
my.lifespan = 50;
my.function = explode_2;
}

function explode_2()
{
my.alpha -= 4 * time;
my.size += 3 * time;
if (my.alpha <= 0)
{
my.lifespan = 0;
}
}

Die Explosionsbitmap ist explo2.tga; es ist ein helles, transparentes Bitmap mit einem Alphawert von 100. Dieses wird sich in x und y Richtung zufällig bewegen; die zugehörige Funktion (explode_2) verringert die Transparenz und vergrößert das Bitmap, wobei das Partikel entfernt wird, wenn der Alphawert unter 0 sinkt.

Schauen wir uns nun die Action der gegnerischen Schiffe an:

// uses follow_player
action shuttle
{
my.ambient = -100;
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 = 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;
my.skill1 = 1 + int(random(70));

Das schiff wartet ab bis der Spieler existiert und gleicht dann seinen z-Wert an den des Spielers an. Wir setzen skill10 auf 1 (dieser Wert wird in der Eventfunktion benutzt) und skill40 ebenfalls auf 1 (das ist die Id der Entity), sensibilisieren das Schiff für Kollisionen und weisen die destroy_them() Event Funktion zu. Die Schiffe bewegt sich nur in x-Richtung; hier ist ein Bild aus dem Wed, das dies erklären sollte:

Ich konnte Ihnen nicht das ganze Level zeigen; machen Sie es so groß Sie möchten, indem Sie die Größe in der x-Richtung erhöhen und mehr Schiffe und Asteroiden usw. plazieren. Zurück zu unserem Code:

while ((my.x > -300) && (my.skill10 == 1))
{
if (my.follow_player == on)
{
vec_set(temp, player.x);
vec_sub(temp, my.x);
vec_to_angle(my.pan, temp);
}
move_mode = ignore_you + ignore_passable;
ent_move (nullvector, shuttle_speed);
if ((vec_dist (my.x, player.x) < 800) && (total_frames % 90 == my.skill1)) // random firing intervals
{
if (my.x > player.x) // still got the chance to hit the player?
{
snd_play (fire1_wav, 40, 0);
ent_create (laser_mdl, my.x, move_bullet1);
}
}
wait (1);
}
ent_remove (my);
}

Solange sich die Schiffe nicht hinter den Spieler bewegt haben oder zerstört wurden (also solange ihr skill10 noch 1 ist), drehen sich die Schiffe in Richtung des Spielers, falls ihr follow_player Flag gesetzt ist. Dadurch haben wir zwei verschiedene Arten von feindlichen Schiffen: diejenigen, die einfach geradeaus in x-Richtung feuern und die anderen, die auf den Spieler zielen und versuchen, ihn zu treffen. Die Schiffe ignorieren passierbare Objekte und schießen, wenn sie näher als 800 Quants am Spieler sind und falls total_frames % 90 einem zufälligen Wert entspricht, der in skill1 gespeichert wurde. Zu Deutsch: die Schiffe feuern einmal alle 1,5 Sekunden (die Framerate ist auf 60 limitiert), aber versetzt, da der erste Schußpunkt für jedes Schiff anders ist. Wenn die x-Koordinate des Schiffes größer ist als die des Spielers, dann ertönt fire1.wav und wir erstellen eine Laser.mdl Entity mit der move_bullet1() Action.

function move_bullet1()
{
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;

Diese Funktion wird von den Geschossen des Spielers und der Feinde benutzt. Am Anfang ist das Geschoß passierbar und reagiert auf Kollisionen mit anderen Entities mit der Event Funktion destroy_bullet. Das Geschoß übernimmt den Pan Winkel des feuernden Schiffes und hat eine Geschwindigkeit von 50 * time. Dem Model fügen wir noch einige farbige Lichter hinzu.

while (my != null) // as long as the bullet exists
{
if (you != null)
{
if(vec_dist (my.x, you.x) > 50)
{
my.passable = off;
}
}
move_mode = ignore_you + ignore_passable;
ent_move (bullet_speed, nullvector);
wait (1);
}
}

Solange das Geschoß und das feuernde Schiff existieren und sobald der Abstand zwischen beiden größer als 50 Quants wird, dann setzen wir das Geschoß auf “unpassierbar”. Das Geschoß ignoriert seinen “Erzeuger” und alle passierbaren Entities. Die zugehörige Eventfunktion wird ein Geräusch erklingen lassen und das Geschoß entfernen.

function destroy_bullet()
{
wait (1);
snd_play (explo1_wav, 30, 0);
ent_remove (my);
}

Sehen wir uns die Asteroiden an:

action asteroid
{
var asteroid_speed;
my.metal = on;
my.ambient = -random(100);
my.scale_x = 2 + random(5);
my.scale_y = 2 + random(5);
my.scale_z = 2 + random(5);
my.skill40 = 2; // I'm an asteroid
while (player == null) {wait (1);}
my.z = player.z; // set the same height for the enemy ships and the player
my.enable_impact = on;
my.event = destroy_them;
asteroid_speed.x = -2 * time;
asteroid_speed.y = 0;
asteroid_speed.z = 0;
my.skill10 = 1;
my.skill1 = 4 - random(8);
my.skill2 = 4 - random(8);
my.skill3 = 4 - random(8);
while ((my.x > -300) && (my.skill10 == 1))
{
move_mode = ignore_you + ignore_passable;
ent_move (nullvector, asteroid_speed);
my.pan += my.skill1 * time;
my.tilt += my.skill2 * time;
my.roll += my.skill3 * time;
wait (1);
}
ent_remove (my);
}

Diese haben einen zufälligen Ambient und eine ebenso zufällige Größe; ihr skill40 ist auf 2 gesetzt, damit wir erkennen, ob eine bestimmte Entity ein Asteroid ist oder nicht. Die Asteroiden warten bis der Spieler existiert, gleichen ihre Höhe an, damit sie auch wirklich mit dem Spieler kollidieren können, wenn sie die Möglichkeit haben :) . Auch die Asteroiden haben die geheimnisvolle destroy_them Event Funktion und bewegen sich mit einer Geschwindigkeit von –2 * time auf den Spieler zu. Wir erzeugen 3 zufällige Drehgeschwindigkeiten für Pan, Tilt und Roll und sichern diese in skill1 bis skill3. Die Asteroiden bewegen sich in einer Schleife und drehen sich dabei, solange ihre x-Position größer ist als –300 und ihr Skill10 noch 1 ist (dann wurden sie nicht zerstört).

Ok, schauen wir uns die Funktion an, die den Spieler, die feindlichen Schiffe und die Asteroiden zerstört:

function destroy_them()
{
my.skill10 = 0;
snd_play (explo1_wav, 50, 0);
if (my.skill40 == 1) // a ship was hit?
{
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?
{
score_value += 50; // get 50 points for an asteroid
effect(particles_explo1, 20, my.x, nullvector);
}

Wenn der Spieler, ein gegnerisches Schiff oder ein Asteroid getroffen wird, wird sein skill10 auf 0 gesetzt (was für uns bedeutet, dass die Entity zerstört wurde). Explo1.wav ertönt und wir prüfen die Id (Skill40) der Entity: ist diese 1, dann wurde ein gegnerisches Schiff getroffen, also fügen wir der Punktzahl (score_value) 100 hinzu und erzeugen mit der oben schon beschriebenen Partikelfunktion particles_explo2() einige Partikel. Ist skill40 hingegen 2, wurde ein Asteroid erwischt und die Punktzahl erhöht sich um 50. Hier nutzen wir die particles_explo1() Funktion, die im Grunde identisch zu der anderen ist, bis auf die Tatsache, dass eine andere Bitmap und einige andere Zahlwerte verwendet werden. Hier sind die beiden Effekte zu sehen:

Bis nächsten Monat dann!

Anpaßbares User Interface

Ich muß zugeben, dass ich eine konservative Person bin. Die Tasten für die Bewegung müssen auf eine bestimmte Art belegt sein, die Anzeigen müssen eine bestimmte Größe und Position haben und so weiter. Dieser Artikel wird Ihnen beibringen, ein Panel zu erstellen, das an alle 4 Ecken des Bildschirms gezogen werden kann und dann automatisch auf die gewünschte Position “snappt”.

panel hud_pan
{
bmap = hud_pcx;
layer = 20;
pos_x = 0;
pos_y = 0;
on_click = drag_panel;
flags = overlay, refresh, visible;
}

Das ist Ihre normale Panel Definition; das einzig Wichtige ist die Funktion, die abläuft, wenn Sie das Panel anklicken.

function drag_panel()
{
while (mouse_left == on)
{
hud_pan.pos_x = pointer.x - bmap_width(hud_pcx) / 2;
hud_pan.pos_y = pointer.y - bmap_height(hud_pcx) / 2;
wait (1);
}

Die Schleife läuft, solange die linke Maustaste gedrückt bleibt; sie zentriert das Panel an der Position, die durch pointer.x und pointer.y gegeben ist.

if ((hud_pan.pos_x + bmap_width(hud_pcx) / 2) > (screen_size.x / 2)) // using the right side of the screen
{
if ((hud_pan.pos_y + bmap_height(hud_pcx) / 2) > (screen_size.y / 2)) // lower right corner?
{
hud_pan.pos_x = screen_size.x - bmap_width(hud_pcx);
hud_pan.pos_y = screen_size.y - bmap_height(hud_pcx);
}
else // upper right corner
{
hud_pan.pos_x = screen_size.x - bmap_width(hud_pcx);
hud_pan.pos_y = 0;
}
}

Wenn das Zentrum eine Position hat, die größer als die halbe Auflösung in x-Richtung ist, dann führen wir denselben Test für die y-Position durch und plazieren das Panel dann entsprechend in der oberen rechten oder unteren rechten Ecke.

else // using the left side of the screen
{
if ((hud_pan.pos_y + bmap_height(hud_pcx) / 2) > (screen_size.y / 2)) // lower left corner?
{
hud_pan.pos_x = 0;
hud_pan.pos_y = screen_size.y - bmap_height(hud_pcx);
}
else // upper left corner
{
hud_pan.pos_x = 0;
hud_pan.pos_y = 0;
}
}
}

Ist der pos_x Wert hingegen klein, kommt das Panel auf die linke Seite, je nach der y-Position nach oben links oder unten links. Über eine Funktion müssen wir noch reden:

starter init_mouse()
{
mouse_map = pointer_pcx;
while (1)
{
if (key_space == on)
{
mouse_mode = 2;
}
else
{
mouse_mode = 0;
}
mouse_pos.x = pointer.x;
mouse_pos.y = pointer.y;
wait (1);
}
}
Diese Starter Funktion zeigt den Mauszeiger, wenn wir die Leertaste drücken und halten, was uns erlaubt, das Panel dorthin zu schieben, wo wir es haben wollen. Wie Sie sehen ist der Code sehr flexibel: Sie definieren das Panel und der Code erledingt den Rest.