Virtua Cup

Falls Sie jemals Virtua Cop gespielt haben, wissen Sie, dass wir über ein schnelles Actionspiel reden. Falls Sie es nicht gespielt haben, eine kurze Erklärung, worum es geht: Sie sind ein Cop und versuchen, so viele Verbrecher wie möglich zu erschießen. Klingt simpel, wie? Sie stehen still und die bösen Jungs erscheinen von Zeit zu Zeit an zufälligen Positionen. Sie schießen auf sie, sie schießen zurück... simpel, nicht wahr? Virtua Cup ist nun ein Virtua Cop Klon.

Dieses Spiel ist ein weiteres "Standalone" Beispiel, hat also eine eigene Main Funktion:
 
function main()
{
   level_load (virtuacup_wmb);
   wait (2);
   clip_size = 0;
   fps_max = 50;
   generate_enemies();
}

Nachdem das Level geladen wird, stellen wir die Framerate auf 50 fest und beginnen damit, Feinde zu erzeugen:

function generate_enemies()
{
   while (1)
   {
      waitt (48 + random (48));
      index = 3 * int(random(10));
      temp.x = enemy_pos[index + 0];
      temp.y = enemy_pos[index + 1];
      temp.z = enemy_pos[index + 2];
      ent_create(enemy_mdl, temp, enemy_shoots);
      if (index > 9) {index = 0;}
   }
}

Die Gegner erscheinen alle 3 bis 6 Sekunden an zufälligen Positionen. Ich habe ein Array definiert, in dem die xyz Werte für die gültigen Positionen gespeichert sind:

var enemy_pos[30] = -450,315,0, 60,315,0, 575,315,0, -325,540,255, -65,540,255, 195,540,255, 450,540,255, -190,850,515, 65,850,515, 320,850,515;

Niemand wollte mir diese Werte so schreiben, ich mußte sie aus dem Wed holen. Die ersten 3 Zahlen sind die xyz Koordinaten für die erste Position, die nächsten drei sind die Koordinaten für die zweite Position usw. Mein Array hat 30 Elemente, ich kann also 10 Positionen speichern. Nun zur Funktion generate_enemies:

index = 3 * int(random(10));

Dies wird eine zufällige Zahl zwischen 0 und 27 erzeugen, aber in Dreierschritten (0, 3, 6, 9, 12, ..., 27). Wir kopieren diese zufälligen Koordinaten nach temp und erzeugen einen Gegner, der die emeny_shoots Funktion nutzt:

function enemy_shoots()
{
   wait (1);
   my.pan += 270;
   my.enable_impact = on;
   my.event = kill_enemy;
   ent_create(enemygun_mdl, nullvector, attach_enemygun);
   my.skill10 = 0;
   while (my != null && my.skill10 < 100)
   {
      ent_cycle("attack", my.skill10); // play "attack" frames animation
      my.skill10 += 3 * time;
      wait (1);
   }
   snd_play (enemyshot_snd, 70, 0);
   vec_set (temp.x, player.x); // turn towards the player
   vec_sub (temp.x, enemy_gun.x);
   vec_to_angle (my.pan, temp); 
   ent_create (bullet_mdl, enemy_gun, move_bullet);
   waitt (32);
   ent_remove (me);
}

Dieser Gegner wird so gedreht, dass er den Spieler anschaut. Wenn der Gegner getroffen wird, dann wird die kill_enemy Funktion aufgerufen - dazu etwas später. Die Zeile unten und die Funktion, die danach kommt

ent_create(enemygun_mdl, nullvector, attach_enemygun);

function attach_enemygun()
{
   proc_late();
   my.passable = on;
   my.metal = on;
   while(you != null)
   {
      vec_set(my.x,you.x);
      vec_set(my.pan,you.pan);
      my.frame = you.frame;
      my.next_frame = you.next_frame;
      vec_for_vertex(enemy_gun, my, 148); 
      wait(1);
   }
   ent_remove(my); 
}

gibt dem Gegner eine Waffe. Die Kugel wird an der Position erschaffen, die durch Vertex 148 des Waffenmodels gegeben ist.

Der Gegner wird seine Attack Animation abspielen, solange er nicht getötet wird:

   while (my != null && my.skill10 < 100)
   {
      ent_cycle("attack", my.skill10); 
      my.skill10 += 3 * time;
      wait (1);
   }

Sobald der Gegner seine "Attack" Animation beendet hat, wird ein Sound abgespielt, er dreht sich zum Spieler und schießt. Der Gegner wird für zwei weitere Sekunden sichtbar bleiben, um dem Spieler den Schuß zu ermöglichen.

   snd_play (enemyshot_snd, 70, 0);
   vec_set (temp.x, player.x); 
   vec_sub (temp.x, enemy_gun.x);
   vec_to_angle (my.pan, temp); 
   ent_create (bullet_mdl, enemy_gun, move_bullet);
   waitt (32);
   ent_remove (me);

Diese Funktion bewegt die Kugel (sie ist identisch für den Spieler und die Gegner):

function move_bullet()
{
   wait (1);
   if (my == null) {return;}
   my.enable_entity = on;
   my.enable_block = on;
   my.event = remove_bullets;
   my.passable = on;
   my.pan = you.pan;
   my.tilt = you.tilt;

   my.skill1 = 0;
   bullet_speed.x = 500;
   bullet_speed.y = 0;
   bullet_speed.z = 0;
   bullet_speed *= time;
   while (my.skill1 < 50)
   {
      if (my == null) {return;}
      if (my.skill1 < 0.1)  // don't collide with the creator
      {
         my.passable = on;
      }
      else
     {
         my.passable = off;
      }
      my.skill1 += 0.1 * time;
      ent_move (bullet_speed, nullvector);
      wait (1);
   }
   remove me;
}

function remove_bullets()
{
   wait (1);
   ent_remove (me);
}

Die Kugel reagiert auf Kollisionen mit Entities und Level Blocks; sollte sie auf so etwas treffen, wird sie mit der Funktion remove_bullets entfernt. Ihren pan und tilt holt sie von dem Gegner oder dem Spieler, je nachdem, wer sie erschaffen hat... falls diese Entities also in die richtige Richtung zeigen, dann auch die Kugel - verstanden? Sie haben modifizierte Versionen von mode_bullet in anderen Aum Ausgaben gesehen.

Es ist Zeit, sich die kill_enemy Funktion näher anzusehen:

function kill_enemy()
{
   wait (1);
   exclusive_entity; // stop all the other functions for this enemy
   my.enable_impact = off; // can't be shot from now on
   my.event = null;
   dead_enemies += 1;
   my.skill10 = 0;
   snd_play (enemydead_snd, 70, 0);
   while (my.skill10 < 90)
   {
      ent_cycle("die", my.skill10); // play "die" frames animation
      my.skill10 += 5 * time;
      wait (1);
   }
   waitt (16);
   ent_remove (me);
}

Falls der Gegner von einer Kugel des Spielers getroffen wird, dann halten wir alle anderen Funktionen, die derzeit zu dem Gegner gehören an. Der Gegner wird nun nicht mehr auf weitere Kugeln reagieren und seine Event Funktion wird auf null gesetzt. Wir erhöhen die dead_enemies Variable um einen, spielen einen Sound und die "die" Animation ab. Die Leiche wird eine Sekunde lang sichtbar bleiben und dann verschwinden.

Der Gegnercode ist damit besprochen - nun zu dem Code für den Spieler:

action virtua_player // attached to the player
{
   player = me;
   player.invisible = on;
   player.skill1 = 100;
   my.enable_impact = on;
   my.event = decrease_players_health;
   camera.x = player.x;
   camera.y = player.y;
   camera.z = player.z;
   camera.pan = 90;
   while (player.skill1 > 0) // as long as the player is alive
   {
      camera.tilt += 10 * mouse_force.y * time;
      if (camera.tilt < -25) {camera.tilt += 1;}
      if (camera.tilt > 25) {camera.tilt -= 1;}
      camera.pan -= 10 * mouse_force.x * time;
      if (camera.pan < 60) {camera.pan += 1;} // 90 - 30
      if (camera.pan > 120) {camera.pan -= 1;} // 90 + 30
      if (mouse_left == 1 && bullets > 0)
      {
         player_shoots();
      }
      player.pan = camera.pan; // rotate the player too
      player.tilt = camera.tilt; // not just the camera
      if (mouse_right == 1 && bullets == 0) // right click to reload the gun
      {
         bullets = 10;
         while (mouse_right == 1) {wait (1);}
      }
      wait (1);
   }
}

Der Spieler ist unsichtbar, weil wir nicht wollen, dass er uns im Weg ist, aber keine Angst - die Gegner wissen genau, wo er ist! Der Spieler hat 100 Health Punkte (in skill1 gespeichert). Falls er von einer Kugel getroffen wird, läuft seine Event Funktion (decrease_players_health).

Solange skill1 > 0 ist (der Spieler lebt noch), kann die Kamera bewegt und geschossen werden. Ich habe die Kamerabewegung auf 60 bis 120 Grad für den Pan und -25 bis 25 Grad für den Tilt beschränkt, da alle Gegner von vorn kommen. Falls dem Spieler die Munition ausgeht, kann er mit der rechten Maustaste nachladen. Die linke Maustaste ruft die Funktion player_shoots auf, falls bullets > 0 ist:

function player_shoots()
{
   exclusive_global;
   while (mouse_left == 1) {wait (1);}
   snd_play (playershot_snd, 70, 0);
   ent_create (bullet_mdl, player.pos, move_bullet);
   bullets -= 1; // you have used a bullet
}

Player_shoots würde mehrfach aufgerufen, wenn die linke Maustaste gedrückt wird, wenn es nicht durch exclusive_global aufgehalten würde. Wir warten bis die Taste losgelassen wird, spielen einen Sound und erschaffen eine Kugel, die wir mit derselben move_bullet Funktion bewegen wie oben. Schließlich senken wir die Zahl der Kugeln um eins.

Die letzte Funktion im Spiel wird aufgerufen, wenn der Spieler getroffen wird:

function decrease_players_health()
{
   wait (1);
   if (player.skill1 > 0)
   {
      player.skill1 -= 10; // decrease player's health with 10 points every shot
   }
   else
   {
      player.skill1 = 0;
   }
}

Falls der Spieler lebt, ziehen wir 10 Punkte von seiner Health ab. Wenn der Spieler schon tot ist, setzen wir den Wert auf 0 (es ist unprofessionell, negative Health Werte anzuzeigen).
 
 

Power - ups

Manchmal müssen Sie ihrem Spieler einen Speed Boost geben. Vielleicht möchten Sie ihn auch mit mehr Health ausstatten. Oder wie wäre es mit Munitionspaketen für alle Waffen?
 
action power_health
{
   show_panels(); // display the panels
   my.metal = on;
   my.passable = on;
   while (vec_dist(my.x, player.x) > 50)
   {
      my.pan += 3 * time;
      wait (1);
   }
   my.invisible = on;
   player.light = on;
   player.lightrange = 300; // the player will glow red
   player.lightred = 100;
   snd_play (gotitem_snd, 70, 0);
   while (my.skill1 < 500) // ~ 30 seconds
   {
      if (player._armor < 100)
      {
         player._armor += 0.8 * time;
      }
      else
      {
         if (player._health < 200)
         {
            player._health += 0.5 * time;
         }
      }
      my.skill1 += 1 * time;
      wait (1);
    }
   ent_remove (me);
   player.light = off; // clean up the mess
   player.lightrange = 0;
   player.lightred = 0;
}

Die erste Zeile zeigt die Munitions / Health / Rüstungs Panals der Templates an. Das power_health Objekt rotiert, bis der Spieler nah heran kommt. Wenn dies geschieht, wird das Objekt unsichtbar und der Spieler erzeugt ein rotes Licht um ihn herum. Solange sein skill1 < 500 ist (für ca. 30 Sekunden), wird die Rüstung auf 100 Punkte angehoben und die Health nimmt zu bis sie 200 Punkte erreicht. Wenn die Zeit abgelaufen ist (skill1 > 500) verschwindet das unsichtbare power-up; wir haben es nur "am Leben erhalten", weil wir seinen skill1 benutzt haben. Der Spieler erzeugt kein Licht mehr und alles ist wieder wie vorher.

Schauen wir uns die Action power_ammo mal an:

action power_ammo
{
   my.metal = on;
   my.light = on;
   my.lightrange = 0;
   my.lightgreen = 200;
   my.transparent = on;
   my.passable = on;
   while (vec_dist(my.x, player.x) > 50)
   {
      my.pan += 1 * time;
      my.tilt += 1 * time;
      my.roll += 1 * time;
      wait (1);
   }
   ent_remove (me);
   snd_play (gotitem_snd, 70, 0);

   // 50 bullets for every weapon - use different values here
   ammo1 = 50;
   ammo2 = 50;
   ammo3 = 50;
   ammo4 = 50;
   ammo5 = 50;
   ammo6 = 50;
   ammo7 = 50;
}

Dieses Power-Up rotiert um alle Achsen (pan, tilt und roll). Wenn der Spieler nah heran kommt, verschwindet es und ammo1 bis ammo7 erhöhen sich (dies sind die Variablen für die Munition in den Templates). Sie können jeder Waffe eine unterschiedliche Anzahl von Kugeln geben oder nur Munition für bestimmte Waffen.

Das letzte power-up:

action power_speed
{
   my.metal = on;
   my.light = on;
   my.lightrange = 0;
   my.lightblue = 200;
   my.transparent = on;
   my.passable = on;
   while (vec_dist(my.x, player.x) > 50)
   {
      my.pan += 3 * time;
      my.tilt += 2 * time;
      my.roll += 1 * time;
      wait (1);
   }
   my.invisible = on;
   player.light = on;
   player.lightrange = 300;
   player.lightblue = 250;
   while (my.skill1 < 300) // ~ 20 seconds
   {
      strength.x = 10; // strength.x = 5 in templates
      strength.y = 8; // strength.y = 4 in templates
      my.skill1 += 1 * time;
      wait (1);
   }
   ent_remove (me);
   snd_play (gotitem_snd, 70, 0);
 
   // restore defaults
   strength.x = 5;
   strength.y = 4;

   player.light = off;
   player.lightrange = 0;
   player.lightblue = 0;
}

Dieses Power-Up verdoppelt die Geschwindigkeit. Der Template Spieler bewegt sich mit der Geschwindigkeit, die mit "strength" in movement.wdl angegeben ist. Wenn er dieses Power-Up aufsammelt, erzeugt er ein blaues Licht um sich und bewegt sich für etwa 20 Sekunden viel schneller. Wenn die Zeit abgelaufen ist, wird der "normale" Wert wiederhergestellt und das Licht verschwindet.

Ich wollte Ihnen die Möglichkeit geben, mit all den Power-Ups in meinem Test-Level zu arbeiten, also habe ich eine Waffe und unseren Gast Star eingefügt:

action thebutcher
{
   my.light = on;
   my.lightred = 100;
   my.lightgreen = 100;
   my.lightblue = 100;
   my.frame = 31;
   while (player == null) {wait (1);}
   while (1)
   {
      if (vec_dist(my.x, player.x) < 60 && player._health > 0)
      {
         if (player._armor > 0)
         {
            player._armor -= 3 * time;
         }
         else
         {
            player._armor = 0;
            player._health -= 2 * time;
         }
      }
      wait (1);
   }
}

Dieser Kerl wird den Spieler verletzen, wenn er (oder sie) zu nahe kommt. Nun können Sie power_health und power_ammo testen (schießen sie die Waffe leer und holen Sie dann power_ammo). Nehmen Sie auch das power_speed Item und versuchen, so weit wie möglich zu laufen... ich verfolge Sie mit der power_power_gun. :)