|
Wetter Effekte |
Top Previous Next |
|
Dieser Workshop wird uns zeigen, wie man Schnee, Regen, Nebel und Tag-Nacht-Übergänge erzeugt. Wir werden auf dem vorherigen Workshop aufbauen, der sich mit Partikeleffekten beschäftigte.
Um den Workshop zu testen, kopieren Sie den workshop40 Ordner in Ihr 3DGS Verzeichnis, starten Sie WED, öffnen Sie work40.wmp und starten Sie das Level mit script40.wdl als Skript.
Mit der "S" Taste starten Sie den Schneefall; das sollte in etwa so aussehen.
Hier sind einige tausend Schneepartikel zu sehen und wenn Sie diese etwas betrachten, werden Sie bemerken, dass der Wind von Norden her weht und den Spieler ein wenig nach hinten drückt. Wir werden uns gleich den Code anschauen, der all dies ermöglicht, aber lassen Sie mich vorher den Teil vorführen, der es erlaubt, den Spieler mit Hilfe der Pfeiltasten zu bewegen und die Maus zum Umschauen zu nutzen.
action players_code // simple player and 1st person camera code { player = my; // I'm the player my.INVISIBLE = ON; // no need to see player's model in 1st person mode while (1) { // move the player using the "W", "S", "A" and "D" keys; "10" = movement speed, "6" = strafing speed c_move(my, vector(6*(key_cuu-key_cud)*time_step, 4*(key_cul-key_cur)*time_step,0), nullvector, IGNORE_PASSABLE | GLIDE); vec_set (camera.x, player.x); // use player's x and y for the camera as well camera.z += 30; // place the camera 30 quants above the player on the z axis (approximate eye level) camera.pan -= 5 * mouse_force.x * time_step; // rotate the camera around by moving the mouse camera.tilt += 3 * mouse_force.y * time_step; // on its x and y axis player.pan = camera.pan; // the camera and the player have the same pan angle wait (1); } }
An dem Teil ist nichts Besonderes; Sie haben ähnlichen Code bereits im vorigen Workshop gesehen. Sehen wir uns nun den Code für den Schnee-Effekt an.
function toggle_snow() { snow_on += 1; snow_on %= 2; var snow_origin; var i; while (!player) {wait (1);} snow_sound(); while (snow_on) { i = 0; while (i < 50) // that's a "while without wait" loop { snow_origin.x = player.x + 1000 - random(2000); snow_origin.y = player.y + 1000 - random(2000); snow_origin.z = player.z + 300; effect(snow_particles, 1, snow_origin.x, normal); i += 1; } if(random(1) > 0.7) { c_move (player, nullvector, vector((-1 + random(1)) * time_step, 0, 0), IGNORE_PASSABLE | GLIDE); } wait (1); } }
on_s = toggle_snow;
Wie Sie sehen kann der Schneefall mit "S" ein- und abgeschaltet werden. Die Variable namens snow_on hat den Anfangswert von 0, sie wird also auf 1 gesetzt, wenn die "S" Taste einmal gedrückt wird. Die Zeile "snow_on %= 2" limitiert die Werte auf 0 oder 1. Wir warten bis der Spieler geladen ist (damit der "player" Zeiger einen gültigen Wert hat) und rufen die snow_sound() Funktion auf (dazu später mehr), die für die Geräuschkulisse verantwortlich ist.
Die äußere "while"-Schleife läuft, solange snow_on auf 1 gesetzt ist; wir haben einen Zähler (eine Variable) namens i, die dabei hilft, in jedem Frame 50 Schneepartikel innerhalb einer weiteren Schleife zu generieren, die allerdings keine "wait(1);" Anweisung enthält. Warum das so ist? Die Schleife läuft exakt 50 Mal und wartet dann für einen Frame, weil die Variable namens i den Wert 50 erreicht hat. Dadurch läuft diese Schleife nicht endlos ohne zu pausieren und löst die entsprechende Fehlermeldung nicht aus. Sie können (natürlich) einen anderen Wert als Grenze angeben, wenn Sie dichteren oder weniger dichten Schnee wünschen.
Die Funktion, die den Schnee erzeugt heißt "snow_particles"; die Partikel werden in einer quadratischen Region erzeugt, die das Spielermodel umgibt und sich mti diesem bewegt, wie im Bild unten gezeigt.
Die Schneepartikel fallen nicht einfach senkrecht nach unten sondern bewegen sich in negativer x-Richtung; warum das so ist, sehen wir gleich, aber betrachten wir zunächst die Zeilen, die den Spieler fortschieben.
if(random(1) > 0.7) { c_move (player, nullvector, vector((-1 + random(1)) * time_step, 0, 0), IGNORE_PASSABLE | GLIDE); }
Dies ist eine der seltenen Situationen, in denen wir den absoluten Bewegungsvektor benötigen, der sich auf die Weltkoordinaten und nicht die Ausrichtung der Entity bezieht. Falls random(1) größer ist also 0,7 wird der Spieler ebenso wie die Schneeflocken ein Stück in negativer x-Richtung bewegt und zwar mit einer Geschwindigkeit zwischen -1 * time_step und 0. Auf diese Weise entsteht ein realistischer Effekt, der Spieler bewegt sich nicht die ganze Zeit. Im 18. Workshop finden sich weitere Informationen zu c_move.
Wenn die "S" Taste nochmal gedrückt wird, setzt dies snow_on auf 0 und beendet so die äußere Schleife, die Geräusche und die Partikel. Die übrigens von den folgenden Funktionen gesteuert werden:
function snow_particles() { var particle_direction; particle_direction.x = random(5) - 15; particle_direction.y = (1 - random(2)) / 10; particle_direction.z = -5 - random(7); vec_add(my.VEL_X, particle_direction); my.ALPHA = 30 + random(65); my.FLARE = ON; my.BMAP = snow_tga; my.SIZE = 3; my.BRIGHT = ON; my.MOVE = ON; my.FUNCTION = fade_snow; }
function fade_snow() { my.ALPHA -= 1.2 * time_step; if (my.ALPHA < 0) {my.LIFESPAN = 0;} }
Particle_direction.x bekommt einen zufälligen Wert zwischen -15 und -10; dies ist für die x-Verschiebung verantwortlich; particle_direction.y bekommt einen geringen zufälligen Wert zwischen -0,1 und 0,1, wodurch sich die Partikel ein wenig seitwärts bewegen. Spielen Sie mit diesen Werten, wenn der Schnee sich zufälliger bewegen soll. Der Wert für particle_direction.z liegt zwischen -12 und -5, der Schnee fällt also immer nach unten.
Jede Flocke hat einen zufälligen Transparenzfaktor zwischen 30 und 95. Die fade_snow Funktion verringert diesen und entfernt die Flocke, wenn er unter 0 fällt. Sie können die 1,2 modifizieren - ein gut gewählter Wert hier sollte die Flocken entfernen, wenn sie den Boden erreicht haben, andernfalls verlieren Sie wertvolle Ressourcen an Partikel, die ohnehin nicht mehr sichtbar sind.
Die folgende Funktion startet bzw. stoppt das snow.wav Geräusch, wenn der Spieler den Schnee ein- oder abschaltet. Beachten Sie, dass ich hier snd_loop verwende, was garantiert, dass die Datei ohne Pause in einer Endlosschleife gespielt wird, da sie nicht immer wieder zwischendurch nachgeladen wird.
function snow_sound() { if (snow_on) { snow_handle = snd_loop(snow_wav, 50, 0); } else { snd_stop(snow_handle); } }
Wie Sie sich vorstelen können, kann ein guter Regeneffekt allein dadurch erzeugt werden, dass die snow.tga durch eine andere Bitmap ersetzt wird, die wie ein Regentropfen aussieht und durch Änderung einiger Zahlenwerte in der snow_particles Funktion. Ich habe mich aber dafür entschieden, den Regen auf andere Weise zu implementieren, mit Hilfe von Sprites anstelle von Partikeln.
Warum sollte ich das tun wollen? Sind Partikel nicht viel schneller als Sprites? Das ist wahr, aber einer der Gründe dafür liegt darin, dass Partikel zweidimensional sind, also nicht orientierbar sind. Daher könnten wir keine schöne etwas längere Bitmap für die Regentropfen verwenden:
Warum nicht? Wenn der Spieler nach oben schaut, dann würde sich folgendes Bild ergeben:
Die Partikel sind immer nur in der 2D Perspektive zu sehen. Die einzige Alternative besteht darin, Sprites zu nutzen, schauen wir uns also die Funktion an, die sie erzeugt.
function toggle_rain() { rain_on += 1; rain_on %= 2; var rain_origin; var i; while (!player) {wait (1);} rain_sound(); while (rain_on) { i = 0; while (i < 30) // that's a "while without wait" loop { rain_origin.x = player.x + 500 - random(1000); rain_origin.y = player.y + 500 - random(1000); rain_origin.z = player.z + 300; ent_create(rain_tga, rain_origin.x, rain_sprites); i += 1; } rain_origin.x = player.x + 100 - random(200); rain_origin.y = player.y + 100 - random(200); rain_origin.z = player.z + 500; ent_create(rain_tga, rain_origin.x, rain_sprites); wait (1); } }
on_r = toggle_rain;
Drücken Sie die "R" Taste, um den Regen ein- oder abzuschalten; wenn rain_on auf 1 gesetzt ist, erstellen wir 30 rain.tga Sprites pro Frame und dann ein weiteres direkt über dem Spieler. Warum sollten wir das tun? Schauen wir uns ein Bild an.
Wie wir alle wissen ist das Leben nicht fair. Sagen Sie es nicht weiter, aber ich bin mir ziemlich sicher, dass die meisten Ihrer potentiellen Kunden schon recht alte Computersysteme verwenden. Ich würde lieber 1000 Rain Sprites pro Frame erzeugen und nicht nur 30, aber fast jeder Computer würde davon überlastet werden. Die Lösung? Tricksen Sie die Spieler aus, so dass sie glauben, Sie würden viel mehr Sprites verwenden als Sie tatsächlich tun. Wenn der Regen sehr nah am Spieler fällt, werden Sie es Ihnen abkaufen!
Sehen wir uns die Funktion für die Regensprites an.
function rain_sprites() { my.PASSABLE = ON; my.ORIENTED = ON; my.TRANSPARENT = ON; my.ALPHA = 20 + random(75); // my.UNLIT = ON; // remove the comment to increase the brightness of the rain sprites my.AMBIENT = 40 + random(60); my.BRIGHT = ON; while (my.ALPHA > 0) { if (c_content(my.X, 0) == CONTENT_SOLID) { my.ALPHA = 0; } my.Z -= (25 + random(30)) * time_step; wait (1); } ent_remove(my); }
Die Sprites sind passierbar und orientiert; sie sind auch transparent mit einem Alpha Faktor zwischen 20 und 95 mit einem Ambientwert zwischen 40 und 100. Die Sprites bewegen sich, solange ihr Alpha größer als 0 ist.
Mit Hilfe von c_content prüfe ich, ob ein bestimmtes Sprite in etwas Festes geraten ist (ein Level Block oder eine Map Entity). Sollte dies der Fall sein, wird einfach der Alphawert des betreffenden Sprites auf 0 gesetzt, die "while"-Schleife stoppt und das Sprite wird entfernt. Drücken Sie "R" um den Regen zu starten und bewegen Sie sich unter die hölzerne Plattform; wie Sie sehen, wird der Spieler hier nicht vom Regen getroffen.
Sie können einen ähnlichen Code verwenden, um Partikel davon abzuhalten in unerwünschte Areale zu dringen; dies verlangsamt den Effekt allerdings. Die letzte wichtige Codezeile in der Schleife sorgt dafür, dass sich das Regensprite nach unten bewegt, indem die z-Koordinate verringert wird und zwar um einen Wert zwischen -55 und -25 * time_step.
function rain_sound() { if (rain_on) { rain_handle = snd_loop(rain_wav, 50, 0); } else { snd_stop(rain_handle); } }
Die Funktion für die Geräuschkulisse des Regens ist identisch mit der, die für den Schnee genutzt wurde, es gibt da also nichts Neues zu sehen. Schauen wir uns also den Code für die Nebelfunktion an:
function toggle_fog() { fog1_on += 1; fog1_on %= 2; if (fog1_on) { camera.fog_start = 10; d3d_fogcolor1.red = 55; d3d_fogcolor1.green = 55; d3d_fogcolor1.blue = 55; fog_color = 1; while (fog1_on) { camera.fog_end = 1000 + 400 * sin(0.5 * total_frames); wait (1); } } else { fog_color = 0; } }
on_f = toggle_fog;
Wie wir wissen ist es möglich, in den "Map Properties" vom WED verschiedene Farben für den Nebel zu definieren. Es ist eine einfache und elegante Art, vier verschiedene Farben für den Nebel zu erhalten. Es gibt jedoch auch einen anderen Weg, diese Farbe zur Laufzeit zu manipulieren, mit Hilfe der vordefinierten Vektoren d3d_fogcolor1 bis d3d_fogcolor4.
Die Funktion toggle_fog schaltet den Nebel ein, solange fog_1 auf 1 gesetzt ist; mit Hilfe der "F" Taste kann der Nebel ein- und ausgeschaltet werden. Wenn fo_1 auf 1 gesetzt ist, starten wir den Nebel 10 Quants vor der Kamera, definieren eine dunkelgraue Farbe (RGB = 55 55 55) und setzen dann fog_color auf 1.
Die "while"-Schleife ändert camera.fog_end in Abhängigkeit von total_frames, der bisherigen Laufzeit des Programms. Weil die Sinus-Funktion Werte zwischen -1 und 1 annimmt, wird camera.fog_end zwischen 1000 + 400 * (-1) und 1000 + 400 * 1 variiert, also wird der Spieler den Nebel zwischen 600 und 1400 Quants Entfernung in Bewegung sehen.
Der Effekt ist am besten zu beobachten, wenn Sie die "F"-Taste drücken, ohne dass Regen oder Schnee aktiv sind. Ich denke, dass es im Zusammenspiel mit Regen oder Schnee auch interessant wirkt, aber Sie können dann auch einen festen camera.fog_end Wert wählen.
ENTITY fading_ent { type = "fader.mdl"; layer = 10; alpha = 0; pan = 90; flags = TRANSPARENT | VISIBLE; view = camera; x = 250; // 250 quants ahead of the view y = 0; z = 0; }
function toggle_daynight() { day += 1; day %= 2; if (day) { while (day) { camera.ambient = min(100, camera.ambient + 1 * time_step); fading_ent.alpha = max(0, fading_ent.alpha - 1 * time_step); wait (1); } } else // night { while (!day) { camera.ambient = max(-100, camera.ambient - 1 * time_step); fading_ent.alpha = min(70, fading_ent.alpha + 1 * time_step); wait (1); } } }
on_d = toggle_daynight;
Der letzte Effekt soll einen Übergang vom Tag zur Nacht schaffen. Ich habe dafür eine "Entity" Definition benutzt und verschiedene Werte für camera.ambient. Wenn die "D" Taste zum ersten Mal gedrückt wird, dann wird day auf 1 gesetzt und die Kamera vergrößert den Ambient bis dieser 100 erreicht, mit einer Geschwindigkeit von 1 * time_step. Gleichzeitig gewinnt die fading_ent Entity, welche sich 250 Quants vor der Kamera befindet, an Transparenz bis ihr Alphawert 0 erreicht (komplette Transparenz).
Wenn der Spieler nochmal die "D" Taste drückt, wird es Nacht: die Kamera verringert ihren Ambient bis dieser -100 erreicht und die fading_ent Entity verliert an Transparenz bis der Alphawert auf 70 gestiegen ist. Mit dieser einfachen Methode können Übergänge vom Tag zur Nacht geschaffen werden; wenn Sie eine Himmelstextur als Hintergrund für Ihr Level haben, können Sie auch den sky_color Vektor manipulieren.
Wir haben in diesem Workshop über einige Wettereffekte gesprochen; ich bin sicher, dass Sie weitere erzeugen können, beispielsweise Tornados und so weiter. Wie so etwas erstellt werden kann? Nun, man könnte ein unsichtbares und passierbares, rotierendes Model eines Kegels im Level plazieren und dann Partikel an den Vertizes erzeugen, so wie im vergangenen Workshop.
|