Anzeige
Tutorialbeschreibung

Schneefall mit AS simulieren

Schneefall mit AS simulieren

Zu den beliebtesten Partikeleffekten zählt die Simulation von Schneefall. Eine Möglichkeit, es mit Actionscript schneien zu lassen, zeigt dieses Tutorial.

Schneefall


Version: ab Flash MX, AS 1.0

Niveau: Fortgeschrittene

Vorkenntnisse: Grundlagen Flash, Grundlagen Actionscript (MovieClip-Symbole, attachMovie, Schleifen, enterFrame, parametrisierte Funktionen, Arrays, Math.random, Math.ceil), Tutorial "Regen mit AS simulieren"

Lernziele: Partikeleffekte in AS skripten lernen

Im heißen Sommer haben wir uns eine leichte Abkühlung durch unseren virtuellen Regen verschafft (Tutorial "Regen mit AS simulieren"). Da es nun stramm auf den Winter zugeht, wird es höchste Zeit, etwas Schnee hinter her zu schieben.

Was uns so locker von den Lippen kommt – Schneeflocke – ist in Wahrheit ein höchst kunstvolles Gebilde. Zahlreiche Ausgangsbedingungen in der Natur beeinflussen Gestalt, Konsistenz und Flugverhalten der einzelnen Schneeflocke (tolle Beispiele und gute Erläuterungen: http://www.snowcrystals.com).

Für unsere Zwecke reicht es, wenn wir mit einem einfachen Exemplar vorliebnehmen.

  1. Erstellen Sie einen neuen Film (640x480, Hintergrundfarbe 0000cc, 30 Bps)
  2. Erstellen Sie eine beliebige Schneeflocke (gerne nach obigen Vorlagen, Größe ca. 10x10 Pixel) und wandeln sie in einen MovieClip namens "mc_flocke" um.
  3. Da wir sie per Scripting einfügen müssen, weisen Sie ebenfalls einen Exportnamen "mc_flocke" zu. Sollten Sie das nicht bereits beim Erstellen getan haben, kann man es leicht nachholen: Klicken Sie in der Bibliothek mit der rechten Maustaste auf Ihre Schneeflocke, wählen die Option Verknüpfung, aktivieren das Häkchen bei Export für Actionscript und bestätigen mit Ok. Flash übernimmt dann automatisch als Verknüpfungsnamen den Namen des Symbols und aktiviert ebenfalls automatisch die Option In erstes Bild exportieren, so daß das Symbol unmittelbar nach Laden des Flashfilms zur Verfügung steht.
  4. Anschließend löschen Sie den MovieClip von der Bühne.

Für die Bewegung der Flocken wollen wir uns zunächst an einigen Prinzipien orientieren, die wir bereits bei der Simulation von Regen kennen gelernt haben:

  • Alle Objekte (Flocken) werden in einen einzigen Behälter eingefügt. So lassen sie sich später bei Bedarf leichter ändern oder löschen.
  • Einfügen und Bewegen der Objekte müssen permanent erfolgen. Eine Möglichkeit besteht in der Verwendung von enterFrame ( eine hier nicht weiter behandelte Alternative wäre setInterval ). Um Performanceprobleme zu reduzieren, wollen wir jedoch nicht jedem Objekt ein eigenes enterFrame zuweisen, sondern mit Hilfe eines einzigen enterFrames alle Aktionen steuern.
  • Da sich alle Flocken gleich verhalten, werden sie in diversen Arrays erfasst, um den Zugriff auf sie zu erleichtern.
  • Alle Variablen, Arrays, Methoden etc, die im Zusammenhang mit dem Schneefall stehen, werden einem einzigen Objekt zugewiesen. So lassen sie sich bei Bedarf schnell entfernen, indem eben dieses Objekt gelöscht wird. Das vereinfacht die Datenverwaltung.

Sollten Ihnen diese Punkte unverständlich sein, dann schauen Sie bitte noch mal in der vorherigen Übung nach. Sie ist tatsächlich Voraussetzung, um nicht immer dieselben Arbeitsschritte wiederholen und erläutern zu müssen.

  1. Ändern Sie den Namen der einzigen Ebene in der Hauptzeitleiste in "actions". Dort wollen wir unser ganzes Skript zuweisen.
  2. Schreiben Sie folgendes Bildskript:

this.createEmptyMovieClip("s", this.getNextHighestDepth());
s.aFallen = [];
s.vMax = 10;
s.vNum = 1;
s.vBreite = 640;
s.vHoehe = 480;

Wir erstellen einen leeren MovieClip s, dem wir alles zuweisen, was mit dem Schneefall zusammenhängt. Dazu gehören beispielsweise die hier initialisierten Variablen s.vMax (Gesamtzahl Flocken), s.vNum (Startnummer erste Flocke), s.aFallen (Array für alle Flocken, die fallen), s.vBreite und s.vHoehe (Größe des Bereichs, in dem der Schneefall stattfindet). Die beiden letztgenannten Variablen lassen sich auch dynamisieren, indem man ihnen einfach die Bühnenbreite Stage.width und Bühnenhöhe Stage.height statt fester Werte zuweist.

Zu sehen ist natürlich noch nichts. Dazu muß eine erste Flocke eingeblendet werden.

  1. Ergänzen Sie das Bildskript um folgende Zeilen:

s.schneien = function() {
   var o = this.attachMovie("mc_flocke2", "flocke"+this.vNum, this.vNum);
   o._x = Math.ceil(Math.random()*this.vBreite);
   o.vTempoY = Math.ceil(Math.random()*3)+2;
   var scale = Math.random()+0.2;
   o._xscale *= scale;
   o._yscale *= scale;
   o._alpha = 101-(Math.ceil(Math.random()*51));
   this.aFallen.push(o);
   this.vNum++;
};

Wie die zuerst initialisierten Variablen wird auch unsere Funktion dem MovieClip s zugeordnet. Das Einblenden der Flocken erfolgt mit Hilfe der Methode attachMovie, der wir als Parameter den Export- bzw. Verknüpfungsnamen unserer Flocke, einen neuen Instanznamen sowie eine Tiefe übergeben. Da mehr als eine Flocke erscheinen soll, muß der Instanzname dynamisch zusammengesetzt werden. Der Einfachheit halber verwenden wir dazu die Variable s.vNum (bzw. in der Funktion: this.vNum), die ja mit 1 startet und am Schluß der Funktion jeweils inkrementiert, also um eins hochgezählt wird. Die gleiche Variable dient auch zum Festlegen der Tiefe, auf der Flash die Flocke einfügen soll. Um einen einfacheren Zugriff auf den Instanznamen zu erhalten, wird das Ergebnis des attach-Vorgangs in die lokaleVariable o geschrieben. Andernfalls müssten wir sie mit this["flocke"+this.vNum] ansprechen, was unübersichtlich und umständlich ist.

Horizontal positioniert werden neue Flocken auf einem beliebigem Wert zwischen 1 und dem in der Variablen vBreite definierten Wert. Beim Fehlen einer entsprechenden Angabe fügt sie Flash vertikal auf 0, also am oberen Bildschirmrand ein. Das ist insofern ein bißchen schlampig, als wir mit der automatischen Initialisierung von nicht definierten Werten und Eigenschaften arbeiten, um uns etwas Tipparbeit zu ersparen.

Nicht alle Flocken sind gleich groß. Daher errechnen wir einen Skalierungsfaktor. Würde nur Math.random() verwendet, wäre 0 die kleinstmögliche Zahl. Da sich damit allerdings relativ schlecht skalieren läßt, addieren wir einfach einen gerinegn Wert, hier 0.2 hinzu, um zu gewährleisten, daß die skalierten Flocken auf jeden Fall sichtbar sind. Sie können gerne auch mit größeren Werten experimentieren.

In den nachfolgenden Zeilen wird die Flocke sowohl horizontal wie vertikal mit dem gleichen Faktor skaliert. I.d.R. handelt es sich um eine Verkleinerung, da scale in der Mehrzahl der Fälle kleiner 1 ist. Verstärkt wird der Eindruck unterschiedlicher Flocken durch die Änderung ihres Alphawerts, dem eine Zufallszahl zwischen 50 und 100 zugewiesen wird. Jede erzeugte Flocke wird dann im Array aFallen erfasst. Damit ist es zu einem späteren Zeitpunkt leichter möglich, auf alle Flocken zuzugreifen, die bewegt werden müssen. Abschließend inkrementiert Flash, wie bereits erwähnt, die Variable vNum.

Zwar haben wir jetzt eine Funktion definiert, aber ohne sie auch auszuführen, kann man nichts sehen.

  1. Rufen Sie nach der Definition die Funktion auf mit:

s.schneien();

Wenn Sie testen, erscheint am oberen Bildschirmrand eine kleine bescheidene Flocke. Das ist ja schon mal ein vernünftiger Anfang, aber schöner wäre es natürlich, wenn sich die Flocke nach unten bewegen würde. Auch dafür erstellen wir eine eigene Funktion.

  1. Fügen Sie direkt vor (oder direkt nach) der bisherigen Funktionsdefinition folgende Zeilen ein:

s.fallen = function() {
   for (var i = 0; i<this.aFallen.length; i++) {
      var obj = this.aFallen[i];
      obj._y += obj.vTempoY;
   }
};

  1. Jetzt fehlt nur noch der Aufruf, den Sie nach dem Aufruf von s.schneien() in die allerletzte Zeile einfügen:

this.onEnterFrame = function() {
   s.fallen();
};

Bei einem Test müsste jetzt unsere einsame Flocke todemutig gen Boden stürzen und verschwinden. Weil Schneeflocken ein geselliges Naturell besitzen, droht unsere einzelne Flocke zu vereinsamen.

  1. Rufen Sie s.schneien() in einer Schleife auf (Änderungen Fettdruck):

for (var i = 1; i<=s.vMax; i++) {
   s.schneien();
}

Nach wie vor fügt s.schneien() genau eine Flocke ein. Weil wir diese Funktion aber 10-mal, d.h. entsprechend der Gesamtzahl der Flocken, ausführen, verfügen wir auch über 10 Flocken. Je nachdem welchen Wert Sie der Variablen s.vMax zuweisen, erhalten Sie mehr oder weniger Flocken.

Zehn einsame SchneeflockenBilder

Wenn Schneeflocken in großer Höhe entstehen, dann haben sie viel, wirklich viel Zeit, um auf den Boden zu fallen. Damit es ihnen nicht langweilig wird, lassen sie sich gerne etwas Besonderes einfallen. Anstatt wie ihre Verwandten, die Regentropfen, recht geradlinig, ja geradezu plump nach unten zu stürzen, tänzeln sie gerne hin und her. Was auf den ersten Blick wie ein Gesamtkunstwerk aussieht, dient doch nur einem einzigen Zweck, denn die Flocken machen dasselbe, was jede andere gesellige Spezies auch tut, wenn sie zuviel Zeit hat: sie tratschen miteinander. Doch das ist eine andere Geschichte.

Unseren Flocken fehlt momentan noch das tänzelnde Element. Wir brauchen neben der normalen Fallbewegung und einem Seitenwind noch eine weitere Bewegung, die einem sanften Hin- und Herschwingen gleicht. Glücklicherweise kommt uns hier die Trigonometrie entgegen. Denn mit der Sinus- und der Cosinusfunktion verfügen wir über die Möglichkeit, eine kreisförmige Bewegung zu definieren. Ein Hin- und Herschweben entspricht einer derartigen kreisförmigen Bewegung zuzüglich einer zusätzlichen Bewegung des Kreismittelpunkts nach unten.

Stark vereinfacht formuliert befasst sich die (ebene) Trigonometrie mit den Eigenschaften eines Dreiecks, dessen Größen wie Seitenlänge, Winkel etc. aus anderen bekannten Größen des Dreiecks berechnet werden können. Dies geschieht mit Hilfe der sogenannten trigonometrischen Funktionen, zu denen auch Sinus und Cosinus zählen. Wenn wir diese Funktionen auf einen Einheitskreis übertragen, dann erlauben sie uns, bei gegebenem Kreisradius und Winkel jeden beliebigen Punkt auf dem Kreis zu bestimmen. Cosinus gibt uns dabei die x-Position, Sinus die Y-Position. Der Einheitskreis ist ein Kreis mit einem Radius von 1 und einer beliebigen Maßeinheit.

 

Flash arbeitet mit Grad, der Einheitskreis dagegen erwartet Bogenmaß. Die Umrechnung erfolgt einfach durch eine Multplikation mit PI/180.

Das alles klingt noch recht abstrakt. Daher wollen wir uns zunächst anschauen, wie man eine derartige Kreisbahn konkret in Flash berechnen kann und daraus anschließend eine wellenförmige Bewegung nach unten generieren. Damit uns der bestehende Code nicht stört, wollen wir ihn auskommentieren.

  1. Fügen Sie am Beginn der ersten Zeile diese Zeichen ein:

/*

  1. Fügen Sie am Ende der letzten Zeile diese Zeichen ein:

*/

Damit wurde ein mehrzeiliger Kommentar erstellt, d.h. die geschriebenen Codezeilen bleiben bestehen, aber werden nicht ausgeführt. Zu Testzwecken werden wir im nachfolgenden einen nicht optimierten Code schreiben, der lediglich verdeutlichen soll, wie man mit den genannten Funktionen eine Kreisbewegung definiert.

  1. Schreiben Sie folgenden Code:

this.createEmptyMovieClip("s", 1);
s.vNum = 1;
var obj = s.attachMovie("mc_flocke2", "flocke"+s.vNum, s.vNum);
this.onEnterFrame = function() {
   var kos = Math.cos(s.vNum*(Math.PI/180))*50;
   obj._x = kos;
   var sin = Math.sin(s.vNum*(Math.PI/180))*50;
   obj._y = sin;
   s.vNum++;
};

Es wird ein leerer MC s erstellt, der sich auf dem Koordinatenursprung, also x = 0 und y = 0, befindet. Anschließend initialisieren wir eine Variable mit 1 und fügen in den leeren MC unsere Flocke ein. Da nur eine Flocke verwendet wird, wäre weder ein Zusammensetzen des Instanznamens noch die Verwendung von obj notwendig, aber der Einfachheit halber orientieren wir uns hier am vorhergehenden Code.

Im enterFrame-Ereignis berechnen wir permanent die x- und y-Position der Flocke auf einem gedachten Kreis mit einem Radius von 50 Pixeln. Am Anfang übergeben wir einen Winkel von 0 Grad, d.h., die Flocke muß auf einem gedachten Punkt exakt 50 Pixel rechts neben dem Kreismittelpunkt liegen. Ihre Koordinaten betragen daher bei der ersten Berechnung x = 50 und y = 0.

  1. Das läßt sich auch überprüfen, indem Sie innerhalb des enterFrame-Ereignisses vor der schließenden Klammer ein trace einfügen (Fettdruck):

this.onEnterFrame = function() {
   ...
   s.vNum++;
   trace(obj._x + " "+obj._y);
};

Im Nachrichtenfenster müssten die ersten Werte bei einem Test "50 0" lauten, also 50 für die x- und 0 für die y-Position.

  1. Kommentieren Sie den trace-Befehl mit zwei vorangestellten // aus oder löschen ihn; er wird nicht weiter benötigt.

Da sich der Winkel durch das Inkrementieren von s.vNum ständig ändert, entsteht die gewünschte kreisförmige Bewegung. Daraus erhalten wir eine wellenförmige Bewegung durch ein einfaches Verschieben des Kreismittelpunktes nach unten.

  1. Fügen Sie unmittelbar vor das enterFrame-Ereignis eine neue Variablendeklaration ein:

obj.vTempoY = 2;

  1. Ändern Sie im enterFrame-Ereignis die Zuweisung von y wie folgt und löschen Sie die Errechnung des Sinus:

obj._y += obj.vTempoY;

Das aktuelle Skript müsste jetzt so aussehen:

ScriptBilder

Nun bewegt sich die Flocke permanent nach unten, da sich ja seine y-Position dauernd ändert. In x-Richtung dagegen verhält sie sich wie vorher. Das Ganze sieht jedoch durch den verlagerten Mittelpunkt völlig anders aus, nämlich wie Kreise, die man vertikal mittig durchschneidet, nach unten versetzt und in der Höhe ausdehnt.

Halbkreisförmige BewegungBilder

Damit ist das Bewegungsprinzip zwar klar, aber einige Probleme bleiben noch bestehen.

  • Erstens starten die Flocken an verschiedenen Positionen, unsere Berechnung wird aber unabhängig von der anfänglichen x-Position gleich aussehen. Das mag zunächst überraschend klingen, ist aber völlig logisch, denn in die Formel der Positionsberechnung über den Winkel bzw. in die Cosinusfunktion geht keine Positionsangabe ein. Genau genommen geht diese Funktion vom Mittelpunkt eines gedachten Kreises aus, der in unserem Fall der Position von s entspricht. Wenn Sie testweise vor dem enterFrame-Ereignis eine Positionsangabe wie z.B. obj._x = 300 eingeben, wird diese direkt beim erstmaligen Ausführen des enterFrame überschrieben.
  • Zweitens benötigen wir neben der Bewegung nach unten auch ein seitliches Abdriften, d.h. zur horizontalen Kreisbewegung muß eine weitere horizontale Bewegung hinzu kommen, die den Seitenwind simuliert.
  • Drittens müssen die betreffenden Werte bei allen Flocken jeweils etwas voneinander abweichen, da sie sonst alle exakt die gleiche Bewegung ausführen würden.
  • Viertens müssen wir verhindern, daß der übergebene Winkelwert ins Endlose steigt, was momentan bei der Variablen s.vNum möglich ist. Sie wird ja bei jedem enterFrame einfach inkrementiert.

Da wir nicht darauf angewiesen sind, bei der Kreisbewegung mit einem bestimmten Radius arbeiten zu müssen, sondern beliebige Werte akzeptabel sind, können wir uns hier mit einem kleinen Trick behelfen. Wir weisen der x-Position nicht, wie es formal korrekt wäre, den errechneten Cosinuswert zu, sondern addieren ihn zur aktuellen x-Position. Damit können wir unsere Flocke auf eine beliebige Stelle positionieren und von dort aus die erwünschte Kreisbewegung ausführen.

  1. Ändern Sie das Skript wie folgt (Fettdruck):

var kos = Math.cos(s.vNum*(Math.PI/180))*50;
obj._x += kos;

Wenn Sie jetzt hoffnungsfroh testen, werden Sie mit Entsetzen feststellen, daß sich die Flocke rasch nach rechts absetzt und nach kurzer Zeit mit Verve quer nach links über den Bildschirm schießt. Vielleicht würde sich eine Flocke in einem Blizzard so bewegen, wir bevorzugen jedoch gemäßigtere mitteleuropäische Wetterverhältnisse.

Das Verhalten der Flocke wird verständlich, wenn wir uns die errechneten Positionswerte einmal näher anschauen. Sie startet bei x = 0 und y = 0. Im ersten enterFrame wird für die x-Position 50 errechnet, wie wir bereits weiter oben gesehen haben. Die y-Position verschiebt sich zur gleichen Zeit um 2 Pixel nach unten. Im zweiten enterFrame errechnet Flash 49 (hier der Einfachheit halber abgerundet), so daß als neue x-Position 50 + 49 = 99 herauskommt, während sich die yPosition um weitere 2 Pixel auf 4 erhöht. Im dritten enterFrame liegen wir wieder bei 49 (gerundet) für x, was 148 ergibt, während y nun 6 beträgt. Es zeigt sich also, daß die x-Position rasant anwächst, während in y-Richtung das vorherige gemächliche Tempo beibehalten wird.

  1. Reduzieren Sie den Wert für den Radius in der Berechnung von Cosinus (Fettdruck):

var kos = Math.cos(s.vNum*(Math.PI/180))*2;

Nun bleibt die Flocke fein brav auf dem Bildschirm. Da jedoch der errechnete Wert zur aktuellen x-Position addiert wird (s. Punkt 19), stimmt der Multiplikator 2 nicht mehr mit dem Radius des gedachten Kreises überein. Das macht jedoch nichts, da unsere Bewegung ja nicht auf einen bestimmten Radius angewiesen ist. Jetzt kann die Flocke auch anfangs in x-Richtung verschoben werden.

  1. Schreiben Sie unmittelbar vor das enterFrame:

obj._x = 200;

Die kreisförmige Bewegung wird beibehalten, aber nach rechts versetzt um den Betrag, um den wir auch die Flocke verschoben haben.

Das seitliche Abdriften läßt sich relativ einfach simulieren.

  1. Ändern Sie die Addition von Cosinus (Fettdruck):

obj._x += kos+2;

Je nach Winkel ergibt Cosinus niedrige positive und negative Werte. Durch die Addition von 2 erhöhen wir die positiven und reduzieren die negativen Werte, was umgemünzt auf die x-Position ein Abdriften nach rechts ergibt, wenn Sie testen. Würden wir nicht 2 addieren, sondern subtrahieren, erhielten wir ein Driften nach links. Flexibler ist man natürlich, wenn die 2 nicht als konkrete Zahl innerhalb der Berechnung von Cosinus auftaucht, sondern vorher in einer Variablen festgelegt wird. Dann kann man an dieser Stelle leicht Änderungen vornehmen, ohne in die konkrete Berechnung eingreifen zu müssen.

  1. Schreiben Sie unmittelbar nach der Erstellung des leeren MovieClips s:

s.vWind = 2;

  1. Ändern Sie die Addition von Cosinus (Fettdruck):

obj._x += kos+s.vWind;

Wenn wir mehrere Flocken haben und wir möchten, daß sich zwar jede wellenförmig bewegt, aber nicht absolut gleich, so muß lediglich der Wert von s.vWind etwas variieren.

  1. Schreiben Sie unmittelbar vor das enterFrame-Ereignis diese Zeile:

obj.vWind = Math.random()*2;

  1. Ändern Sie die Addition von Cosinus (Fettdruck):

obj._x += kos+s.vWind-obj.vWind;

Bei jedem Start ändert sich der addierte Wert des Windes. Es werden zwischen 0 (wenn Math.random() 0 ergibt) und ca. 1.8 (wenn Math.random() ca. 0.9 ergibt) von s.vWind subtrahiert. Damit ändert sich auch jedesmal die Driftbewegung.

Zum Schluß bleibt noch der an die Cosinusfunktion übergebene Winkelwert. Wir haben bisher eine Variable verwendet und inkrementiert.

  1. Ändern Sie die Berechnung von Cosinus:

var kos = Math.cos(obj._y*(Math.PI/180))*2;

Die y-Position der Flocke gibt uns prinzipiell dieselben Werte wie s.vNum, beginnend bei 0. Sie hat jedoch den Vorteil, daß wir sie nicht gesondert inkrementieren müssen, denn y wird ja schon automatisch durch die Bewegung geändert. Dieser Wert kann auch nicht ins Unendliche steigen, da wir später die Flocke, sobald sie den Bildschirm verläßt, wieder an den oberen Rand zurücksetzen. Wir weisen der y-Position dann explizit den Wert 0 zu.

Kehren wir zurück zu unserem ursprünglichen Code, um dort die eben erarbeiteten Änderungen einzufügen.

  1. Heben Sie den Kommentar bei unserem ersten Code auf und kommentieren Sie alles aus, was wir danach geschrieben haben.
  2. Arbeiten Sie die Änderungen in den ersten Sourcecode ein, so daß sich folgendes Skript ergibt (Änderungen fett):

this.createEmptyMovieClip("s", this.getNextHighestDepth());
s.aFallen = [];
s.vWind = 2;
s.vMax = 10;
s.vNum = 1;
s.vBreite = 640;
s.vHoehe = 480;
s.schneien = function() {
   var o = this.attachMovie("mc_flocke2", "flocke"+this.vNum, this.vNum);
   o._x = Math.ceil(Math.random()*this.vBreite);
   o.vWind = Math.random()*2;
   o.vTempoY = Math.ceil(Math.random()*3)+2;
   var scale = Math.random()+0.2;
   o._xscale *= scale;
   o._yscale *= scale;
   o._alpha = 101-(Math.ceil(Math.random()*51));
   this.aFallen.push(o);
   this.vNum++;
};
s.fallen = function() {
   for (var i = 0; i<this.aFallen.length; i++) {
      var obj = this.aFallen[i];
      var kos = Math.cos(obj._y*(Math.PI/180))*2;
      obj._x += kos+s.vWind-obj.vWind;

      obj._y += obj.vTempoY;
   }
};
for (var i = 1; i<=s.vMax; i++) {
   s.schneien();
}
this.onEnterFrame = function() {
   s.fallen();
};

Wenn Sie testen, müßten sich 10 Flocken anmutig tänzelnd nach unten bewegen - und dann verschwinden! Das wäre in der Tat ein kurzer Schneefall.

  1. Erweitern Sie die Funktionsdefinition von s.fallen() innerhalb der for-Schleife nach der Addition von vTempoY:

if (obj._y>=this.vHoehe || obj._x<=0 || obj._x>this.vBreite) {
   obj._y = 0;
   obj._x = Math.ceil(Math.random()*this.vBreite);
}

Sobald die Flocke einen Screenrand links, rechts oder unten erreicht hat, setzen wir sie zurück an den oberen Rand und weisen ihr wie zu Anfang eine zufällige x-Position zu. Dadurch schneit es nun permanent. Zwar hätte man wie bei der vorherigen Regensimulation beim Neusetzen jeder Flocke auch deren Werte (Tempo, Wind) ändern können, aber wir sparen uns nun den Schritt, da auch so der Eindruck völlig verschiedener Flocken entsteht. Wir können mit dem Auge gar nicht wahrnehmen, daß immer nur die 10 gleichen Flocken auftauchen. Das gilt erst recht dann, wenn man mit einer größeren Anzahl arbeitet.

  1. Weisen Sie s.vMax 100 statt 10 zu:

s.vMax = 100;

Es ist erstaunlich, wie wenig die Wissenschaft über das Sozialverhalten von Schneeflocken weiß. Selbst „Das Kauverhalten vietnamesischer Hängebauchschweine nach unilateralen Zahnextraktionen“ (Dissertationstitel) ist offenbar wichtiger als die Flocke von nebenan. Immerhin: der wissenschaftlich interessierte Laie kann nach eingehender Beobachtung auch ohne akademische Vorbildung ein wichtiges Phänomen feststellen, nämlich die Tatsache, daß anfangs die Flocken nur vereinzelt auftreten. Erst ihr ausgeprägter Sozialdrang führt dazu, daß sie sich nach und nach zu immer größeren Verbänden zusammenschließen, ehe sie massenhaft nach unten schweben. Unsere Flocken dagegen bilden anfangs eine unnatürliche Linie, die sich erst im Laufe der Animation langsam auflöst. Viel realistischer wirkt es, wenn die Flocken sukzessive eingeblendet werden, also zuerst einige wenige, dann immer mehr, bis wir einen Schneefall mit 100 Flocken haben.

Der Grund für das gleichzeitige Auftreten aller Flocken ist die Schleife, die s.schneien() 100-mal aufruft.

  1. Löschen Sie die Schleife.
  2. Ergänzen Sie das enterFrame-Ereignis um den Aufruf von s.schneien():

this.onEnterFrame = function() {
   s.schneien();
   s.fallen();
};

Jetzt tauchen die Flocken nach und nach auf. Allerdings werden Sie nach einiger Zeit feststellen, daß wir einerseits einen regelrechten Schneesturm entfesselt haben, denn es tauchen immer mehr Flocken auf. Andererseits verlegt sich Flash alsbald darauf, alles in Zeitlupe wieder zu geben. Denn der Flash-Player ist mit der großen Anzahl an Objekten - pro Frame taucht ein weiteres Objekt auf - schnell schlicht überfordert.

Es gibt mehrere Stellen, an denen eine Kontrolle der Anzahl durchführbar ist, nämlich sowohl im enterFrame-Ereignis wie auch in der aufgerufenen Funktion.

  1. Erweitern Sie die Funktion s.schneien() wie folgt (Fettdruck)

s.schneien = function() {
   if (this.aFallen.length<this.vMax) {
      var o = this.attachMovie("mc_flocke2", "flocke"+this.vNum, this.vNum);
      o._x = Math.ceil(Math.random()*this.vBreite);
      o.vWind = Math.random()*2;
      o.vTempoY = Math.ceil(Math.random()*3)+2;
      var scale = Math.random()+0.2;
      o._xscale *= scale;
      o._yscale *= scale;
      o._alpha = 101-(Math.ceil(Math.random()*51));
      this.aFallen.push(o);
      this.vNum++;
   } else {
      this._parent.onEnterFrame = function() {
         s.fallen();
      };

   }
};

Da alle Flocken beim Einblenden in das Array aFallen eingetragen werden, gibt uns dessen Länge Auskunft über die Gesamtanzahl der Flocken. Wenn wir sie jeweils mit der Variablen s.vMax abgleichen, können wir einfach erkennen, wann die Maximalanzahl vorhanden ist. Daher binden wir das Einblenden der Flocke an die Bedingung, daß die Arraylänge kleiner als der Wert der vMax-Variablen ist. Wenn das nicht mehr zutrifft, ist der Aufruf von s.schneien() im enterFrame unnötig und wir überscheiben im dann zutreffenden else-Fall das bestehende enterFrame mit einem neuen enterFrame, das nur noch s.fallen() aufruft.

Hier taucht pro enterFrame eine neue Flocke auf, es beginnt also relativ schnell, heftig zu schneien. Natürlich kann man diesen Prozeß beliebig verzögern: nicht in regelmäßigen, sondern völlig beliebigen Abständen tauchen Flocken auf, so daß es einige Zeit dauert, bis alle Flocken zu sehen sind.

  1. Erweitern Sie die Initialisierung von Variablen des MovieClips s:

s.vWieHeftig = 10;

  1. Erweitern Sie das ursprüngliche enterFrame am Ende des Codes (nicht das enterFrame im else-Fall) (Fettdruck):

this.onEnterFrame = function() {
   if (Math.ceil(Math.random()*s.vMax)>=s.vMax-s.vWieHeftig) {
      s.schneien();
   }
   s.fallen();
};

Ob eine Flocke im enterFrame eingefügt wird, hängt nun von einer Zufallszahl ab. Als kleinste Zahl entsteht durch die Aufrundung per Math.ceil() die 1, als größte 100. Diese Zahl wird verglichen mit 90 (100 - 10). Ist die Zufallszahl größer oder gleich 90, taucht eine neue Flocke auf, andernfalls nicht. Die Berechnung mag auf den ersten Blick umständlich aussehen, hat aber den Vorteil, daß wir die Heftigkeit des Schneefalls an die Gesamtzahl der Flocken binden können und ihn leicht über eine weitere Variable steuern können. Denn je höher der Wert der Variablen s.vWieHeftig ist, desto stärker wird es schneien, weil sich die Wahrscheinlichkeit des Einblendens erhöht. Sie können das austesten, indem Sie extreme Werte für s.vWieHeftig verwenden, z.B. 1 und danach 80.

Bekanntermaßen ist Schnee nicht nur gesellig, sondern auch sehr anhänglich, d.h., wenn er auf ein Objekt trifft, neigt er dazu, dort eine Zeit lang zu verweilen, bevor er davonschmilzt. Dazu benötigen wir einige Objekte, auf denen sich der Schnee ausruhen kann.

  1. Verschönern Sie Ihre Flashdatei beispielsweise mit einer hübschen Wolke, einem Text sowie einem Boden:

KollisionsobjekteBilder

  1. Alle Objekte müssen als MovieClips vorliegen; vergeben Sie folgende Instanznamen (die Bibliotheksnamen sind beliebig): wolke, txt, boden. Achten Sie darauf, den Text per <strg><b> zweimal zu teilen, bis er als Grafik vorliegt, da Flash ansonsten den hitTest nicht korrekt ausführt.
  2. Erweitern Sie die Initialisierung von Variablen des MovieClips s:

s.aObjekte = [txt, boden];
s.aSchmelzen = [];
s.vSchmelzen = 1;

In s.aObjekte werden alle Objekte erfasst, an denen der Schnee hängen bleiben kann. Dieses Array läßt sich beliebig erweitern. Wie bei den fallenden Flocken richten wir ein zunächst leeres Array namens aSchmelzen ein, um all jene Flocken aufzunehmen, die schmelzen sollen. In vSchmelzen legen wir das Tempo des Schmelzprozesses fest.

Von der Logik her bedeutet das Hängenbleiben des Schnees lediglich, daß seine Fallbewegung beim Auftreffen auf ein Objekt für eine bestimmte Zeit unterbrochen werden muß. Danach wird er wieder am oberen Rand eingeblendet und darf munter weiter fallen.

  1. Erweitern Sie die Funktion s.fallen() (Fettdruck)

s.fallen = function() {
   for (var i = 0; i<this.aFallen.length; i++) {
      var obj = this.aFallen[i];
      var kos = Math.cos(obj._y*(Math.PI/180))*2;
      obj._x += kos+(this.vWind-obj.vWind);
      obj._y += obj.vTempoY;
         if (obj._y>=this.vHoehe || obj._x<=0 || obj._x>this.vBreite) {
            obj._y = 0;
            obj._x = Math.ceil(Math.random()*this.vBreite);
         }
         for (a in this.aObjekte) {
            if (this.aObjekte[a].hitTest(obj._x, obj._y, true)) {
               this.aSchmelzen.push(obj);
               this.aFallen.splice(i, 1);
            }

         }
      }
};

Wenn Sie testen, bleibt der Schnee bei einer Kollision an Ort und Stelle liegen.

In der for-Schleife greifen wir auf alle Elemente zu, die im Array aObjekte enthalten sind. In unserem Fall handelt es sich um den Boden und den Text. Anschließend wird nachgefragt, ob die Grafik dieser Objekte unabhängig von ihrem Begrenzungsrechteck mit dem Registrierungspunkt einer Flocke kollidiert. Das ist zwar kein perfekter Kollisionstest, denn eigentlich müßten wir ja auch für die Flocke kontrollieren, ob ihre Grafik unabhängig vom Begrenzungsrechteck eines der fraglichen Objekte berührt. Das läßt Flash aber leider nicht zu. Der hitTest ermöglicht nur die Kollisionsabfrage zwischen den Begrenzungsrechtecken zweier Objekte, was hier viel zu ungenau wäre, und zwischen einer Grafik unabhängig vom Begrenzungsrechteck sowie einem Punkt. Wir wählen diesen Weg.

HitTest1Bilder

HitTest2Bilder

Der hitTest gibt true zurück, wenn eine Flocke auf eines der Objekte stößt. Dann wird es in das Array aSchmelzen eingetragen. Da es sich nicht mehr weiter bewegen soll, muß es natürlich gleichzeitig aus dem Array aFallen entfernt werden. Jetzt fehlt noch die Funktion, die das Schmelzen darstellt.

  1. Definieren Sie folgende Funktion nach s.fallen():

s.schmelzen = function() {
   for (var j = 0; j<this.aSchmelzen.length; j++) {
      var ob = this.aSchmelzen[j];
      if (ob._alpha>0) {
         ob._alpha -= this.vSchmelzen;
      } else {
         ob._x = Math.ceil(Math.random()*this.vBreite);
         ob._y = 0;
         ob._alpha = 100-(Math.ceil(Math.random()*50));
         this.aFallen.push(ob);
         this.aSchmelzen.splice(j, 1);
      }
   }
};

Wir simulieren das Schmelzen durch eine sukzessive Reduktion des Alphawerts. Mit einer Schleife greifen wir auf alle Objekte innerhalb von aSchmelzen zu. Aus Gründen der Vereinfachung werden sie in der Variablen ob gespeichert. Solange der Alphawert der erfassten Flocke größer als 0 ist, reduzieren wir ihn um den in der Variablen vSchmelzen festgelegten Wert. Andernfalls erfolgt wieder das Neusetzen der Flocke, d.h. zufällige x-Position sowie feste y-Position. Da die Flocke beim Schmelzen vollständig ausblendet, muß ihr beim Neusetzen auch wieder ein Alphawert größer 0 zugewiesen werden. Außerdem wird sie aus aSchmelzen entfernt und in aFallen eingefügt, so daß sie sich wieder nach unten bewegt.

  1. Fügen Sie in beiden enterFrame-Ereignissen den Aufruf von s.schmelzen() ein (Fettdruck):

...
} else {
   this._parent.onEnterFrame = function() {
   s.fallen();
   s.schmelzen();
   };
}
..
this.onEnterFrame = function() {
   if (Math.ceil(Math.random()*s.vMax)>=s.vMax-s.vWieHeftig) {
      s.schneien();
   }
   s.fallen();
   s.schmelzen();
};

Jetzt haben wir einen hoffentlich zufriedenstellenden Schneefall. Eine kleine Änderung ist noch notwendig. Unsere Funktion s.schneien() setzt voraus, daß alle Flocken in aFallen enthalten sind. Das trifft jetzt nicht mehr zu, denn wenn eine Flocke an einem Objekt hängt, dann befindet es sich in aSchmelzen und wird in aFallen gelöscht.

Ändern Sie daher die if-Abfrage in s.schneien() wie folgt (Fettdruck):

if (this.aFallen.length+this.aSchmelzen.length<this.vMax) {

Die Gesamtzahl aller Flocken ergibt sich aus einer Addition der Elemente in aFallen und aSchmelzen. Nehmen wir an, 27 Flocken befinden sich im freien Fall (d.h.: die Länge von aFallen ist gleich 27) und 16 schmelzen gerade (d.h.: aSchmelzen enthält 16 Flocken), dann befinden sich insgesamt 27 + 16 = 43 Flocken auf dem Screen.

Damit die Flocken nicht vor der Wolke starten, fügen Sie nach dem Erzeugen des leeren MC s ein:

wolke.swapDepths(1000);

Das Objekt wolke wird auf eine Tiefe gesetzt, die über derjenigen von s und damit über derjenigen aller Schneflocken liegt.

Das gesamte Script hier noch einmal zur Kontrolle:

this.createEmptyMovieClip("s", this.getNextHighestDepth());
wolke.swapDepths(1000);
s.aFallen = [];
s.aObjekte = [txt, boden];
s.aSchmelzen = [];
s.vWind = 2;
s.vMax = 100;
s.vWieHeftig = 10;
s.vNum = 1;
s.vBreite = 640;
s.vHoehe = 480;
s.vSchmelzen = 1;
s.schneien = function() {
   if (this.aFallen.length<this.vMax) {
      var o = this.attachMovie("mc_flocke2", "flocke"+this.vNum, this.vNum);
      o._x = Math.ceil(Math.random()*this.vBreite);
      o.vWind = Math.random()*2;
      o.vTempoY = Math.ceil(Math.random()*3)+2;
      var scale = Math.random()+0.2;
      o._xscale *= scale;
      o._yscale *= scale;
      o._alpha = 101-(Math.ceil(Math.random()*51));
      this.aFallen.push(o);
      this.vNum++;
   } else {
      this._parent.onEnterFrame = function() {
         s.fallen();
         s.schmelzen();
      };
   }
};
s.fallen = function() {
   for (var i = 0; i<this.aFallen.length; i++) {
      var obj = this.aFallen[i];
      var kos = Math.cos(obj._y*(Math.PI/180))*2;
      obj._x += kos+s.vWind-obj.vWind;
      obj._y += obj.vTempoY;
      if (obj._y>=this.vHoehe || obj._x<=0 || obj._x>this.vBreite) {
         obj._y = 0;
         obj._x = Math.ceil(Math.random()*this.vBreite);
      }
      for (a in this.aObjekte) {
         if (this.aObjekte[a].hitTest(obj._x, obj._y, true)) {
            this.aSchmelzen.push(obj);
            this.aFallen.splice(i, 1);
         }
      }
   }
};
s.schmelzen = function() {
   for (var j = 0; j<this.aSchmelzen.length; j++) {
      var ob = this.aSchmelzen[j];
      if (ob._alpha>0) {
         ob._alpha -= this.vSchmelzen;
      } else {
         ob._x = Math.ceil(Math.random()*this.vBreite);
         ob._y = 0;
         ob._alpha = 100-(Math.ceil(Math.random()*50));
         this.aFallen.push(ob);
         this.aSchmelzen.splice(j, 1);
      }
   }
};
this.onEnterFrame = function() {
   if (Math.ceil(Math.random()*s.vMax)>=s.vMax-s.vWieHeftig) {
      s.schneien();
   }
   s.fallen();
   s.schmelzen();
};

Die Naturforscher unter ihnen werden bemerkt haben, daß bei größeren Werten von s.vWind abhängig von deren Vorzeichen entweder unten links oder unten rechts eine Lücke entsteht, die vom Schnee annähernd verschont bleibt. Das liegt daran, daß jede Flocke in die Windrichtung abdriftet. Bei einem Wind größer 0 und einer Startposition von x um 0 herum, wird sich die Flocke immer etwas nach rechts bewegen. Sie hat also nie die Chance, die linke untere Ecke zu erreichen. Das läßt sich anpassen, indem die Startposition der Flocken in horizontaler Richtung auf einen Wert kleiner 0 bzw größer Bühnenbreite verschoben und dementsprechend die if-Abfrage in s.fallen() angepasst wird.

Viel Spaß beim Schneien


Kommentare
Achtung: Du kannst den Inhalt erst nach dem Login kommentieren.
Portrait von Taraniel
  • 10.10.2011 - 11:40

Super Tutorial, sehr schön beschrieben. Weiter so! :)

Portrait von rentier
  • 21.04.2011 - 09:19

Das habe ich vor ca. 10 Jahren schon mal gemacht und wieder vergessen. Und dabei sind hier einige wirklich tolle Funktionen drin. Danke!

Portrait von Beho
  • 29.10.2010 - 21:41

Dicken Dank, hab´s kapiert! Trau mich jetzt wohl auch mal endlich an AS ran!
Schade nur, dass es bei über 6000 Aufrufen nur 8 Kommentare gibt - achja, jetzt sind´s 9 ... ;-)

Portrait von nahkillo94
  • 11.11.2009 - 16:20

sehr sehr gutes tutorial, und gut zum selbst ausprobieren! danke!

Portrait von _dp_
  • 02.06.2009 - 19:06

richtig gut, danke schön

Portrait von iMac
  • 10.04.2008 - 01:01

sehr schön , danke

Alternative Portrait
-versteckt-(Autor hat Seite verlassen)
  • 16.12.2007 - 01:30

sehr schön^^ die beschreibung gefällt mir

Alternative Portrait
-versteckt-(Autor hat Seite verlassen)
  • 16.10.2007 - 17:29

Absolut genial erklärt. War echt genial. Weiter so. Und stimmt....könnte ruhig mehr positive Resonanz hageln ;)

Portrait von Liebling373
  • 22.06.2007 - 08:13

Super Tut, selbst für Einsteiger, Seltennutzer von Flash gut geeignet! Funktioniert super, nur schade das gerade bei solchen wirklich gut geschriebenen Tuts kaum einer seine qualifizierte Meinung abgibt!
Weiter so, ich wünsche mehr davon!

Alternative Portrait
-versteckt-(Autor hat Seite verlassen)
  • 20.12.2006 - 15:00

Schon so oft angeschaut und wahrscheinlich auch genutzt, aber nur ein Kommentar!?!
"Das geht doch nicht" hab ich mir gedacht und diesen Kommentar verfasst.
Ich finde das du das "Mega" gut erklärt hast !!!
Fettes Kompliment für diesen doch sehr großen Aufwand.
Ich weiß dies zu schätzen und das Brot des Künstlers ist ja bekanntlich der Applaus!
Aufblickend von mir erhältst du diesen auch.
*Klatsch,Klatsch*

Weiter so!!!

Gruß

Franky

Portrait von pinsel77
  • 21.11.2006 - 17:56

gutest Tutorial, für einen Anfänger wie ich es bin sind solche Dinge echt ne Hilfe!

x
×
×