Musik

Die gute Nachricht über “gewöhnliche” Musik ist, daß viele ihrer Teile im Laufe des Stücks mehrmals wiederholt werden. Dies verschwendet Platz und erhöht die Größe des Spiels. Vielleicht haben Sie auch einfach einige Teile guter Musik und möchten daraus einen Song zusammensetzen. Falls Sie nach Code gesucht haben, der so etwas kann, dann sind Sie fündig geworden: music.wdl.

Mein Beispiel benutzt 4 Wave Dateien (zusammen ca. 1 Mb) und erstellt daraus einen Song, welcher normalerweise 3Mb groß wäre. Stellen Sie sich vor, was Sie mit 8 Dateien (2 Mb) machen könnten und wie viel Platz es sparen würde! Die Idee ist wieder einmal leicht: wir erstellen eine kleine Textdatei mit einem Editor und schreiben einige Zahlen hinein; dies ist der Inhalt der music.dat Datei (Sie können sie auch anders benennen):

1 1 4 4 3 3 1 2 2 3 3

Wie gesagt benutze ich 4 Dateien; die Textdatei sagt der Engine, dass sie Stück 1 zweimal spielen soll, dann Stück 4 zweimal, Stück 3 zweimal, 1 einmal, 2 zweimal und schließlich noch zweimal 3. Sobald eine Datei abgespielt wurde, wird der nächste Eintrag aus music.dat gestartet. Wenn einige Teile sehr oft wiederkehren, sparen Sie eine Menge Platz.

Und hier ist nun der Code, der aus der Acknex Engine einen Sequenzer macht:

var number_of_pieces = 11; // will play 11 music pieces (defined in music.dat)
var file_handle;
var music_handle = 0;
var piece_number;

function start_music()
{
     file_handle = file_open_read("music.dat"); // open the file
     while (number_of_pieces > 0)
     {
          piece_number = file_var_read (file_handle);
          if (piece_number == 1)
          {
               music_handle = snd_play (luke1_snd, 40, 0);
          }
          if (piece_number == 2)
          {
               music_handle = snd_play (luke2_snd, 40, 0);
          }
          if (piece_number == 3)
          {
               music_handle = snd_play (luke3_snd, 40, 0);
          }
          if (piece_number == 4)
          {
               music_handle = snd_play (luke4_snd, 40, 0);
          }
          number_of_pieces -= 1;
          while (snd_playing (music_handle) != 0) {wait (1);}
          wait (1);
     }
     file_close (file_handle);
}

Dies ist leicht, nicht wahr? Zunächst setzen wir die Anzahl der Musikstücke auf 11 (die Anzahl der Einträge in music.dat), öffnen die Datei und lesen die erste Zahl. Falls piece_number == 1, spielen wir die erste Datei, falls piece_number == 2 die zweite und so weiter. Diese Zeile:

while (snd_playing (music_handle) != 0) {wait (1);}

stellt sicher, dass nur ein Stück gleichzeitig läuft. Die Stücke werden in der Reihenfolge gespielt, die von den Zahlen in music.dat angegeben wird und wenn number_of_pieces 0 erreicht, stoppt die Musik und die Datei wird geschlossen.

Aber was ist zu tun, wenn ich zum Beispiel 8 Dateien benutzen möchte?
Simpel, definieren Sie die Dateien und fügen Sie if (piece_number == 5..8) etc. in die start_music Funktion ein. Dann können Sie in der music.dat die Zahlen von 1 bis 8 verwenden. Wenn Sie möchten, können Sie piece_number auch zufällig generieren und Ihre Nachbarn mit der so erzeugten Musik quälen.
 

 
Skeletons Inc.

Ich weiß nicht, wie es Ihnen geht, aber mir tut das alte Skelett leid, das versucht, einen Ausweg zu finden... was, Sie haben Sk. Inc. noch nicht gespielt?

Das Ziel des Spiels ist es, genug Punkte zu sammeln, um ins nächste Level zu kommen (originell, was?). Die Anzahl der Leben, die Punkte und die entsprechenden Texte werden mit Hilfe des folgenden Codes angezeigt:

panel skinc_pan // displays the numbers
{
     pos_x = 0;
     pos_y = 0;
     digits = 120, 575, 4, skinc_font, 1, lives;
     digits = 650, 575, 4, skinc_font, 1, points;
     flags = refresh, visible;
}

text skinc_txt // displays "Lives:" and "Points:"
{
     pos_x = 0;
     pos_y = 575;
     font = skinc_font;
     string = skinc_str;
     flags = visible;
}

Die main Funktion ist so simpel wie möglich:

function main()
{
     level_load (skinc_wmb);
     wait (2); // wait for the level to be loaded
     clip_size = 0; // show all the triangles for all the models
     fps_max = 30; // lock the frame rate to 30 fps
}

Die Kamera wird an das arrow.mdl angehängt, welches mit folgendem Code im Level platziert wird:

action init_camera
{
     my.invisible = on;
     while (player == null) {wait (1);}
     vec_set (camera.pos, my.pos);
     while (1)
     {
          camera.arc = max (10, (camera.arc - 2 * time * key_equals)); // press "+" and "-"
          camera.arc = min (70, (camera.arc + 2 * time * key_minusc)); // to zoom in  / out
          vec_set (temp.x, player.x);
          vec_sub (temp.x, my.x);
          vec_to_angle (camera.pan, temp); // rotate towards the player
          wait (1);
     }
}

Die Kamera orientiert sich ständig zum Spieler; die scheinbare Distanz zwischen Kamera und Spieler ändert sich, wenn Sie + oder – drücken. Warum scheinbare Distanz? Weil ich die Kamera nicht wirklich bewege, sondern einfach den camera.arc Wert, welches einen Zoom Effekt ergibt. Und hier ist die Action für den Spieler:

action skeleton // the player
{
     while (key_any == 1) {wait (1);}
     my.z = 30;
     player = me;
     falling = 0;

Die erste Zeile wartet, bis alle Tasten losgelassen wurden; das ist nützlich, wenn das Spiel erneut gestartet wird. Das Skelett wird mit den Füßen auf den Boden gestellt; verwenden Sie einen anderen Wert als 30 für ein anderes Model oder ein anderes Level. Die Variable falling nimmt den Wert 1 an, wenn der Spieler fällt und 0, wenn er auf festem Grund geht oder steht.

     while (lives > 0)
     {
          if (key_cuu == 1)
          {
               my.pan = 0;
               my.skill10 = my.x;
               while ((my.x < my.skill10 + step_width) && (falling == 0))
               {
                    ent_cycle("walk", my.skill20);
                    my.skill20 += animation_speed * time;
                    my.skill20 %= 100; 
                    my.x += speed * time;
                    check_content();
                    wait (1);
               }
          }

Solange der Spieler noch let, wenn die “Pfeil oben” Taste gedrückt wird, dreht sich der Spieler in diese Richtung (pan = 0) und speichert seine aktuelle x Position in skill10. Solange der Spieler keine Distanz zurückgelegt hat, die größer oder gleich der step_width Variablen ist, wird die Schleife laufen. Dies beinhaltet Animation und Bewegung, welche einfach durch direkte Änderung der x Koordinate des Spielers erreicht wird, denn wir brauchen keine Kollisionen mit anderen Entities prüfen. Die check_content Funktion prüft, ob der Spieler auf festem Boden steht oder nicht.

Der Code für die anderen Pfeiltasten ist ähnlich, ich überlasse ihn Ihnen erst mal; wir sehen uns am Ende der Funktion wieder.

          if (key_cud == 1)
          {
               my.pan = 180;
               my.skill10 = my.x;
               while ((my.x > my.skill10 - step_width) && (falling == 0))
               {
                    ent_cycle("walk", my.skill20); // play walk frames animation
                    my.skill20 -= animation_speed * time; // reversed "walk" animation
                    my.skill20 %= 100; // loop the animation
                    my.x -= speed * time;
                    check_content();
                    wait (1);
               }
          }
          if (key_cul == 1)
          {
               my.pan = 90;
               my.skill10 = my.y;
               while ((my.y < my.skill10 + step_width) && (falling == 0))
               {
                    ent_cycle("walk", my.skill20); // play walk frames animation
                    my.skill20 += animation_speed * time; // "walk" animation speed
                    my.skill20 %= 100; // loop the animation
                    my.y += speed * time;
                    check_content();
                    wait (1);
               }
          }
          if (key_cur == 1)
          {
               my.pan = 270;
               my.skill10 = my.y;
               while ((my.y > my.skill10 - step_width) && (falling == 0))
               {
                    ent_cycle("walk", my.skill20); // play walk frames animation
                    my.skill20 += animation_speed * time; // "walk" animation speed
                    my.skill20 %= 100; // loop the animation
                    my.y -= speed * time;
                    check_content();
                    wait (1);
               }
          }
          wait (1);
      }

Ich habe ja gesagt, dass der Rest des Codes ähnlich ist. Schauen wir uns die nächsten Zeilen an; diese laufen, wenn der Spieler alle Leben verloren hat:

     my.skill20 = 0;
     while (my.skill20 < 100) // the player is dead
     {
          ent_cycle("death", my.skill20); // play death frames animation
          my.skill23 += 5 * time; // "death" animation speed
          wait (1);
     }
}

Wir setzen skill20 zurück, denn dieser wurde für die walk Animation benutzt und wir möchten ja, dass die death Animaton vom ersten Frame startet.

Schließlich prüft die Funktion check_content() den Boden und setzt falling entsprechend.
 
function check_content()
{
     vec_set (temp, my.pos);
     temp.z -= 64;
     if (content (temp) != content_solid) // if it is a hole
     {
          exclusive_global;
          lives -= 1;
          falling = 1;
          snd_play (falling_sound, 70, 0);
          while (player.z > -1000)
          {
               player.z -= 35 * time;
               wait (1);
          }
          sleep (2);
          if (lives > 0)
          {
               points = 0;
               main();
          }
     }
     else
     {
          falling = 0; // walking on solid ground
     }
}

Als erstes speichern wir die z Koordinate des Spielers in temp und ziehen 64 Quants ab; dieser Punkt sollte nun unter den Füßen des Spielers sein (und innerhalb des Bodenblocks). Falls content (temp) ein Loch bemerkt, wird der Spieler fallen, also stoppen wir alle anderen Instanzen der Action, senken die Anzahl der Leben, setzen falling auf 1 (es wird in der action skeleton gebraucht), spielen einen Sound, bewegen den Spieler abwärts, bis seine Höhe –1000 Quants ist, warten 2 Sekunden, setzen die Punktzahl zurück, starten das Level erneut... oh, ich sollte innehalten.

Falls content (temp) solid ist, falling wird auf 0 gesetzt, damit der Spieler sich normal bewegen kann.

Jede Kugel, die dem Spieler Punkte bringt, hat folgende Action:

action bonus1
{
     my.passable = on;
     my.transparent = on;
     my.light = on;
     my.bright = on;
     my.lightgreen = 250;
     my.lightrange = 0;
     while (vec_dist (player.x, my.x) > 30)
     {
          my.pan += 3 * time; // rotate if the player is far
          wait (1);
     }
     snd_play (bonus_sound, 30, 0);
     while (my.scale_x < 3)
     {
          my.scale_x += 0.1 * time;
          my.scale_y = my.scale_x;
          my.scale_z = my.scale_x;
          my.z += 0.5 * time;
          my.alpha -= 2 * time;
          wait (1);
     }
     points += 10;
     ent_remove (me);
}

Die Kugel ist passierbar, transparent und wird von einem (grünen) Licht erhellt. Wenn der Spieler weiter als 30 Quants entfernt ist, dann dreht die Kugel sich. Falls er näher ist, spielen wir einen Sound und skalieren die Kugel nach oben, bis sie ihre dreifache Größe erreicht hat, bewegen sie nach oben und verringern ihren Alphawert. Dann erhöhen wir die Punktzahl um 10 und entfernen die Kugel.

Das Spiel wäre langweilig ohne einige Tricks; dies ist der erste:

action block_breakable
{
     while (vec_dist (player.x, my.x) > 70) {wait (1);}
     sleep (1.5); // wait 1.5 seconds
     ent_morph (my, "block2.wmb"); // morph the initial block into this one
     snd_play (break_sound, 50, 0);
     sleep (0.1);
     ent_morph (my, "block3.wmb"); // then this block
     sleep (0.1);
     ent_morph (my, "block4.wmb"); // and finally this block
     sleep (0.1);
     while (my.z > -500) // now move the final block downwards
     {
          my.z -= 15 * time;
          wait (1);
     }
     ent_remove (me); // and then remove it
}

Dieser Block wird zerbrechen, wenn der Spieler darüberläuft, so dass es nur eine begrenzte Zahl an Möglichkeiten gibt, um ein Level zu beenden. Er sitzt geduldig im Dunkeln bis der Spieler näher als 70 Quants kommt, wartet dann 1,5 Sekunden, morpht sich in einen anderen Block, spielt einen Sound, wartet 0,1 Sekunden, morpht wieder und so weiter. Der letzte Block bewegt sich nach unten bis sein z Wert etwa –500 Quants ist und wird dann entfernt. Sie können sicher sein, dass die check_content() Funktion von oben falling auf 1 setzt, wenn Sie zuviel Zeit auf einem solchen Block verbringen.

Der zweite (und leider auch letzte) Block ist:

action block_end
{
     my.invisible = on;
     my.passable = on;
     while (points < 100) {wait (1);} // will be invisible until the player has 100 points
     my.passable = off;
     my.invisible = off;
     snd_play (finished_sound, 30, 0);
     sleep (0.5);
     my.invisible = on;
     snd_play (finished_sound, 30, 0);
     sleep (0.5);
     my.invisible = off;
     snd_play (finished_sound, 30, 0);
}

Dieser Block ist wie eine Brücke, die gesenkt wird, wenn Sie 100 Punkte erreicht haben... na ja, es ist keine Brücke und wird auch nicht gesenkt... wie auch immer, es ist der rote Block, der es Ihnen erlaubt, das Ende des Levels zu erreichen. Der Block wird unsichtbar und passable sein bis der Spieler 100 Punkte hat; sobald das geschieht, blitzt er auf, ein Sound wird mehrmals gespielt und der Spieler wird nun zur Flagge gehen können:

action flag
{
     my.passable = on;
     while (player == null) {wait (1);}
     while (vec_dist (my.x, player.x) > 50) {wait (1);}
     player.light = on;
     player.lightred = 250;
     player.lightrange = 100;
     sleep (3); // wait for 3 seconds
     exit; // shut down the engine
}

Wenn er nahe genug an der Flagge ist, wird diese rot leuchten (man muß das schließlich etwas feiern, oder?) und die Engine wird nach 3 Sekunden geschlossen. Ich weiß, das ist keine so tolle Idee für einen nächsten Level, aber wenn sich einer Ihrer Kunden beschwert, sagen Sie einfach, es war eine Demo und geben Sie ihnen das Geld zurück. Wenn sich nicht alle beschweren, erhalten Sie so zumindest ein bisschen...