Geskriptete Zwischensequenzen

Die Welt ändert sich! Die guten alten 256-Farben Videos, die einige Jahre zuvor für Zwischensequenzen verwendet wurden, werden mehr und mehr durch geskriptete Sequenzen ersetzt, die in der Game Engine selbst laufen.

Dieser Artikel wird die am häufigsten genutzten Techniken diskutieren.Schauen wir uns das Szenario an:

1) Einige Soldaten unterhalten sich in der Nähe von zwei Trucks, die sie bewachen sollen;

2) Ein Helikopter landet leise in der Nähe, ohne gesehen zu werden. Zwei maskierte Männer steigen aus und laufen auf die Trucks zu;

3) Ein Van fährt geräuschvoll vorbei und zieht die Aufmerksamkeit der Wachen auf sich;

4) Einer der maskierten Männer schleicht hinter einen der Trucks, plaziert einen Sprengsatz und eilt davon;

5) Die Trucks explodieren und die Soldaten kommen um.

All diese Dinge wurden mit Hilfe von simplen Actions und Funktionen erstellt:

function main()
{
    fps_max = 40;
    level_load (cutscene_wmb);
    wait (3);
    camera.x = -800;
    camera.y = -1100;
    camera.z = 50;
    camera.pan = 115;
    camera.tilt = -10;
}

Es ist wirklich sehr empfehlenswert, die Framerate für die Dauer der Sequenz fest vorzugeben; Sie werden in einer Minute sehen, wieso. Wir laden das Level, warten, bis das geschehen ist und setzen die Anfangsposition für die Kamera:

Dieses Model hat die Action soldier_1:

action soldier_1
{
    soldier1 = my;
    while (vec_dist (my.x, soldier2.x) > 150)
    {
       ent_cycle("stand", my.skill20);
       my.skill20 += 0.25;
       if (my.skill20 == 19)
       {
            snd_play (radio_wav, 20, 0);
       }
       wait (1);
    }
    my.skill20 = 0;
    snd_play (allquiet_wav, 50, 0);
    while (my.skill20 < 95)
    {
       ent_cycle("salut", my.skill20);
       my.skill20 += 5 * time;
       wait (1);
    }

    snd_play (isee_wav, 60, -30); // bigger volume for the left channel

Der erste Soldat wird warten, bis der Abstand zwischen ihm und dem höherrangigen soldier2 geringer als 150 Quants ist und durchläuft dabei seine “stand” Animation. Beachten Sie, dass ich keinen “time”-korrigierten Wert für die Animation benutzt habe, sondern einen festen Wert (0,25), weil ich den radio.wav Sound bei einem bestimmten Frame ertönen lassen will. Wir brauchen auch keine korrigierten Werte, weil die Framerate festgelegt ist.

Der Offizier ist nun nahe herangekommen (vgl. das erste Bild), also sagt der erste Soldat seinen allquiet.wav Spruch und salutiert. Der selbe Soldat spielt dann auch den Satz des Offiziers (isee.wav), allerdings lauter auf dem linken Kanal, wegen der Balance von –30. Der Punkt ist, dass es egal ist, aus welcher Action heraus Dinge stattfinden, solange es echt wirkt.

    sleep (2);
    media_play("soundtrack.wav", null, 20); // start the soundtrack
    chopper_moves = 1;
    camera.x = 300;
    camera.y = -2700;
    camera.z = 50;
    while (start_van == 0) {wait (1);}
    while (van1.x < 1000)
    {
       vec_set(temp, van1.x);
       vec_sub(temp, my.x);
       vec_to_angle (my.pan, temp); // rotate the soldier towards the van
       wait (1);
    }
    snd_play (itsnothing_wav, 80, 0);
    while (start_explosion == 0) {wait (1);}
    sleep (0.2);
    while (my.y > -3000)
    {
       my.y -= 50 * time;
       my.x -= 5 * time;
       my.z += 30 * time;
       my.roll += 50 * time;
       wait (1);
    }
    sleep (9); // experimental value
    beep; // you would load a new (playable) level here
}

Zwei Sekunden vergehen und der Soundtrack beginnt. Die Variable “chopper_moves” wird auf 1 gesetzt, was dem Helikopter das Startsignal gibt und wir bringen die Kamera in eine neue Position. Soldier1 geschieht nun nichts mehr, bis start_van auf 1 gesetzt ist. Diese Variable wird von dem Van genutzt, der die Wachen ablenken wird. Wie auch immer, wenn dieser sich in Bewegung setzt, werden die beiden Soldaten ihm hinterherschauen; wenn Sie die A6 besitzen, sollten Sie dafür Bones verwenden, aber ich wollte hier sicherstellen, dass der Code auch mit A5 funktioniert.

Die x-Position des Vans ist nun größer als 1000, also wird der erste Soldat itsnothing.wav “sagen”. Dann wird er warten, bis “start_explosion” auf 1 gesetzt wird und dann, nach weiteren 0,2 Sekunden, wird er davonfliegen, wie im Bild unten:

Wir warten weitere 9 Sekunden (ein experimenteller Wert) und lassen dann einen Beep ertönen; normalerweise sollte hier die level_load Anweisung hin, die Ihr spielbares Level lädt.

action soldier_2
{
    soldier2 = my;
    sleep (6); // wait for 6 seconds
    while (vec_dist (soldier1.x, my.x) > 70)
    {
       ent_cycle("run", my.skill20);
       my.skill20 += 4 * time;
       my.skill20 %= 100; // loop the animation
       my.y += 5 * time;
       wait (1);
    }
    my.skill20 = 0;
    while (my.skill20 < 95)
    {
       ent_cycle("salut", my.skill20);
       my.skill20 += 3.5 * time;
       wait (1);
    }

Der Offizier wartet 6 Sekunden, was uns genug Zeit gibt, den ersten Soldaten anzusehen. Dann wird er sich auf ihn zubewegen, indem sein y-Wert in einer Schleife erhöht wird, wobei seine “run” Animation durchlaufen wird. Ist er näher als 70 Quants, so hält er an und zeigt seine “Salut” Animation.

    while (start_van == 0) {wait (1);}
    while (van1.x < 1000)
    {
       vec_set(temp, van1.x);
       vec_sub(temp, my.x);
       vec_to_angle (my.pan, temp); // rotate the soldier towards the van
       wait (1);
    }
    while (start_explosion == 0) {wait (1);}
    sleep (0.3);
    while (my.y > -3000)
    {
       my.y -= 50 * time;
       my.x -= 10 * time;
       my.z += 30 * time;
       my.roll += 50 * time;
       wait (1);
    }
}

Auch der Offizier wird stillstehen, bis start_van auf 1 gesetzt wird und wird dann, ebenso wie der erste Soldat dem Van nachschauen, bis dessen x-Position größer als 1000 ist. Auch der Offizier wird auf die Explosion warten und dann nach einer Pause von 0,3 Sekunden ebenso wie soldier1 davongeschleudert werden.

action john_doe
{
    my.skill20 = random(100); // set a random animation start for every dummy soldier
    while (start_explosion == 0)
    {
       ent_cycle("stand", my.skill20);
       my.skill20 += 1 * time;
       my.skill20 %= 100; // loop the animation
       wait (1);
    }
    sleep (0.5 + random(1) / 3);
    snd_play (scream_wav, 80, 0);
    my.skill10 = my.x;
    while (my.x > my.skill10 - 1000)
    {
          my.x -= 50 * time;
          if (my.x > my.skill10 - 500)
          {
             my.z += 20 * time;
             if (my.tilt < 180) {my.tilt += 50 * time;}
         }
         else
         {
            ent_cycle ("death", 95);
            my.z -= 20 * time;
         }
         wait (1);
    }
    my.z = 20;
}

Diese Action ist den sechs Soldaten zugewiesen, die nahe am zweiten Truck stehen. Sie beginnen mit einem zufälligen Frame und durchlaufen einfach ihre “stand” Animation, bis start_explosion auf 1 gesetzt wird. Dann warten sie 0,5 bis 0,83 Sekunden, lassen den “scream.wav” Sound hören und fliegen ebenfalls. Dieses Mal wollte ich sie auch zeigen, wie sie am Boden liegen, also habe ich eine andere Methode gewählt: sie speichern ihre ursprüngliche x-Position (in skill10) und fliegen, bis dieser Wert um 1000 Quants geringer ist. Die letzte Codezeile setzt ihre z-Position auf einen angemessenen Wert (20).

action chopper
{
    var chopper_handle;
    var animation_speed = 20; // chopper's animation speed
    while (chopper_moves == 0) {wait (1);} // wait until the chopper is allowed to move
    while (my.z > 35)
    {
       ent_cycle("fly", my.skill20); // play "fly" animation
       my.skill20 += animation_speed * time;
       my.skill20 %= 100; // loop the animation
       my.z -= 10 * time;
       my.y -= 20 * time;
       vec_set(temp, my.x);
       vec_sub(temp, camera.x);
       vec_to_angle (camera.pan, temp); // rotate the camera towards the chopper
       wait (1);
    }
    start_specops = 1;
    while (animation_speed > 0)
    {
       if (!snd_playing(chopper_handle))
       {
            chopper_handle = ent_playsound (my, chopper_wav, 100); // the sound is louder this time
            snd_tune (chopper_handle, 0, 25 * animation_speed, 0);
       }
       ent_cycle("fly", my.skill20); // play "fly" animation
       my.skill20 += animation_speed * time;
       my.skill20 %= 100; // loop the animation
       animation_speed -= 0.15 * time;
       wait (1);
    }
}

Der Helikopter wartet an einer geeigneten Position und muß warten bis die Variable namens chopper_moves auf 1 gesetzt wird. Wenn dies geschieht, durchläuft er seine “Fly” Animation in einer Schleife und verringert seinen z-Wert (setzt zur Landung an) und seinen y-Wert (er nähert sich den Trucks). Die Kamera folgt ihm bis er den Boden berührt (also sein z-Wert unter 35 fällt). Wir müssen zwei “Special Ops” erstellen, also setzen wir die Variable “start_specops” auf 1. Der Helikopter hat das chopper.wav Geräusch, wobei die Frequenz mit snd_tune nach unten reguliert wird, ebenso wie die Animationsgeschwindigkeit der “Fly” Animation.

Für die Special Ops haben wir eine Starter Funktion:

starter create_specops
{
    while (start_specops == 0) {wait (1);} // wait until the chopper has landed
    sleep (1);
    temp.x = -300;
    temp.y = 1500;
    temp.z = 35;
    ent_create (specops_mdl, temp, move_specop1);
    temp.y += 100;
    ent_create (specops_mdl, temp, move_specop2);
}

Diese Funktion wartet bis start_specops auf 1 gesetzt ist, wartet eine weitere Sekunde und wählt dann eine Startposition für die maskierten Männer, erstellt die Models und gibt ihnen die Actions move_specop1 und move_specop2:

function move_specop1()
{
    specop1 = my;
    my.pan = 270;
    while (my.y > -650)
    {
       ent_cycle("run", my.skill20);
       my.skill20 += 10 * time;
       my.skill20 %= 100; // loop the animation
       my.y -= 15 * time;
       if (my.x > -450) {my.x -= 1 * time;}
       if (my.y < 600)
       {
           camera.x = -145;
           camera.y = 2060;
           camera.z = 200;
           camera.pan = 260;
           camera.tilt = -10;
       }
       wait (1);
    }

Dieser macht die Hauptarbeit, ich weiß gar nicht, warum sie noch einen zweiten angeheuert haben! Er wählt einen geeigneten Winkel und rennt dann los, bis sein y-Wert unter –650 sinkt (das ist nahe der Straßenecke). Wir geben ihm auch eine Bewegung in x-Richtung, aber nur wenn x größer als –450 ist. Sobald die y-Koordinate kleiner ist als 600, wird eine neue Kameraposition gewählt, eine hinter dem Helikopter:

    start_van = 1;
    my.y = -710;
    my.pan = 180;
    while (my.x > -800)
    {
        ent_cycle("run", my.skill20);
        my.skill20 += 7 * time;
        my.skill20 %= 100; // loop the animation
        my.x -= 8 * time;
        wait (1);
    }
    ent_cycle("standcrouch", 90); // crouch behind the truck
    sleep (5); // plant the bomb :)
    my.pan = 0;
    while (my.x < 0)
    {
        ent_cycle("run", my.skill20);
        my.skill20 += 7 * time;
        my.skill20 %= 100; // loop the animation
        my.x += 8 * time;
        wait (1);
    }
    sleep (1); // wait a bit and then trigger the explosion
    start_explosion = 1;
}

Der Van bewegt sich jetzt, also wählt der Maskierte eine neue Position und Ausrichtung und läuft dann auf den Truck zu, indem sein x-Wert verringert wird. Diese Bewegung setzt sich fort, bis x kleiner wird als –800 Quants; ist dies der Fall, geht der Mann in die Hocke (“standcrouch”) und wartet 5 Sekunden (die Bombe wird plaziert). Dann ändert er wieder seinen Pan und eilt vom Truck fort.

Wir warten eine Sekunde und setzen dann start_explosion auf 1, um die lang erwartete Explosion auszulösen.

function move_specop2()
{
    specop2 = my;
    my.pan = 270;
    while (start_van == 0)
    {
       my.x = specop1.x;
       my.y = specop1.y + 100;
       ent_cycle("run", my.skill20);
       my.skill20 += 10 * time;
       my.skill20 %= 100; // loop the animation
       wait (1);
    }
}

Der zweite Maskierte wählt einen passenden Winkel und folgt dann dem ersten Mann, bis start_van auf 1 gesetzt wird. Ruhe in Frieden!

action van
{
    van1 = my;
    while (start_van == 0) {wait (1);}
    camera.x = -150;
    camera.y = -1700;
    camera.z = 320;
    camera.pan = 140;
    camera.tilt = -25;
    snd_play (van_wav, 20, 0);
    while (my.x < 2000)
    {
       my.x += 20 * time;
       wait (1);
    }
}

Das ist eine kleine Action. Der Van wartet darauf, dass start_van auf 1 gesetzt wird und ändert dann die Position und Ausrichtung der Kamera. Motorengeräusche sind zu hören und der Van vergrößert seinen x-Wert, bis dieser 2000 Quants übersteigt.

Der erste Truck besteht aus zwei verschiedenen Models:

action truck1_part1
{
    while (start_explosion == 0) {wait (1);}
    snd_play (explosion1_wav, 100, 0);
    sleep (0.2);
    snd_play (explosion2_wav, 100, 0);
    while (my.y > -3000)
    {
       my.y -= 50 * time;
       my.z += 30 * time;
       my.roll += 50 * time;
       camera.roll = 2 - random(4); // shake the camera a bit
       wait (1);
    }
    camera.roll = 0; // reset camera's roll angle
}

Der erste Teil des Trucks wartet bis start_explosion auf 1 gesetzt wird und läßt dann zwei Explosionsgeräusche ertönen. Der Tank (truck1_part1) fliegt in den Himmel und erhöht seinen z-Wert dabei um 30*time. Die Kamera wählt einen zufälligen Roll zwischen –2 und 2; das Ende der Explosion setzt den Winkel wieder zurück. Was geschieht mit dem zweiten Teil des Trucks?

action truck1_part2
{
   while (start_explosion == 0) {wait (1);}
   sleep (0.5);
   snd_play (explosion1_wav, 100, 0);
   while (1)
   {
      temp.x = my.x -70;
      temp.y = my.y;
      temp.z = my.z + 50;
      effect (fire_fx, 1, temp, normal);
      wait (1);
   }
}

Auch dieser Teil wartet bis start_explosion auf 1 steht, läßt dann das Explosionsgeräusch ertönen und erzeugt ein Feuer bestehend aus Partikeln an der Position, die durch temp gegeben ist.

action truck2
{
    while (start_explosion == 0) {wait (1);}
    sleep (0.4);
    snd_play (explosion1_wav, 100, 0);
    sleep (0.1);
    snd_play (explosion2_wav, 100, 0);
    snd_play (explosion1_wav, 100, 0);
   while (my.x > -1400)
    {
        my.x -= 20 * time;
        my.z += 3 * time;
        if (my.roll < 180) {my.roll += 50 * time;}
        camera.roll = 2 - random(4); // shake the camera a bit
        wait (1);
    }
    camera.roll = 0; // reset camera's roll angle
    while (1)
    {
       temp.x = my.x -70;
       temp.y = my.y;
       temp.z = my.z + 80;
       effect (fire_fx, 1, temp, normal);
       wait (1);
    }
}

Der zweite Truck wartet ebenfalls auf die Explosion und läßt dann mehrere Explosionsgeräusche ertönen, bewegt sich ein Stück nach hinten (sein x-Wert wird kleiner) und überschlägt sich dabei (der Roll Wert ändert sich von 0 auf 180 Grad). Die Kamera wird ein bißchen wackeln und dann wieder stabilisiert. Die letzte Schleife erstellt den gleichen Feuereffekt an einer geeigneten Position.

function fire_fx()
{
    temp.x = random(20) - 10;
    temp.y = random(20) - 10;
    temp.z = random(10);
    vec_add (my.vel_x, temp);
    my.alpha = 20 + random(80);
    my.bmap = fire_tga;
    my.size = 200;
    my.bright = on;
    my.move = on;
    my.lifespan = 4;
    my.function = fade_away;
}

function fade_away()
{
    my.alpha -= 2 * time;
    if (my.alpha < 0) {my.lifespan = 0;}
}

Die Funktion für das Feuer ist typisch: es wählt zufällige Werte für die Geschwindigkeit der Flammen und eine zufällige Transparenz zwischen 20 und 100 und gibt jedem einzelnen Partikel die Funktion fade_away. Diese verringert einfach den Alpha-Wert und entfernt das Partikel, wenn dieser unter 0 sinkt. Sie sollten noch etwas Rauch hinzufügen – ich wollte das Feuer so simpel wie möglich halten.

Das war es im Großen und Ganzen. Natürlich können Sie diesen Code nicht einfach in Ihr Spiel übernehmen, weil er ja entscheidend von der Handlung abhängt und davon, wie die Umgebung aussieht. Die gute Nachricht allerdings ist, dass Sie nun einige Vorlagen haben, die helfen sollten. Noch ein letzter Rat: die Kamera bleibt wo sie ist, wenn Sie ihre x, y, z, pan, tilt und roll Werte nicht in einer Schleife ändern. Verstehen Sie mich nicht falsch: While Schleifen und Kameras kommen normalerweise gut miteinander aus, aber Sie sollten die Schleife beenden, wenn Sie die Kamera woanders hin bewegen möchten.