|
Türen und Schlüssel |
Top Previous Next |
|
In diesem Monat lernen wir den Code für Türen und dazugehörige Schlüssel zu erstellen. Laden Sie work37.wmp und starten Sie dann das Level mit dem work37.wdl Skript; Sie sehen folgendes Bild:
Es ist unser roter Ninja, der wie durch ein Wunder den Kampf mit 3 großen Monstern im letzten Workshop überlebt hat! Weiter so, Ninja! Das Level hat ein geheimnisvolles, mittelalterliches Ambiente, also gehen wir vorsichtig auf die dunkle Tür zu. Ahhh... nun ist sie offen, aber das quietschende Geräusch raubt mir den letzten Nerv! Werden diese Türen nicht ordentlich gewartet? Wie auch immer, links sind weitere Türen...
Das sind Science-Fiction artige Türen, die zur Seite gleiten, also haben wir es hier mit einem düsteren, Science-Fiction Mittelalterspiel zu tun! Hinter diesen Türen gehen wir nach rechts; bald erreichen wir einen blauen Schlüssel.
Diesen können wir aufheben, indem wir ihn berühren; er wird dem Schlüsselinventar hinzugefügt, wie das kleine Schlüsselicon in der oberen linken Bildschirmecke anzeigt. Versuchen wir nun, das Tor daneben zu öffnen... es geht nicht, weil das Tor einen anderen Schlüssel benötigt!
Ok, versuchen wir es an der anderen Tür.
Es hat geklappt! Diese Tür ist offen und der blaue Schlüssel ist aus unserem Schlüsselinventar verschwunden. Verlassen Sie den Raum und suchen Sie den roten Schlüssel; mit ihm können Sie das Tor von eben öffnen.
Herzlichen Glückwunsch! Sie haben gezeigt, dass Sie ein furchtloser Ninja sind und keine Angst vor folgenden Dingen haben: a) Standardtüren, die sich öffnen, wenn der Spieler in die Nähe kommt und offen bleiben; b) Horizontale Schiebetüren, die sich öffnen, wenn der Spieler in die Nähe kommt und sich anschließend wieder schließen, wenn der Spieler weiter weg ist; c) Vertikale Tore, die sich nur mit den korrekten Schlüsseln öffnen lassen.
Gehen wir all diese Typen nun einzeln durch und schauen uns an, wie sie funktionieren. Bevor wir anfangen, beachten Sie bitte, dass ich den Bewegungscode des Ninjas aus dem vorigen Workshop übernommen habe und auf diesen Teil nicht weiter eingehe.
action rotating_door { while (player == null) {wait (1);} while (vec_dist (player.x, my.x) > 100) {wait (1);} // play with 100 snd_play (dooropens_wav, 100, 0); while (my.pan > -140) { c_rotate (me, vector(-4 * time_step, 0, 0), IGNORE_MAPS); wait (1); } }
Das nenne ich mal ein simples Türskript! Die Türen nutzen entweder c_scan oder vec_dist, wobei letzteres meine bevorzugte Methode ist.Die Tür tut überhaupt nichts bis der Spieler im Level existiert und dann wartet sie bis der Spieler näher als 100 Quants ist. Die vec_dist Anweisung dient dazu, den Abstand zwischen zwei Positionen zu berechnen, in unserem Fall die Position der Tür und die des Spielers. Erhöhen Sie die "100", falls die Tür sich schon öffnen soll, wenn der Spieler noch weiter weg ist oder verringern Sie den Wert, wenn sich die Tür nur öffnen soll, wenn der Spieler direkt davor steht.
Wenn der Spieler näher als 100 Quants ist, ertönt das quietschende Geräusch von eben, nämlich dooropens.wav und dann drehen wir die Tür um ihre z-Achse, d.h. wir ändern den pan Wert mit einer Geschwindigkeit von -4 * time_step. Betrachten wir die allgemeine Form der c_rotate Anweisung:
c_rotate (entity_name, angles(pan, tilt, roll), mode);
In unserem Beispiel ist entity_name einfach me, was der Tür Entity entspricht und in angles wird nur der -4 * time_step Wert für die Änderung des pan Winkels eingetragen. Schließlich wird mode auf IGNORE_MAPS gesetzt, wodurch andere Map Entities und auch Terrains ignoriert werden. Weitere Informationen über c_rotate und mögliche Modi befinden sich in der C-Script Anleitung.
Die Tür dreht sich bis ihr Pan Winkel unter -140 gefallen ist; falls Ihre Tür in die andere Richtung aufgehen soll, benutzen Sie solchen Code:
while (my.pan < 120) // rotate this door until its pan angle grows bigger than 120 degrees { c_rotate (me, vector(3 * time_step, 0, 0), IGNORE_MAPS); // use a positive value to rotate in the opposite direction (3) wait (1); }
Wenn Sie solch eine Tür in Ihrem Projekt einbauen möchten, müssen Sie ein paar Werte ändern, aber der Code ist da. Kommen wir zu den Schiebetüren! Ich habe mich entschieden, diese Tür durch das "Object Properties" Interface im WED konfigurierbar zu machen.
define left, flag1; define right, flag2; define trigger_distance, skill1; define displacement, skill2; define sliding_speed, skill3;
// uses left, right, trigger_distance, displacement, sliding_speed action sliding_door { ....................... }
Diese ersten "define" Anweisungen erzeugen alternative Namen für flag1, flag2 und skill1 bis skill3, wodurch der Sinn dieser Variablen deutlicher wird. Zum Beispiel ist es viel einfacher sich zu merken, wofür sliding_speed steht als sich daran zu erinnern, was genau nochmal in skill3 stand, richtig? Von jetzt an können wir also statt "my.skill3 += 10 * time_step" auch "my.sliding_speed += 10 * time_step" schreiben, ohne dass es für die Engine einen Unterschied macht!
Schauen wir uns die folgende Kommentarzeile an:
// uses left, right, trigger_distance, displacement, sliding_speed
Kommentarzeilen zu betrachten erscheint unnötig, aber die Wahrheit ist, dass dieser Kommentar eine spezielle Funktion hat: er teilt WED mit, bei allen Objekten mit der Action, die darunter steht flag1 durch left, flag2 durch right etc. zu ersetzen. Sie müssen also nur einige Namen definieren und diese in einem "uses" Kommentar über die Action zu schreiben; dann sehen Sie folgendes im "Object Properties" Fenster im WED:
Wie Sie sehen, haben sich die Namen der ersten drei Skills und die ersten beiden Flags entsprechend unserer Konvention geändert. Auf diese Weise können Sie leicht konfigurierbare Skripte schreiben, die auch leicht von Leuten genutzt werden können, die nicht programmieren können. Wie Sie vermutlich schon erraten haben, sind die "left" und "right" Flags dazu da, der Tür zu sagen, ob sie nach links oder rechts gleiten soll; dadurch können wir die gleiche Action für beide Arten von Türen definieren.
Trigger_distance legt wie oben den Abstand fest, ab dem die Tür auf den Spieler reagiert, displacement sagt der Tür, wieviele Quants sie gleiten soll und sliding_speed schließlich ist die Geschwindigkeit, mit der die Tür aufgleitet. Sie können diese Werte beliebig ändern; sie funktionieren mit jeder Tür, die sich entlang der x-Achse verschiebt. Für eine Tür, die sich entlang der y-Achse verschiebt, müssen Sie nur x überall durch y ersetzen, mit Ausnahme der vec_dist Kommandos.
Zurück zu unserem Code:
action sliding_door { my.skill10 = my.x; // store the initial x of the door while (player == null) {wait (1);} while (1) { while (vec_dist (player.x, my.x) > my.trigger_distance) {wait (1);} // wait until the player comes close ent_playsound (my, sliding_wav, 400); // play the sound if (my.left) // the "left" checkbox was checked for this door? { while (my.x > my.displacement) // move the door to the left until Wed's "displacement" value is reached { my.x -= my.sliding_speed * time_step; // with the speed given by "sliding_speed" set in Wed wait (1); } } if (my.right) // the "right" checkbox was checked for this door? { while (my.x < my.displacement) // move the door to the right until Wed's "displacement" value is reached { my.x += my.sliding_speed * time_step; // with the speed given by "sliding_speed" wait (1); } }
Die erste Codezeile speichert den Startwert der x-Koordinate in skill10. Das ist notwendig, damit die Tür sich beim Schließen wieder auf ihre ursprüngliche Position bewegen kann. Dann wird gewartet bis der Spieler existiert.
Die erste Tür oben öffnete sich nur einmal, aber diese Schiebetüren sollen sich so oft öffnen und schließen wie wir wollen; daher benutzen wir hier eine "while(1)" Schleife. Diese wartet bis der Spieler näher als my.trigger_distance kommt, die im WED auf 150 festgelegt wurde; daher könnte die folgende Codezeile ebensogut auch so aussehen:
while (vec_dist (player.x, my.x) > 150) {wait (1);} // wait until the player comes close
Wenn der Spieler näher als 150 Quants ist, ertönt das sliding.wav Geräusch bei der Position der Tür (my) mit einer Lautstärke von 400 und dann prüfen wir, ob die "left" Flag gesetzt ist (nur ein anderer Name für flag1, erinnern Sie sich?). Falls ja, dann haben wir es mit einer Tür zu tun, die nach links aufgeht - und das ist exakt, was die nächste Schleife leistet. Ich ersetze die Skills durch ihre in WED gesetzten Werte, um die Lesbarkeit zu erhöhen.
while (my.x > -135) { my.x -= 15 * time_step; wait (1); }
Er sieht viel einfacher aus, oder? Unsere Tür bewegt sich entlang der x-Achse und verringert ihre x-Koordinate, bis die Position geringer als -135 Quants ist. Die Geschwindigkeit ist mit 15 * time_step angegeben. Hier handelt es sich um eine der seltenen Fälle, in denen wir eine Entity direkt durch Manipulation ihrer Koordinaten bewegen können (und sollten).
Die Action verläuft ähnlich, wenn statt dem "left" das "right" Flag aktiv ist, wodurch unsere Tür nach rechts gleitet. Hier ist die entsprechende Schleife mit eingetragenen Zahlen:
while (my.x < 135) { my.x += 15 * time_step; wait (1); }
Die Tür bewegt sich immer noch entlang der x-Achse, wobei dieses Mal die x-Koordinate erhöht wird bis sie größer als 135 Quants ist, wiederum mit einer Geschwindigkeit von 15 * times_step. Das sieht im eigentlichen Skript etwas komplizierter aus, aber das ist der Preis der Flexibilität. Schauen wir uns den zweiten Teil des Codes in der Schleife an:
while (vec_dist (player.x, my.x) < (my.trigger_distance * 1.5)) {wait (1);} // wait until the player has moved away ent_playsound (my, sliding_wav, 300); // play the same sound with a slightly smaller volume if (my.left) // the "left" checkbox was checked for this door? { while (my.x < my.skill10) // move the door at its initial position { my.x += my.sliding_speed * time_step; // with the speed given by "sliding_speed" wait (1); } } if (my.right) // the "right" checkbox was checked for this door? { while (my.x > my.skill10) // move the door at its initial position { my.x -= my.sliding_speed * time_step; // with the speed given by "sliding_speed" wait (1); } } my.x = my.skill10; // now restore the exact initial x position which was stored inside skill10 } }
Dieser Teil startet wenn die Tür geöffnet ist. Es wird gewartet bis der Spieler sich von der Tür entfernt hat; das ist die höfliche Art, eine solche Tür zu programmieren, denn wir wollen vermeiden, dass die Tür ständig auf und zu geht oder den Spieler zerquetscht, wenn er oder sie sich etwa 150 Quants entfernt entscheidet, nochmal umzudrehen und doch durch die Tür zu gehen.
Wenn der Spieler die Tür geöffnet hat, indem er näher als 150 Quants gekommen ist, wie im ersten Teil der Schleife beschrieben, dann sorgt diese Zeile
while (vec_dist (player.x, my.x) < (my.trigger_distance * 1.5)) {wait (1);} // wait until the player has moved away
dafür, dass der Spieler weiter als 150 * 1,5 = 225 Quants entfernt ist, bevor die Tür sich schließen darf. Das bedeutet, dass die Tür sich ab einer Entfernung von 150 Quants öffnet, aber erst bei einem Abstand von 225 Quants wieder schließt, was zwei Vorteile liefert: a) Es schließt mögliche Öffnen / Schließen / Öffnen / Schließen Szenarien aus, die geschehen könnten, wenn der Spieler ca. 149 bis 151 Quants entfernt ist. Dieser einfache Mechanismus, der diesen Effekt verhindert, nennt sich Hysterese. b) Der Code verhindert, dass der Spieler versehentlich in der Tür eingeklemmt wird, wenn er sich entscheidet sich nochmal herumzudrehen, während die Türen sich schließen.
Die zweite Codezeile lässt erneut sliding.wav ertönen, allerdings mit geringerer Lautstärke (300). Der Rest des Codes sorgt dafür, dass die Tür sich auf ihre ursprüngliche Position zurückbewegt und sich dadurch wieder schließt; falls die "left" Flag aktiv ist, muss die x-Koordinate erhöht werden; die folgende Schleife sorgt genau dafür. Im Fall einer aktivierten "right" Flag kehrt die Schiebetür durch Verringern der x-Koordinate an ihre ursprüngliche Position zurück.
Sehen wir uns die letzte Codezeile unserer Action an:
my.x = my.skill10; // now restore the exact initial x position which was stored inside skill10
Brauchen wir diese Zeile wirklich? Bewegen sich die Türen nicht auf die korrekte Position zurück? Nun ja... eigentlich tun sie das nicht! Schauen wir uns eine der Schleifen an, die dafür sorgen, dass die Tür sich schließt und setzen wir einmal Zahlenwerte ein:
while (my.x > 135) { my.x -= 15 * time_step; wait (1); }
Die Schleife bewegt die Tür nach links, bis ihre x-Koordinate geringer ist als 135 Quants. Auf einem langsamen Rechner, mit einer Framerate von (sagen wir mal) 20 Frames pro Sekunde, ist time_step exakt 16 / Framerate = 16 / 20 = 0,8. Stellen wir uns nun vor, dass die Tür sich schließt und die x-Koordinate schon sehr nahe an 135 ist, also z.B. my.x = 136. Die Bedingung der while Schleife ist immer noch wahr (136 > 135) und daher läuft die schleife nochmal und setzt dabei den neuen x-Wert der Tür auf 136 - 15 * time_step = 136 - 15 * 0,8 = 124 Quants. Nun, da 124 > 135 falsch ist, hört die schleife auf zu laufen, aber die Tür ist bei einem x-Wert von 124 angekommen, anstelle der gewollten 135 Quants.
Damit ist klar, welche Rolle die Zeile my.x = my.skill10 spielt, sie korrigiert Ungenauigkeiten und setzt die Tür unabhängig von der Framerate und der Geschwindigkeit, mit der sie sich bewegt, auf die korrekte Position.
Schon müde? Der Code für das vertikal öffnende Tor ist einfacher. Beginnen wir mit einigen Definitionen:
define key_red, flag1; define key_blue, flag2;
var got_red = 0; var got_blue = 0;
bmap red_tga = <red.tga>; bmap blue_tga = <blue.tga>;
panel redkey_pan { bmap = red_tga; pos_x = 5; pos_y = 5; }
panel bluekey_pan { bmap = blue_tga; pos_x = 20; pos_y = 5; }
// uses key_red, key_blue action gate_with_key { .................... }
Wir definieren zwei neue Flags namens key_red und key_blue; sie werden in unserer neuen gate_with_key Action genutzt. Wie Sie sehen spielt es keine Rolle, ob wir erneut Namen für Flag1 und Flag2 vergeben; jede Action verwendet andere Definitionen, so dass wir die alten left und right Namen nicht in WED sehen - diese tauchen nur im Zusammenhang mit der Action für die Schiebetüren auf.
Ich habe ebenfalls zwei Variablen namens got_red und got_blue definiert; jede wird auf 1 gesetzt, wenn der Spieler den entsprechenden Schlüssel eingesammelt hat. Schließlich verwende ich zwei Panels, die dafür sorgen, dass die Schlüssel in der oberen linken Ecke des Bildschirms angezeigt werden.
action gate_with_key { while (player == null) {wait (1);} while (1) { while (vec_dist (player.x, my.x) > 100) {wait (1);} // play with 100 if (my.key_red) // I'm a door that needs the red key? { if (got_red) // the player has got the red key? { snd_play (gateopens_wav, 100, 0); while (my.z < 160) { my.z += 10 * time_step; wait (1); } redkey_pan.visible = off; // the red key panel isn't needed anymore break; // get out of the loop (the door was opened and stays that way) } else // the player hasn't got the red key? { snd_play (gatelocked_wav, 100, 0); while (vec_dist (player.x, my.x) < 200) {wait (1);} // wait until the player moves away } }
Der Code wartet wie gehabt bis das Spielermodel im Level geladen ist; sobald dies geschehen ist, startet die while(1) Schleife. Es muss hier eine Endlosschleife sein, weil das gatelocked.wav Geräusch jedes Mal ertönen soll, wenn der Spieler auf das Tor zukommt und nicht den richtigen Schlüssel dabei hat.Wenn bei der Tür key_red (Flag1) aktiv ist und der Spieler den roten Schlüssel hat (got_red == 1 oder noch einfacher got_red), dann ertönt gateopens.wav und das Tor wird nach oben bewegt, indem die z-Koordinate vergrößert wird bis sie 160 Quants erreicht, mit einer Geschwindigkeit, die durch 10 * time_step gegeben ist. Das redkey_pan Panel, welches den roten Schlüssel in der oberen linken Ecke anzeigt, wird verborgen und die break Anweisung veranlasst die Engine, die while(1) Schleife zu verlassen - wir benötigen sie nicht mehr, weil das Tor nun offen ist und offen bleiben wird.
Wenn der Spieler auf das Tor zukommt, für das der rote Schlüssel benötigt wird und nicht den richtigen Schlüssel besitzt, befinden wir uns im else-Teil, in dem einfach das gatelocked.wav Geräusch ertönt, woraufhin der Code wartet, bis der Spiele mehr als 200 Quants entfernt ist. Das ist nötig, damit nicht andauernd das Geräusch ertönt, wenn der Spieler sich entscheidet ohne schlüssel vor dem verschlossenen Tor stehenzubleiben.
Der gleiche Code wird für das Tor mit dem blauen Schlüssel genutzt, es gibt also keinen Grund, ihn nochmal durchzugehen. Sehen wir uns die letzten beiden Actions unseres Skriptes an:
action red_key { my.passable = on; while (player == null) {wait (1);} while (vec_dist (player.x, my.x) > 50) {wait (1);} // play with 50 snd_play (gotkey_wav, 50, 0); my.invisible = on; redkey_pan.visible = on; got_red = 1; }
action blue_key { my.passable = on; while (player == null) {wait (1);} while (vec_dist (player.x, my.x) > 50) {wait (1);} // play with 50 snd_play (gotkey_wav, 50, 0); my.invisible = on; bluekey_pan.visible = on; got_blue = 1; }
Dies sind die Actions für den roten bzw. blauen Schlüssel; ich habe die gleichen Sprites verwendet, die auch für die Panels gedacht sind, aber Sie sollten sie durch gut aussehende Schlüssel Models ersetzen. Der Code macht die Schlüssel passierbar und wartet dann, bis der Spieler existiert und dann erneut bis der Spieler näher als 50 Quants an die Schlüssel gekommen ist. Sobald dies geschieht, ertönt gotkey.wav, der Schlüssel wird aus de Level entfernt und das entsprechende Panel wird angezeigt. Die letzte Codezeile setzt got_red bzw. got_blue auf 1; wie Sie wissen, prüfen die Tore, ob diese Variablen entsprechend gesetzt sind.
Das war alles! Ich weiß, es war eine lange Reise, aber nun sollten Sie in der Lage sein, alle möglichen Türen mit oder ohne Schlüsseln zu programmieren. Als Hausaufgabe können Sie dafür sorgen, dass die Action gate_with_key ebenso wie die sliding_door Action vom WED Nutzer konfigurierbar ist. Viel Glück! Obwohl das beim Programmieren keine große Rolle spielen sollte...
|