Auto KI

In diesem Monat lernen wir, wie wir den Code für das Auto des Spielers einfügen können. Unser Code wird einen Wagen ins Spiel bringen, der automatisch schaltet, hupt, die Bremslichter aufleuchten läßt und sogar einen Tacho hat! Noch besser: das Auto kann 3 verschiedene Kameras benutzen.

Der ganze neue Code ist in playercar.wdl zu finden, das in carai3.wdl eingebunden ist, eine leicht modifizierte Version von carai2.wdl. Starten wir mit einigen einfachen Definitionen:

                           

panel speedo_pan
{
    bmap = speedo_pcx;
    pos_x = 640;
    pos_y = 450;
    layer = 10;
    flags = overlay, refresh, visible;
}

entity needle
{
   type = <needle.tga>;
   layer = 20;
   scale_x = 0.15;
   scale_y = 0.25;
   x = 200;
   y = -91.5;
   z = -65;
   view = camera;
   flags = overlay, nofilter, visible;
}

Ich benutze ein Panel und eine Nadel-Entity für den Tacho. Warum ist die Nadel eine Entity? Nun, ein Panel kann sich nicht drehen, eine Entity schon und ist daher der Aufgabe gewachsen. Sie können scale_x und scale_y manipulieren, um Breite und Länge der Nadel zu bestimmen; ändern Sie den x Wert, um die Nadel vor die Kamera zu setzen und y und z für die Position auf dem Bildschirm.

starter set_needle()
{
    var needle_angle;
    while (player == null) {wait (1);}
    while (1)
    {
        needle_angle = player.skill2 * 12.17 - 140; // -140...+140 degrees = 0...140 mph
        needle.roll = max (-140, needle_angle);
        wait (1);
    }
}

Die Funktion oben läuft von selbst; ihre Aufgabe ist es, je nach der Geschwindigkeit des Autos einen passenden Roll Winkel für die Nadel Entity zu ermitteln. Wir definieren eine lokale Variable namens needle_angle, warten bis die Entity des Spielers geladen ist und begegnen dann einer while(1) Schleife mit seltsamen Zahlen darin. Wo kommen die her?

Die Geschwindigkeit kann Werte von 0 bis 140 mph annehmen, aber die Nadel muß ihren Winkel zwischen –140 und 140 Grad ändern. Ich weiß, dass player.skill1 zwischen 0 bis 23 Quants / Frame variiert. Woher ich das weiß? Ich habe ein einfaches Panel mit einer Digit Anzeige eingesetzt, um diese Werte zu messen, wenn der Wagen steht bzw. mit maximaler Geschwindigkeit fährt. Schauen wir uns die seltsam aussehende Formel nochmal an:

needle_angle = (0 to 23) * 12.17 - 140

a) needle_angle = 0 * 12.17 - 140 = -140 degrees

b) needle_angle = 23 * 12.17 - 140 = 139.91 degrees

Die Formel oben beweist, dass die Nadel ihren Winkel von –140 bis 139,91 Grad ändern wird, wenn die Geschwindigkeit zwischen 0 und 23 Quants / Frame liegt. Die nächste Codezeile stellt sicher, dass der Winkel der Nadel nicht unter –140 Grad sinkt.

Ich werde die Funktion für die Hupe des Spielers nicht näher betrachten, weil sie wirklich einfach ist – sehen Sie selbst:

function horn()
{
    snd_play (plhorn_wav, 100, 0);
}

on_h = horn;

Hier ist eine weitere einfache Funktion, die von der Action des Spielers aufgerufen wird; diese Funktion erlaubt es uns, eine der 3 Kamerasichten zu wählen, von denen ich erzählt habe:

function car_camera()
{
    if (key_1 == on) {camera_number = 1;}
    if (key_2 == on) {camera_number = 2;}
    if (key_3 == on) {camera_number = 3;}
    if (camera_number == 1) // top view
    {
       camera.x = player.x;
       camera.y = player.y;
       camera.z = player.z + 900; // play with this value
       camera.pan = player.pan;
       camera.tilt = -90;
    }

Der Spieler kann sich die Kamera aussuchen, indem er die “1”, “2” oder “3” Taste drückt. Wird die “1” gedrückt, erhält die Kamera die gleiche x und y Position wie das Auto, aber wird 900 Quants darüber plaziert und schaut nach unten.

    if (camera_number == 2) // simulated first person view
    {
       camera.x = player.x - 20 * cos(player.pan);
       camera.y = player.y - 20 * sin(player.pan);
       camera.z = player.z + 18;
       camera.pan = player.pan;
       camera.tilt = 0;
    }

Wird die “2” gedrückt, so aktiviert dies die Egoperspektive. Diese ist eigentlich eine isometrische Ansicht, die 20 Quants hinter dem Ursprung des Autos und 18 Quants darüber plaziert wird.

    if (camera_number == 3) // true isometric view
    {
       camera.x = player.x - 200 * cos(player.pan); // 200 = distance
       camera.y = player.y - 200 * sin(player.pan); // same value here
       camera.z = player.z + 150; // above the player
       camera.pan = player.pan;
       camera.tilt = -27; // look down at the player
    }
    wait (1);
}

Wird die “3” gedrückt, so aktiviert dies eine echte isometrische Ansicht. Die Kamera wird 200 Quants hinter dem Auto und 150 Quants darüber plaziert; der Tilt Winkel von –27 stellt sicher, dass der Spieler genug von der Straße vor ihm sieht.

Soviel zum einfachen Teil des Artikels. Bereiten Sie sich nun innerlich auf die Action vor, die das Auto des Spielers steuert (unheimliches Lachen hier einsetzen).

action players_car
{
     var players_engine; // engine sound handle
     var player_dist;
     var player_speed;
     var minimum_z;
     player = my; // I'm the player
     players_engine = ent_playloop (my, plengine_wav, 50);

Ich habe einige lokale Variablen definiert und den “player” Pointer auf die Entity des Autos gesetzt. Plengine.wav wird in einer Schleife abgespielt.

     while (1)
     {
          player_speed.x = movement_speed * (key_cuu - 0.4 * key_cud); // backwards movement speed = 40%
          my.skill1 = player_speed.x * time + max (1 - time * 0.02, 0) * my.skill1;
          player_dist.x = my.skill1 * time;
          player_dist.y = 0;

          vec_set (temp, my.x);
          temp.z -= 1000;
          trace_mode = ignore_me + scan_texture;
          player_dist.z = -trace (my.x, temp) - my.min_z;

Der Spieler beschleunigt / bremst mit den Pfeiltasten “Hoch” bzw. “Runter”. Die Rückwärtsgeschwindigkeit kann allerdings maximal das 0,4-fache der maximalen Geschwindigkeit betragen, also 40% davon. Die folgende Codezeile berechnet die Geschwindigkeit des Wagens; ich habe einen Reibungsfaktor von 0,02 eingesetzt, aber Sie können hier mit anderen Werten experimentieren. Player_dist.x wird noch um time korrigiert, bevor sich der Wagen bewegt; die y Komponente der Geschwindigkeit ist 0.

Die nächste Zeile bringt das Auto auf die korrekte Höhe (z-Koordinate). Mit einem trace ermitteln wir die Höhe des Bodens und den Namen der Textur unter dem Auto.

Die rote Linie stellt den trace dar; der grüne Pfeil die Distanz, die von trace zurückgeliefert wird. Wir ziehen noch my.min_z ab, das ist der Abstand zwischen dem Punkt des Autos mit der geringsten z-Koordinate und dem Ursprung des Auto-Models. Wenn Sie ein animiertes (z.B. zerstörbares) Model für Ihr Auto benutzen, verwenden Sie vec_for_min, um in jedem Frame den korrekten min_z Wert zu haben. Code dafür finden Sie im playercar.wdl Skript.

          if (str_cmpi (tex_name, "tilegrass"))
         {
            if (movement_speed > 0.1)
            {
                movement_speed -= 5 * time;
            }
             else
            {
               movement_speed = 0.1;
            }
         }
         else
         {
              if (movement_speed < 0.9)
             {
                movement_speed += 10 * time;
             }
            else
            {
               movement_speed = 0.9;
            }
         }

Falls die Textur unter dem Auto “tilegrass” ist, wird die Geschwindigkeit des Wagens verringert, bis sie unterhalb von 0,1 liegt. Ist dies nicht der Fall, erhöht der Wagen die Geschwindigkeit bis maximal 0,9. Auf diese Weise wird der Spieler nicht dazu ermuntert, mit Hilfe von “Abkürzungen” zu cheaten.

         move_mode = ignore_passable + glide;
         my.skill2 = ent_move (player_dist, nullvector); // now ent_move the player
         if (my.skill2 > 0) // change gears automatically, depending on the value stored in skill2
         {
             engine_freq = 15;
         }
         if (my.skill2 > 8)
         {
            engine_freq = 20;
         }
         if (my.skill2 > 15)
         {
            engine_freq = 22;
         }
        if (my.skill2 > 19)
        {
           engine_freq = 23.5;
        }

Das Auto ignoriert passierbare Entities und gleitet an Hindernissen entlang; wir speichern das Resultat von ent_move in my.skill2 (das ist die tatsächlich zurückgelegte Distanz im letzten Frame). Falls dieser Wert positiv ist, wählt das Auto den passenden Gang, je nach der Geschwindigkeit. Ich habe die Faktoren für das Tuning des Motorengeräusches je nach skill2 mit Hilfe von Trial & Error gefunden, Sie dürfen gern Ihre eigenen Werte finden.

       player_speed.pan = rotation_speed * (key_cul - key_cur) * my.skill2;
      my.skill3 = time * player_speed.pan + max (1 - time * 0.7, 0) * my.skill3;
      my.pan += my.skill3 * time;

      my.skill4 = max (50, my.skill2 * engine_freq); // play an engine sound at the minimum speed too
      snd_tune (players_engine, 50, my.skill4, 0); // tune the engine sound

Das Auto kann nur dann um Kurven fahren (seinen Pan ändern), wenn my.skill2 größer ist als 0, was der Fall ist, wenn der Wagen sich vorwärts bewegt. Mit einer ähnlichen Formel berechnen wir die Rotationsgeschwindigkeit, dieses Mal mit größerer Reibung. Auch diese Geschwindigkeit wird mit time korrigiert.

Dann ändern wir die Frequenz des Motorengeräusches je nach dem Wert, der in my.skill4 steht – schauen wir uns an, wie dieser ermittelt wird:

      my.skill4 = max (50, my.skill2 * engine_freq); // play an engine sound at the minimum speed too

Ich möchte gern ein Motorengeräusch hören, auch wenn das Auto steht; daher ist my.skill4 in jedem Frame das Maximum von 50 und my.skill2 * engine_freq. Wenn das Auto steht, ist das Motorengeräusch also mit “50” getuned; ansonsten hängt die Frequenz von der Geschwindigkeit und dem Gang ab, in dem das Auto fährt (über den Wert, der durch engine_freq gegeben wird).

        if (key_cud == on)
       {
            my.skin = 2;
       }
       else
      {
         my.skin = 1;
      }

     car_camera(); // update the camera position
     wait(1);
   }
}

Falls die “Pfeil Unten” Taste gedrückt wird, bremst der Spieler, also benutzt der Wagen den zweiten Skin, der mit den eingeschalteten Bremslichtern. Ist die Taste nicht gedrückt, wird der normale, erste Skin benutzt. Diese Methode ist simpel und effektiv, verbraucht aber zusätzlichen Grafikspeicher (ca. 100 kB mehr für das Auto des Spielers). Die letzten Zeilen rufen die car_camera Funktion auf und weisen die Engine an, einen Frame zu warten.

Kommen Sie, dieser Code war doch gar nicht so kompliziert! Schließlich soll er nur für ein simples Arcade-Spiel reichen und nicht für einen Simulator! Im nächsten Monat fügen wir alles bisher gelernte zusammen und fügen noch hinzu, was zu unserem Rennspiel fehlt.

 

Rpg Bewegung

Im Leben eines Mannes kommt die Zeit, wo er beweisen muß, dass er ein richtiger Mann ist. Dann ergreift er seine treue Maus und beginnt, eine Horde Monster nach der nächsten zu töten, um zu zeigen, dass er nicht nur ein Träumer ist, sondern ein Dichter, ein Gitarrenspieler und ein... ja, ein mächtiger Krieger.

Ich wollte ein eigenständiges Projekt erstellen, auf diese Weise können Sie leichter neue Features hinzufügen. Was wird im Skript enthalten sein? Ein Spieler, der sich unter Berücksichtigung der Gravitation bewegt, dabei seine “walk” und “stand” Animationen verwendet und sich zu der Position bewegt, die durch den Mausklick angegeben wird. Sie können eine beliebige Position und einen beliebigen Winkel für die Kamera wählen, das macht keinen Unterschied; um zu beweisen, dass der Mausklick korrekt ermittelt wird, verwende ich eine frei schwenkbare Kamera.

function main()
{
  level_load (rpg1_wmb);
  mouse_map = pointer_pcx;
  mouse_mode = 2;
  camera.x = player.x;
  camera.y = player.y;
  camera.z = 200;
  camera.pan = 90; // initial pan angle
  camera.tilt = -60;
  while (1)
  {
      mouse_pos.x = pointer.x;
      mouse_pos.y = pointer.y;
      if ((mouse_pos.x < 2) && (camera.x > -2500)) {camera.x -= 10 * time;}
      if ((mouse_pos.x > screen_size.x - 2) && (camera.x < 2500)) {camera.x += 10 * time;}
      if ((mouse_pos.y > screen_size.y - 2) && (camera.y < 2500)) {camera.y -= 10 * time;}
      if ((mouse_pos.y < 2) && (camera.y > -2500)) {camera.y += 10 * time;}
      wait (1);
    }
}

Die Main Funktion lädt das Level, setzt pointer_pcx als Mauszeiger, zeigt ihn an und setzt die Startwerte für x, y, z, pan und tilt für die Kamera. Sie können mit diesen Werten spielen, bis Ihr Spiel gut aussieht (Tipp: wählen Sie einen großen Wert für camera.z, um die scheußlich aussehenden Models zu verdecken :)

Der Code in der Schleife aktualisiert in jedem Frame die Mausposition und erzeugt eine frei bewegliche Kamera. Diese wird sich in x und y Richtung zwischen –2500 und 2500 Quants bewegen. Diese Werte habe ich dem WED entnommen (es sind meine Levelgrenzen), aber Sie können hier andere Werte nehmen, je nach der Größe Ihres Levels.

Reden wir über das erste Bild: wenn wir die Maus an die linke Bildschirmseite bewegen (mouse_pos.x < 2) und camera.x > -2500 (die linke Grenze für den x Wert), subtrahieren wir 10 * time von camera.x und bewegen damit die Kamera nach links. Analog geschieht dies, wenn der Mauszeiger an der rechten, oberen bzw. unteren Seite des Bildschirms steht.

Schauen wir uns nun den Code für den Spieler an.

entity* destination;

action warlock
{
  player = me;
  while (1)
  {
      my.skill1 = 5 * time;
      my.skill2 = 0;
      vec_set (temp, my.x);
      temp.z -= 3000;
      trace_mode = ignore_you + ignore_passable + use_box;
      my.skill3 = -trace (my.x, temp);

Beachten Sie, dass ich einen Zeiger namens “destination” definiert habe; jedes Mal, wenn ein Punkt im Level angeklickt wird, weisen wir der Zielentity (das ist das rote Ding, das am Mauszeiger erscheint) diesen Zeiger zu. Der Spieler bewegt sich mit Hilfe seines skill1 (das ist seine Geschwindigkeit) und skill3 (für die Gravitation).

      if (destination != null)
      {
          vec_set (temp.x, destination.x);
          vec_sub (temp.x, my.x);
          vec_to_angle (my.pan, temp);
          my.tilt = 0; // don't bow :)
          if (vec_dist (my.x, destination.x) > 40)
          {
              move_mode = ignore_passable;
              ent_move (my.skill1, nullvector); // moves using skill1..3
              ent_cycle("walk", my.skill46);
              my.skill46 += 10 * time; // "walk" animation speed
              my.skill46 %= 100; // loop animation
          }
          else
           {
               destination.invisible = on;
               ent_cycle("idle", my.skill46); // play "stand" frames animation
               my.skill46 += 2 * time; // "stand" animation speed
               my.skill46 %= 100; // loop animation
           }
        }
        wait (1);
    }
}

Wenn der “destination” Zeiger nicht null ist, dann dreht sich der Spieler dorthin; dies könnte auch seinen tilt Winkel ändern, daher wird dieser mit der nächsten Codezeile wieder auf 0 zurückgesetzt. Wenn der Abstand zwischen dem Spieler und dem Ziel größer ist als 40 Quants, ignoriert er alle passierbaren Objekte und spielt seine “walk” Animation in einer Schleife ab. Ist die Distanz allerdings kleiner oder gleich 40 Quanrs (“else”), wird der rote Trank unsichtbar; das Spielermodel spielt dann seine “stand” Animation ab, bis der Spieler erneut die linke Maustaste drückt.

on_mouse_left = set_target;

Jedes Mal, wenn die linke Maustaste gedrückt wird, läuft die Funktion set_target. Tun wir für einen Augenblick so, als mache jemand aus dem Level heraus ein Bild von Ihnen, während Sie diese Demo spielen.

Ich wußte nicht, dass Sie so jung sind!

Die dicke blaue Linie ist die Oberfläche Ihres Monitors. Sie haben irgendwo auf dem Bildschirm die linke Maustaste gedrückt und erwarten nun, dass der Zauberer sich zu dieser Position in der 3D Welt bewegt! Wie funktioniert das? Sehen wir uns die Funktion set_target an.

function set_target()
{
  var pos1;
  var pos2;
  pos1.x = mouse_pos.x;
  pos1.y = mouse_pos.y;
  pos1.z = 0;
  vec_for_screen (pos1, camera);
  pos2.x = mouse_pos.x;
  pos2.y = mouse_pos.y;
  pos2.z = 20000; // use a big value here
  vec_for_screen (pos2, camera);
  trace (pos1, pos2); // now "target" holds the coordinates of the hit point
  destination = ent_create (destination_mdl, target, show_target);
}

Ich habe zwei lokale Variablen definiert, die folgendermaßen belegt werden:

- pos1 wird die Position der Maus als 3D Koordinate speichern, die nahe der Monitorfläche liegt;

- pos2 wird die Position der Maus als 3D Koordinate speichern, die 20.000 Quants von den Augen des Spielers entfernt ist.

Dies ist genau, was die vec_for_screen Anweisung tut! Sie geben eine x, y Position in der 2D Welt des Monitors und eine gewünschte Tiefe und die Anweisung liefert die gewünschte Position in der 3D Welt zurück. Unsere Funktion führt nun einen trace zwischen pos1 und pos2 aus, um die Koordinaten für “target” zu erhalten. Target ist ein vordefinierter Vektor, der automatisch auf die 3D Welt Koordinaten gesetzt wird, an denen der trace (pos1, pos2) auf ein Hindernis (den Boden) stößt, wie der grüne Pfeil im Bild oben zeigt.

Die letzte Zeile der Funktion erzeugt die “destination” Entity an der Position, die durch “target” angegeben ist und gibt ihr die show_target() Action.

function show_target()
{
    my.passable = on;
    while (mouse_left == 1) {wait (1);}
    while (mouse_left == 0) {wait (1);}
    ent_remove (me);
}

Diese kleine Funktion macht die Entity passierbar und wartet, bis der Spieler die linke Maustaste losläßt. Sobald er die linke Maustaste erneut drückt (ein neues Ziel wird gesetzt), wird die alte Ziel Entity entfernt.

Sie können (und sollten) den Code in diesem Artikel mit dem Schwertkampfcode aus AUM12 kombinieren, um einen guten Ausgangspunkt für Ihr Spiel zu haben.