Morrowing

In diesem Monat habe ich das Dialogsystem beendet und einen NPC erstellt, der mit dem Spieler diskutiert. Es gibt auch noch andere Features: ein Feuereffekt mit Rauch, korrekt skalierte Models und so weiter.

Beginnen wir mit dem Feuereffekt:

action my_fire
{
my.invisible = on;
my.passable = on;
while (1)
{
vec_set(temp.x, my.x);
vec_add(temp.x, vector(random(20) - 40, random(20) - 40, random(15)));
effect (fire_effect, 30 * time, temp.x, normal);
if (random(1) > 0.9)
{
temp.z += 70 + random(30);
effect (smoke_effect, 1, temp.x, normal);
}
my.pan += 1 * time;
my.lightrange = 200;
my.lightred = 200;
my.lightgreen = 150 + random(50);
my.lightblue = 100;
wait (1);
}
}

Die Entity mit dieser Action wird unsichtbar und passable; wir erzeugen Partikel mit Hilfe der fire_effect() Funktion an der Stelle, die durch die Entity gegeben ist und zwar in x und y-Richtung um bis zu 20 Quants in jeder Richtung und auf der z-Achse um bis zu 15 Quants nach oben verschoben. Falls random(1) größer ist als 0,9, dann wird 70 – 100 Quants oberhalb der Entity ein Rauchpartikel erzeugt. Die letzten Zeilen geben der Entity ein dynamisches Licht mit 200 Quants Reichweite und einer gelblichen Farbe.

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

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

function smoke_effect()
{
temp.x = random(1) - 0.5;
temp.y = random(1) - 0.5;
temp.z = random(2);
vec_add (my.vel_x, temp);
my.alpha = 10 + random(20);
my.bmap = smoke_tga;
my.size = 100;
my.bright = on;
my.move = on;
my.lifespan = 350;
my.function = fade_smoke;
}

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

Die Funktionen für das Feuer und den auch sind fast identisch; die Partikel haben in x und y Richtung eine zufällige Geschwindigkeit und eine positive z-Geschwindigkeit (wodurch sie aufsteigen). Beide verblassen, weil sich ihr Alpha Wert verringert.

Reden wir über den Code für den NPC (= Non-Player Character); ich musste eine Möglichkeit integrieren, dem NPC mitzuteilen, ob der Spieler das Zelt betreten hat oder nicht.

var player_entered_tent1 = 0;

action tent1_entrance
{
my.invisible = on;
my.passable = on;
while (player == null) {wait (1);}
while (vec_dist(player.x, my.x) > 300) {wait (1);} // wait until the player has come close to the entrance of the first tent
player_entered_tent1 = 1;
}

Zu diesem Zweck wird am Zelteingang eine unsichtbare Entity plaziert; diese wartet bis der Spieler näher als 300 Quants gekommen ist und setzt dann die Variable namens “player_entered_tent1” auf 1.

action darth_vader
{
var saw_player = 0;
while (player == null) {wait (1);}
while (1)
{
ent_cycle("crstnd", my.skill40); // play stand frames animation
my.skill40 += 2 * time; // "stand" animation speed
my.skill40 %= 100; // loop the animation
if (player_entered_tent1 == 1)
{
player_entered_tent1 += 1; // make sure that this "if" branch is executed only once
// text processing starts here
dialog_number = 2; // talking to darth vader
str_cpy(temp_str, line4_str); // that's the "Look! I am your father!" line
process_their_strings();
wait (1);
theysay_txt.string = their_lines_str;
theysay_txt.visible = on;
wait (3);
stop_player = 1; // stop the player for now
sleep (1); // wait for 1 second
isay1_txt.string = line5_str; // that's the "Really? I'm not so sure about that..." line
isay2_txt.string = line6_str; // that's the "Oh, father! I finally get to meet you!"
isay1_txt.visible = on; // give the player the possibility to answer (this will bring up the my_pan panel as well)
isay2_txt.visible = on;
show_pointer = 1; // display the mouse pointer
sleep (1);
while (their_pan.visible == on) {wait (1);} // stop the action until this panel disappears
stop_player = 0; // allow the player to move again
// text processing ends here
}
wait (1);
}
}

Der Code für den NPC ist simpel; die “crstnd” Animation wird in einer Schleife abgespielt und es wird gewartet, bis der Spieler das Zelt betreten hat. Sobald das geschieht, wird player_entered_tent erhöht, damit der “if”-Zweig nur einmal ausgeführt wird. Achten Sie auf die Kommentarzeilen “// text processing starts here” und “// text processing ends here”; der Code dazwischen wird für Ihre NPCs benötigt, wenn sie mit dem Spieler kommunizieren sollen. Der Code zwischen diesen Kommentarzeilen liefert die erste Zeile, die Darth Vader spricht und die möglichen Antworten des Spielers. Mit einer Variable namens “dialog_number” zähle ich die Dialoge; sie ist 1 für den ersten Dialog (der Spieler spricht am Anfang des Levels mit dem Skelett) und 2 für den Dialog mit Darth Vader. Sie können diese Variable auf jeden beliebigen Wert setzen, sofern Sie in der panelstexts.wdl Datei die gleiche Nummer verwenden (mehr dazu später).

Schauen wir uns den Code an: wir kopieren den Inhalt von line4_str in temp_str und starten die process_their_strings() Funktion, die den Text je nach Größe umbricht. Diese wurde in AUM 47 besprochen; der bearbeitete String steht in their_lines_str und wird mit theysay_txt auf dem Bildschirm dargestellt. An dieser Stelle wird also die “Look! I am your father!” Zeile am oberen Bildschirmrand angezeigt; in der lines.wdl stehen alle Zeilen drin.

Wir setzen stop_player auf 1; dies stoppt den Spieler und nach einer Sekunde zeigen wir dann die möglichen Antworten an: “Really? I’m not so sure about that...” und “Oh father! I finally get to meet you!”. Diese Zeilen werden mit isay1_txt und isay2_txt angezeigt, die sichtbar gemacht werden und show_pointer = 1 zeigt außerdem dne Mauszeiger. Die Action pausiert solange der Spieler mit dem NPC redet (their_pan.visible = on) und sobald das Panel verschwindet, wird stop_player auf 0 gesetzt und der Spieler kann sich wieder bewegen.

Der Clou ist der Folgende: Sie müssen in der Action für die Entity nur die erste Zeile für den NPC und die ersten Antworten festlegen, der Rest wird von panelstexts.wdl übernommen und der Spieler muss nur noch die gewünschten Antworten anklicken. Ich benutze hierfür die choose_answer() Funktion aus panelstexts.wdl:

function choose_answer(number)
{
if (number == 1) // the player has selected the first line (has clicked the first button)
{
if (dialog_number == 2) // the player has picked the "Really? I'm not so sure about that..." line
{
str_cpy(temp_str, line7_str); // that's the ""You KNOW that! What does your heart tell you?" line
process_their_strings();
wait (1);
theysay_txt.string = their_lines_str;
theysay_txt.visible = on;
wait (3);
stop_player = 1; // stop the player for now
sleep (1); // wait for 2 seconds
isay1_txt.string = line8_str; // "My heart tells me that you are a weird sci-fi movie character!"
isay2_txt.string = line9_str; // "I have waited for this moment for so long!"
dialog_number = 3;
}
if (dialog_number == 1) // the player has picked the "I am The mighty Guard..." line
{
isay1_txt.visible = off;
isay2_txt.visible = off;
theysay_txt.visible = off; // hides their_pan as well -> resumes the gameplay
show_pointer = 0; // hide the mouse pointer
}
}

Ich betrachte hier nur den Code der ersten beiden Dialoge (dialog_number == 1 und dialog_number == 2); panelstexts.wdl enthält 6 Dialoge, aber die anderen sind sehr ähnlich zu den hier vorgestellten. Falls number gleich 1 ist, hat der Spieler die erste Antwort gewählt (also den ersten Knopf gedrückt). Beachten Sie die Reihenfolge der Dialoge: ich beginne mit den größten dialog_number Werten und steige dann bis dialog_number == 1 ab, damit nur die gewünschten Zeilen ausgeführt werden, wenn einer der Knöpfe gedrückt wird. Dialog_number == 1 bedeutet, dass der Spieler mit dem Skelett am Anfang des Spiels redet; der Spieler hat die erste Zeile gewählt.

Wir hatten festgelegt, dass der Spieler in diesem Fall in der Lage sein muss, fortzulaufen, daher werden die Antworten dann ausgeblendet, ebenso wie die Zeile des Skeletts und der Mauszeiger wird ebenfalls verborgen. Falls dialog_number gleich 2 ist, redet der Spieler mit dem NPC im Zelt.

Die erste Zeile für den NPC, ebenso wie die möglichen Antworten, wurden durch die Action des NPCs festgelegt, erinnern Sie sich? Das ist nötig, weil diese Zeilen vom NPC gesetzt werden. Wenn der Spieler die oben ausgewählte Antwort anklickt, wird der folgende Bildschirm angezeigt:

Wie ist das möglich? Hier ist der entsprechende Code:

if (dialog_number == 2) // the player has picked the "Really? I'm not so sure about that..." line
{
str_cpy(temp_str, line7_str); // that's the ""You KNOW that! What does your heart tell you?" line
process_their_strings();
wait (1);
theysay_txt.string = their_lines_str;
theysay_txt.visible = on;
wait (3);
stop_player = 1; // stop the player for now
sleep (1); // wait for 1 second
isay1_txt.string = line8_str; // "My heart tells me that you are a weird sci-fi movie character!"
isay2_txt.string = line9_str; // "I have waited for this moment for so long!"
dialog_number = 3;
}

Dialog_number wurde vom NPC auf 2 gesetzt, also ist die richtige Bedingung erfüllt; die siebte Zeile wird in temp_str kopiert, ggf. umgeborchen und dann sichtbar gemacht. Der Spieler wird angehalten, wir warten eine Sekunde und die Antworten werden angezeigt (line8 und line9). Schließlich setzen wir dialog_number auf 3, was den folgenden Bildschirm anzeigt, wenn der Spieler die erste Zeile auswählt:

Dies geschieht, wenn der Spieler den ersten Knopf wählt. Aber was tut der zweite Knopf auf dem Panel?

if (number == 2) // the player has selected the second line (has clicked the second button)
{
if (dialog_number == 2) // the player has picked the "Oh, father! I finally get to meet you!" line
{
str_cpy(temp_str, line10_str); // "Listen to me, son! Take the body armor..."
process_their_strings();
wait (1);
theysay_txt.string = their_lines_str;
theysay_txt.visible = on;
wait (3);
stop_player = 1; // stop the player for now
sleep (1); // wait for 2 seconds
isay1_txt.string = line11_str; // "Thank you, sir! I'll miss you for sure!"
isay2_txt.string = line12_str; // "Good bye. And thank you for the gadgets!"
dialog_number = 4;
}
if (dialog_number == 1) // the player has picked the "All of the sudden..." line
{
exit; // shut down the engine
}
}
}

Falls dialog_number gleich 1 ist, dann spricht der Spieler mit dem Skelett und hat die rot markierte Antwort ausgewählt.

In diesem Fall wird das Spiel beendet. Falls dialog_number aber 2 ist, spricht der Spieler mit dem NPC und hat die folgende Antwort gewählt:

Durch den zweiten Knopf wird die 10. Zeile von lines.wdl angezeigt und als mögliche Antworten werden line11 und line12 angewählt. Indem schließlich dialog_number auf 4 gesetzt wird, kann der Dialog weitergehen.

So funktioniert es! Sie können alles in die Bedingungen schreiben und z.B. dafür sorgen, dass der NPC den Spieler angreift, eine Explosion auslösen usw. Vergessen Sie nicht, die erste Zeile des NPCs und die möglichen Antworten in der Action für den NPC festzulegen und dann alle anderen Texte in der choose_answer(number) Funktion abzuarbeiten. Und vergessen Sie nicht, für jeden Dialogteile einen anderen Wert für dialog_number zu wählen!