|
Waffen - Teil 1 |
Top Previous Next |
|
Warum spielen wir gern Spiele? Geht es nicht meist um Wettbewerb, darum besser zu sein als ein anderer Spieler, der vielleicht 2 oder auch 10.000 km entfernt wohnt? Oder besser zu sein als eine handvoll Pixel, die von einer 4 KB großen KI Routine gesteuert werden? Manche mögen Einwände haben und Beispiele anführen, die diese Elemente nicht besitzen, aber das ist eine eher kleine Nische in der Spieleindustrie. In diesem Monat lernen wir, unsere eigenen Waffen zu erstellen; wir haben das Thema in AUM 55 schon ein wenig gestreift, aber dieses Mal werden wir genau hinschauen.
Wir sind zusammen einen langen Weg gegangen und nun ist Zeit für die Trennung... nein, bitte nicht weinen; ich meine nicht uns zwei, sondern die Skripte. Es ist Zeit, die Skripte in verschiedene Dateien aufzutrennen. Wir werden ein main.wdl Skript verwenden, ein player.wdl Skript für den Spieler und schließlich ein weapons.wdl Skript für die Waffen.
Sehen wir uns das Hauptskript einmal an.
So mag ich ein Skript! Es ist kurz, simpel und erledigt was es soll. Wie Sie sehen werden player.wdl und weapons.wdl eingebunden; dadurch sind alle Variablen, Actions, Functions und anderen Dinge auch im Hauptskript verfügbar und können im Spiel verwendet werden, also auch im WED zugewiesen werden, ganz so als stünden sie in der main.wdl Datei.
Es mag kompliziert aussehen, aber diese Methode erlaubt uns, Code wieder und wieder zu verwenden. Wird ein Code für den Spieler gebraucht? Einfach die player.wdl eines vorigen Projektes einbinden! Möchten Sie die Waffen, die wir hier erstellen werden in Ihrem Projekt verwenden? Dann müssen Sie nur die weapons.wdl Datei einbinden, FALLS (ich weiß, dass Sie Großbuchstaben hassen) FALLS Sie alle benötigten Definitionen mit in die Datei einbauen. Schreiben Sie so etwas nicht in die main.wdl Datei:
var number_of_bullets = 7;
Und fragen sich dann, warum die weapons.wdl nicht funktioniert, wenn Sie versuchen, sie in einem anderen Projekt zu verwenden – es fehlt diese Definition aus der main.wdl. Ich verwende eine typische player.wdl Datei, die wir hier nicht besprechen wollen; schauen Sie mal rein und Sie werden eine simplae Action für die Dummy Ziele sehen.
Mit der Acknex Engine können drei Arten von Waffen erstellt werden:
1) Waffen mit “echten” Projektilen, die mit dem Ziel kollidieren (z.B. Raketenwerfer, Maschinengewehre usw.). Diese Waffen erzeugen ein Porjektil Model, das durch die Luft fliegt bis es das Ziel trifft (oder verfehlt).
2) Waffen, die mit Hilfe von “c_trace” eine unsichtbare Linie von der Waffe zum Ziel ziehen (Rail Guns etc.). Diese Waffen prüfen einfahc, ob es eine gerade Linie gibt, die zum Ziel führt. Falls Sie gern auf den Feuerknopf drücken und den Gegner 2 Meilen entfernt umfallen sehen möchten (Scharfschützengewehre z.B.) , dann ist dies die Waffe Ihrer Wahl. Vergessen Sie nicht, dass diese Waffen ohne Kugeln dadurch sehr schnell sind.
3) Waffen, die mit Hilfe von “c_scan” einige (oder alle) Ziele in ihrer Umgebung betreffen (z.B. Granaten). Das Projektil (die Granate) führt einen Scan aus und verringert die Gesundheit aller Gegner in der Nähe. Die Projektile müssen das Ziel nicht direkt treffen, sie können auch in der Nähe des Ziels ihre Wirkung entfalten.
Einige werden jetzt sagen, dass es noch weitere Arten von Waffen gibt, wie zum Beispiel solche, die das Ziel einfach auf Mausklick zerstören (usw.) aber diese 3 Arten oben sind diejenigen, die wirklich zählen.
Die ersten Zeilen der weapons.wdl definieren die Strings für die Projektile, das Mündungsfeuersprite und das Explosionssprite. Wir definieren auch einen Sound für die Projektile und einen Entity Pointer mit einem schlichten, einprägsamen Namen: weapon1.
Die Waffe des Spielers hat eine eigene Action. Auf diese Weise kann sie irgendwo im Level plaziert werden und wird beim Start des Levels automatisch zur richtigen Position (in die Hände des Spielers) gelegt. Die Waffe ist passable und wartet, bis der Spieler im Level auftaucht (also wenn der player Pointer nicht mehr “null” ist).
Hier ist eine verainfachte Version der Schleife:
while (1) { vec_set (weapon_offset.x, vector (50, -18, -20)); vec_rotate (weapon_offset.x, vector (camera.pan, camera.tilt, 0)); vec_add (weapon_offset.x, camera.x); // add the camera position to weapon_offset vec_set (my.x, weapon_offset.x); // set weapon's coords to weapon_offset my.pan = camera.pan; // use the same camera angles for the weapon my.tilt = camera.tilt; wait (1); }
Ich habe den Code rausgelassen, der die Waffe bei Bewegung des Spielers auf und ab bewegt; zu diesem Teil kommen wir später. Wir haben von vec_rotate im Zusammenspiel mit vec_add und vec_set schon gesehen, daher wissen Sie, wie diese Befehle funktionieren; an dieser Stelle sind sie dafür zuständig, die Waffe 50 Quants vor die Kamera, 18 Quants nach rechts und 20 Quants nach unten zu plazieren. Eine realistische Waffe sollte auch die Ausrichtungswinkel der Kamera übernehmen.
Die gute Nachricht ist, dass Sie ein sehr ähnliches Skript für einen Spieler oder Gegner verwenden können, den man von außen sieht. Ersetzen Sie “camera” einfach durch “player” oder dem Zeiger für den Gegner und schon funktioniert es.
Was? Sie nennen mich einen Lügner? Na warten Sie, Ihnen werde ich es zeigen...
Sehen Sie was ich meine? Es ist immer besser, die Waffen zusammen mit dem Spieler zu animieren und später als separate Models abzuspeichern, aber selbst eine einfache Action wie diese wirkt manchmal Wunder.
action players_weapon2 // attaches a weapon to the player in 3rd person mode { weapon1 = my; // I'm weapon1 var weapon_offset; my.passable = on; // the weapon model is passable while (player == null) {wait (1);} // wait until the player is created while (1) { vec_set (weapon_offset.x, vector (18, -8, 18)); // play with these values vec_rotate (weapon_offset.x, vector (player.pan, player.tilt, 0)); vec_add (weapon_offset.x, player.x); // add the camera position to weapon_offset vec_set (my.x, weapon_offset.x); // set weapon's coords to weapon_offset my.pan = player.pan; // use the same camera angles for the weapon my.tilt = player.tilt; wait (1); } }
Zurück zum Skript; vielleicht haben Sie bemerkt, dass die Waffe etwas schwingt und sich auf und ab bewegt wenn der Spieler es tut. Der Schlüssel hier ist “wenn der Spieler es tut”; wie stellen das fest?
Es gibt dafür mehrere Möglichkeiten:
a) Wir betrachten die Distanz, die der Spieler bei seiner Bewegung mit c_move zurücklegt. Der Aufruf befindet sich in der player.wdl.
c_move (my, temp.x, nullvector, ignore_passable | glide);
Der Code kann wie folgt umgeschrieben werden:
var distance_covered; distance_covered = c_move (my, temp.x, nullvector, ignore_passable | glide); if (distance_covered > 0) // if the player has moved { // do something here }
Wenn der Spieler sich bewegt, dann ist distance_covered größer als 0; bewegt er sich zum Beispiel 5 Quants, dann hat distance_covered den Wert 5. Normalerweise würde man diese Methode verwenden, aber wir machene s diesmal etwas anders.
b) Wir vergleichen zwei Variablen, in denen die Position des Spielers in zwei aufeinanderfolgenden Frames gespeichert wird. Sehen Sie sich das Bild an:
In zwei lokalen Variablen (player1_pos und player2_pos) speichern wir die xyz Koordinaten des Spielers vor und nach dem “wait(1)” Aufruf in der Schleife. Das bedeutet, dass falls sich der Spieler in dem Frame bewegt hat, player1_pos und player2_pos verschiedene Werte haben, ansonsten sind sie gleich.
Sehen wir uns nun die gesamte while(1) Schleife an, dieses Mal mit dem Teil der für die Bewegung der Waffe verantwortlich ist:
var player1_pos; var player2_pos; var weapon_height; var weapon_offset; my.passable = on; while (player == null) {wait (1);} while (1) { vec_set (player1_pos.x, player.x); vec_set (weapon_offset.x, vector (50, -18, -20)); if (vec_dist (player1_pos.x, player2_pos.x) != 0) { weapon_height += 30 * time; weapon_offset.z += 0.3 * sin(weapon_height); } vec_rotate (weapon_offset.x, vector (camera.pan, camera.tilt, 0)); vec_add (weapon_offset.x, camera.x); vec_set (my.x, weapon_offset.x); my.pan = camera.pan; my.tilt = camera.tilt; vec_set (player2_pos.x, player.x); wait (1); }
Wenn der Unterschied zwischen player1_pos und player2_pos nicht 0 ist, dann hat sich der Spieler bewegt, also wird weapon_height um 30 * time Quants pro Frame erhöht. Die folgende Zeile erhöht den weapon_offset.z um bis 0,3 Quants, je nach dem Wert eines alten Bekannten: der Sinus-Funktion. Mit Hilfe dieser Funktion bekommen wir einen gut aussehenden Geh-Effekt hin.
Die hier genutzte Methode ist komplizierter als die erste Variante, die nur auf die c_move Anweisung zurückgreift. Warum also der Aufwand?
Der Grund ist einfach: wir möchten in sich abgeschlossene, 100% Standalone Skripte schreiben. Wenn ich auf die c_move Anweisung des Spielers zurückgreifen muss (in der player.wdl Datei), damit die weapons.wdl funktioniert, dann kann ich mich von der Unabhängigkeit der Skriptteile verabschieden. Ich könnte dann die weapons.wdl nicht mehr einfach in einem anderen Projekt nutzen und erwarten, dass sie ihre Arbeit tut; das Skripte müsste auf die “distance_covered” Variable des Spielers zugreifen.
Schauen wir uns den folgenden Code an.
Das ist nicht schwer! Wenn der Spieler die linke Maustaste drückt, wird die Funktion fire_bullets() aufgerufen. Diese Funktion stellt sicher, dass nur eine einzelne Instanz läuft und geht in eine Schleife, solange die Maustaste gedrückt ist.
Ich habe mich für eine Waffe entschieden, die solange feuert wie die Maustaste gedrückt wird. Wir ermitteln die Koordinaten, an denen das Projektil erscheinen soll (Vertex 29 vom “weapon1” Model), erstellen es, versehen es mit der move_bullets() Funktion und lassen Mündungsfeuer erscheinen mit der display_muzzle() Funktion.
Schließlich ertönt noch ein Feuergeräusch mit Lautstärke 100 (mehr über Sounds und Musik in einem späteren Workshop) und warten für 0,14 Sekunden. Das heißt, dass die Schleife alle 0,14 Sekunden oder 7 Mal pro Sekunde aufgerufen wird, verstehen Sie? Wenn Sie die Feuerrate ändern wollen, dann passen Sie einfach die 0,14 entsprechend an, ein sleep(0.2) zum Beispiel entspricht einer Rate von 5 Schuss pro Sekunde, da 0,2 * 5 = 1 Sekunde.
Nun zur Funktion, die für die Projektile zuständig ist – move_bullets(). Die Projektile reagieren auf Kollisionen mit anderen Entities und Level Blocks; findet eine solche statt, wird die Funktion “remove_bullets()” aufgerufen. Die Projektile übernehmen den Pan und Tilt der Kamera und bewegen sich mit 200 * time Quants pro Frame.
Solange das Projektil exisitert (my != null) bewegt es sich und ignoriert dabei passierbare Entities wie Wasser usw. Sehen wir uns die Event Funktion an: sie wartet einen Frame und erstellt dann ein kleines Expolsionssprite, macht das Projektil passierbar und unsichtbar, wartet zwei weitere Sekunden und entfernt es dann.
Die Funktion display_muzzle läßt Mündungsfeuer erscheinen, das im Bild oben bewundert werden kann. Das Sprite ist passierbar und hat die “flare” und “bright” Flags gesetzt; der Ambient steht auf 100, damit es noch heller wirkt. Das Sprite ist orientiert und übernimmt den Pan und Tilt der Waffe, allerdings mit einem zufälligen Wert für den Roll Winkel, so dass es jedes Mal etwas anders aussieht.
Mein Sprite war etwas zu groß, daher habe ich es auf den Faktor 0,5 herunterskaliert; Sie sollten diesen Wert Ihrem Sprite anpassen. Die folgenden Zeilen sind für einen einfachen aber effektiven Effekt zuständig, nämlich die Erhöhung des Ambient-Wertes der Waffe auf 100 für 2 Frames. Die Waffe leuchtet also bei jedem Schuss auf, als beleuchte sie das Mündungsfeuer. Warum all der Aufwand? Naja, es ist virtuelle Realität, daher müssen wir großen Wert auf “gefälschten Realismus” legen. Die letzte Codezeile entfernt das Sprite.
Die Funktion explosion_sprite zeigt das animierte Explosionssprite an; es ist passierbar und auf 0,15 skaliert, mit den Flare und Bright Flags und wieder einem Ambient von 100 und einem zufälligen Roll Winkel. Das Sprite ist transparent, aber der Anfangs-Alpha von 100 sorgt dafür, dass dies (noch) nicht auffällt.
Das Sprite durchläuft seine Animation mit einer Geschwindigkeit von 2 * time und verblasst dann schnell, mit einer Geschwindigkeit von 50 * time. Die letzte Codezeile entfernt es wieder aus dem Level.
Ich hoffe, dass Ihnen das Maschinengewehr gefällt; der Code ist simpel, aber hat alle nötigen Features. Im nächsten Monat erstellen wir eine Waffe mit Hilfe von c_trace. Bis dahin!
|