Auto KI

Dieses Mal können wir eine Demo spielen, also öffnen Sie carai4.wmp, lassen Sie es ausrechnen und starten es mit der carai4.wdl.

Die Autos warten bis “3” auf dem Bildschirm erscheint, aber halten Sie Ihren Finger lieber auf der “Pfeil Oben” Taste! Die Computer gesteuerten Fahrzeuge sind nicht allzu schnell, Sie sollten also meistens gewinnen. Stellen Sie diese Geschwindigkeiten so ein, dass Sie die meisten Rennen verlieren und verkaufen Sie diese Version des Spiels. Sie können auch einen anderen Wert für max_laps einsetzen, um die Rundenzahl zu ändern.

Ich habe vor einigen Monaten damit begonnen, den Code für diese KI Serie zu schreiben; Sie sollten mit diesen Artikeln vertraut sein. Wie Sie sehen beinhaltet das “Finish”-Panel, das zum Ende des Rennens angezeigt wird, nun auch den Spieler. Der Code für dieses Panel ist in etwa gleich geblieben, ich werde ihn nicht nochmals erklären. Sehen wir uns lieber die Neuerungen an:

- Wir haben ein Intro

- Die Fahrzeuge suchen sich am Ende jeder Runde einen zufälligen Pfad aus

- Der Abstand, die Geschwindigkeit und die Pfadnummer werden in einem Panel oben am Bildschirm angezeigt

Es gibt noch weitere kleinere Änderungen im Vergleich zum alten Spieler / KI Code, aber die sind mit vielen Kommentaren versehen. Schauen wir uns den Intro Code an:

starter init_race()
{
    sleep (2);
    one_ent.visible = on;
    snd_play (lo_wav, 50, 0);
    while (one_ent.x > 100)
    {
       one_ent.x -= 50 * time;
       wait (1);
    }
    one_ent.visible = off;
    two_ent.visible = on;
    snd_play (lo_wav, 50, 0);
    while (two_ent.x > 100)
    {
       two_ent.x -= 50 * time;
       wait (1);
    }
    two_ent.visible = off;
    three_ent.visible = on;
    snd_play (high_wav, 100, 0);
    while (three_ent.x > 100)
    {
       three_ent.x -= 150 * time;
       wait (1);
    }
    race_started = 1;
    three_ent.visible = off;
}

Die Funktion oben startet sich selbst, wenn das Skript läuft. Die erste Codezeile gibt uns 2 Sekunden, die Aussicht zu bewundern und macht dann die erste Zahl sichtbar (one_ent). Ich habe 3 “entity” Definitionen benutzt und 3 Sprites für die Zahlen, die beim Spielstart erscheinen. Jede Entity (Zahl) wird auf die Kamera zubewegt (der x Wert wird verringert) bis es sehr nah dran ist; dann ertönt ein Geräusch und die Zahl wird ausgeblendet, gefolgt von der nächsten. Sobald die “3” näher als 100 Quants an der Kamera ist, wird race_started auf 1 gesetzt und die “3” wird auf invisible gesetzt. Alle Fahrzeuge (auch der Spieler) sind mit einer “while” Schleife ausgestattet, die sie abwarten läßt, bis race_started auf 1 gesetzt ist, daher starten die Motoren erst dann.

Wenn Sie den Artikel in AUM33 gelesen haben, wissen Sie, dass jedes KI-Auto einen Pfad für die Bewegungen nutzt. Diese Methode hat Vorteile, aber führt mitunter zu langweiligen Rennspielen; der Spieler könnte entdecken, dass die Fahrzeuge immer denselben Wegen folgen und daher irgendwann sicher sein, dass z.B. das grüne Auto nie an die Innenseite des Kurses kommt. Keine Angst, in diesem Monat benutzen die Fahrzeuge zufällige Pfade. Sie werden überrascht sein, dass der Algorithmus ohne Zufallswerte auskommt und doch sind die Pfade zufällig ausgewählt.

Ich habe eine Array Definition benutzt, die so aussieht:

var paths[12] = 3, 1, 2, 1, 3, 2, 3, 2, 1, 1, 3, 2;

Ich habe verschiedene Farben für die Werte benutzt, um es Ihnen leichter zu machen. Die Fahrzeuge starten mit den Default Pfaden definiert über diese Variablen:

var car1_path = 1;
var car2_path = 2;
var car3_path = 3;

Schauen wir uns an, auf welche Weise die Pfade zufällig gewählt werden. Jedes KI-Fahrzeug hat einige Codezeilen wie diese:

if (waypoint_number == 66)
{
    pick_new_path();
}

Sobald eines der Fahrzeuge zum Ende der Runde kommt (waypoint_number == 66), die Funktion pick_new_path wird aufgerufen, die einen neuen Pfad für das Fahrzeug aussucht. Ein mögliches Ergebnis des Funktionsaufrufes ist im Bild oben zu sehen.

function pick_new_path()
{
    if (my.skill40 == 1) {return;} // the car has picked its path already
    my.skill40 = 1;
    index += 1; // advance one more step inside the array
    index %= 12; // limit index to the first 12 (0..11) elements in the array
    if (my.skill1 == 1)
    {
       car1_path = paths[index];
    }
    if (my.skill1 == 2)
    {
       car2_path = paths[index];
    }
    if (my.skill1 == 3)
    {
       car3_path = paths[index];
    }
    if (paths[index] == 1)
    {
       ent_path("car01");
    }
    if (paths[index] == 2)
    {
       ent_path("car01");
    }
    if (paths[index] == 3)
    {
       ent_path("car03");
    }
    sleep (10); // wait for 10 seconds
    my.skill40 = 0; // the car can pick a random path again (waypoint_number = 66 for several frames)
}

Glauben Sie mir, dass jedes Fahrzeug die waypoint_number für mehrere Frames auf 66 stehen hat. Das kommt daher, dass die Fahrzeuge nicht allzu schnell sind und der Abstand zwischen zwei aufeinander folgenden Pfadpunkten nicht allzu gering. Die Fahrzeuge sollen aber nur einmal pro Runde den Pfad ändern, also wird skill40 zunächst auf 1 gesetzt. Wir erhöhen “index”, limitieren den Wert aber auf 11, da unser Array nur von 0 bis 11 geht und holen die Pfadnummer aus dem Array. Das erste Fahrzeug hat seinen Skill1 auf 1 gesetzt, das zweite auf 2 und das dritte auf 3. Je nach dem Wert der Pfadnummer wird einem der Fahrzeuge (car01, car02 oder car03) einer der drei Pfade zugeordnet. Die letzten Zeilen lassen die Funktion 10 Sekunden pausieren (inzwischen sollte waypoint_number einen anderen Wert haben) und setzen dann skill40 auf 0 zurück, damit die Funktion am Ende der nächsten Runde erneut aufgerufen werden kann.

Vielleicht fragen Sie sich, warum die Pfade zufällig sind; warum habe ich das behauptet, wo doch die Werte im Array stets die gleichen sind? Der Trick ist, dass wir nicht wissen, welches der Fahrzeuge gerade gewinnt. Die Autos fahren mit zufälligen Geschwindigkeiten, also könnte jedes der Fahrzeuge z.B. den zweiten Pfad zugewiesen bekommen. Sie müssen nur sicherstellen, dass jedes Dreierpack von Werten genau einmal “1”, “2” und “3” enthält, damit die Fahrzeuge nicht zu oft inenander fahren.

panel race_pan
{
    bmap = race_pcx;
    pos_x = 0;
    pos_y = 0;
    layer = 10;

   digits = 323, 18, 1, system_font, 1, car1_path; // the path used by the first car
   digits = 510, 18, 1, system_font, 1, car2_path; // the path used by the second car
   digits = 703, 18, 1, system_font, 1, car3_path; // the path used by the third car

   digits = 100, 18, 3, system_font, 1, players_speed; // the speed of the 1st car
   digits = 280, 18, 3, system_font, 1, car1_speed; // the speed of the 2nd car
   digits = 470, 18, 3, system_font, 1, car2_speed; // the speed of the 3rd car
   digits = 660, 18, 3, system_font, 1, car3_speed; // the speed of the player

   digits = 64, 18, 3, system_font, 0.01, player.distance; // the distance covered by player's car
   digits = 244, 18, 3, system_font, 0.01, car1.distance; // the distance covered by the 1st car
   digits = 434, 18, 3, system_font, 0.01, car2.distance; // the distance covered by the 2nd car
   digits = 624, 18, 3, system_font, 0.01, car3.distance; // the distance covered by the 3rd car

   flags = overlay, refresh, visible;
}

Wie Sie sehen ist das Panel aus der Demo nicht allzu kompliziert; es enthält “digit” Elemente für die Pfade, die Geschwindigkeiten und zurückgelegten Distanzen. Die Werte für die Geschwindigkeit mußten allerdings angepaßt werden; 20 Quants pro Frame ist kein Wert, den man auf einem Tacho finden würde, daher habe ich eine kleine Funktion geschrieben, die passend umrechnet.

starter compute_speeds() // just makes the speeds look good, really
{
    while ((car1 == null) || (car2 == null) || (car3 == null) || (player == null)) {wait (1);}
    while (1)
    {
       car1_speed = car1.car_speed * 6; // 6 = experimental value (same read on the panel and on the speedo)
       car2_speed = car2.car_speed * 6;
       car3_speed = car3.car_speed * 6;
       players_speed = player.skill2 * 6; // player.skill2 = player's "car_speed"
       wait (1);
    }
}

Diese Funktion wartet bis alle Fahrzeuge und der Spieler existieren und dann multipliziert sie die Geschwindigkeiten mit 6 (ein experimenteller Wert); auf diese Weise erhalten wir für den Spieler auf dem Tacho und dem Panel den gleichen Wert. Die letzten Ziffern zeigen die zurückgelegten Wege in Quants (multipliziert mit 0,01) für die Fahrzeuge an; diese stehen in skill25, auch bekannt als “distance”.

Dies beendet die Auto KI Serie. Das fertige Produkt kann es mit NFS nicht aufnehmen, aber es zeigt, wie man ein Arcade Rennspiel von Grund auf angehen kann. Der Schwachpunkt ist das Kollisionssystem, weil die Fahrzeuge ineinander fahren können, ohne dass etwas geschieht. Ich weiß, dass A6 User sich was dies angeht auf das neue Kollisionssystem freuen, das für A6.3 geplant ist (keine Garantie für die Versionsnummer), aber wenn Sie nur A5 besitzen, müssen Sie mit dem Problem leben.

Oder vielleicht nicht? Es kann eine Lösung geben! Warum machen Sie keine Map Entities aus Ihren Fahrzeugen? Versuchen Sie es und teilen Sie uns allen die Ergebnisse im Forum mit. Viel Erfolg!
 

Kontext Menüs

In diesem Artikel lernen Sie, wie man mit der rechten Maustaste Popup Menüs erzeugen kann. Das Beispiel ändert die Bildschirmauflösung, aber Sie können es als Grundlage für ein voll funktionsfähiges Kontextmenü verwenden, mit mehreren Optionen, die in verschiedenen Situationen erscheinen usw.

Wir brauchen ein Panel mit 3 Knöpfen:

panel context_pan
{
    bmap = menu_pcx;
    pos_x = 0;
    pos_y = 0;
    layer = 10;
    button = 2, 2, b1_640_pcx, b2_640_pcx, b1_640_pcx, change_resolution, null, null;
    button = 2, 22, b1_800_pcx, b2_800_pcx, b1_800_pcx, change_resolution, null, null;
    button = 2, 42, b1_1024_pcx, b2_1024_pcx, b1_1024_pcx, change_resolution, null, null;
    flags = overlay, refresh;
}

Das Panel ist am Anfang nicht sichtbar; die Knöpfe verwenden alle die gleiche change_resolution Funktion. Die main Funktion lädt das Level, setzt die Bitmap für den Mauszeiger und erlaubt, dass die Maus bewegt wird.

function main()
{
    level_load (context_wmb);
    mouse_map = pointer_pcx;
    while (1)
    {
        mouse_pos.x = pointer.x;
        mouse_pos.y = pointer.y;
        wait (1);
    }
}

Schauen wir uns die Funktion an, die den Knöpfen zugeordnet ist:

function change_resolution(button_number)
{
    if (button_number == 1) // 640 x 480
    {
       video_switch (6, 0, 0);
    }
    if (button_number == 2) // 800 x 600
    {
       video_switch (7, 0, 0);
    }
    if (button_number == 3) // 1024 x 768
    {
       video_switch (8, 0, 0);
    }
    mouse_mode = 0; // now hide the pointer
}

Drückt der Spieler den ersten Knopt, schaltet die Engine auf 640 x 480 Pixel um, bei der gleichen Farbtiefe. Ebenso für den 2. und 3. Knopf (mit einer anderen Auflösung natürlich). Die letzte Codezeile verbirgt den Zeiger sobald der Knopf gedrückt wurde.

Das Menü wird mit der rechten Maustaste aufgerufen:

function show_menu()
{
    mouse_mode = 2;
    context_pan.pos_x = pointer.x;
    context_pan.pos_y = pointer.y;
    context_pan.visible = on;
}

on_mouse_right = show_menu;

Wie Sie sehen wir show_menu gestartet, sobald der Spieler die rechte Maustaste drückt; die Funktion macht die Maus sichtbar, setzt die korrekten pos_x und pos_y Werte für das Panel und macht es sichtbar.

Aber wie wird man es wieder los? Sehen wir uns diese Funktion an:

function hide_menu()
{
    context_pan.visible = off;
    wait (1);
    mouse_mode = 0;
}

on_mouse_left = hide_menu;

Diese verbirgt das Panel, wartet einen Frame und verbirgt auch den Mauszeiger.