Arno Kohl
ActionScript 2 Ein Lehr- und Übungsbuch
13
Dr. Arno Kohl Macromedia Akademie für Medien Gollierstraße 4 80339 München Deutschland
[email protected] ISBN 978-3-540-35179-5 e-ISBN 978-3-540-35182-5 DOI 10.1007/978-3-540-35182-5 Springer Heidelberg Dordrecht London New York Die Deutsche Nationalbibliothek verzeichnet diese Publikation in der Deutschen Nationalbiblio grafie; detaillierte bibliografische Daten sind im Internet über http://dnb.d-nb.de abrufbar. © Springer-Verlag Berlin Heidelberg 2011 Dieses Werk ist urheberrechtlich geschützt. Die dadurch begründeten Rechte, insbesondere die der Übersetzung, des Nachdrucks, des Vortrags, der Entnahme von Abbildungen und Tabellen, der Funksendung, der Mikroverfilmung oder der Vervielfältigung auf anderen Wegen und der Speicherung in Datenverarbeitungsanlagen, bleiben, auch bei nur auszugsweiser Verwertung, vorbehalten. Eine Vervielfältigung dieses Werkes oder von Teilen dieses Werkes ist auch im Einzelfall nur in den Grenzen der gesetzlichen Bestimmungen des Urheberrechtsgesetzes der Bundesrepublik Deutschland vom 9. September 1965 in der jeweils geltenden Fassung zulässig. Sie ist grundsätzlich vergütungspflichtig. Zuwiderhandlungen unterliegen den Strafbestimmungen des Urheberrechtsgesetzes. Die Wiedergabe von Gebrauchsnamen, Handelsnamen, Warenbezeichnungen usw. in diesem Werk berechtigt auch ohne besondere Kennzeichnung nicht zu der Annahme, dass solche Namen im Sinne der Warenzeichen- und Markenschutz-Gesetzgebung als frei zu betrachten wären und daher von jedermann benutzt werden dürften. Einbandgestaltung: KünkelLopka GmbH, Heidelberg Gedruckt auf säurefreiem Papier Springer ist ein Teil der Fachverlagsgruppe Springer Science+Business Media (www.springer.com)
Danksagung
In erster Linie gebührt all meinen Studenten und Schülern Dank dafür, dass sie klaglos meinen gelegentlich sicher etwas anstrengenden Unterricht über sich ergehen ließen und mir so Gelegenheit gaben, den einen oder anderen Workshop am lebenden Sujet auszutesten. Besonderen Dank schuldet der Autor jenen, die aktiv mit Rat und Tat zur Seite standen. Namentlich seien genannt: – „eyelan“ Benedikt Hocke, der mit seiner raschen Auffassungsgabe, unvergleichlichem grafischen Geschick und großer Phantasie eine frühe Phase des Buches begleitete; – Frau Nina Eichinger, die insbesondere ein passendes Layout entwickelte; – Herr Richard Häusler, der angenehm kritisch größere Teile des Manuskripts goutierte und manch einem hartnäckigen Fehler auf die Schliche kam. Ein ganz großes Danke Schön an das Lektorat des Springer Verlags, das mit einer Engelsgeduld jede Terminübertretung des Autors ertragen hat und so das Fertigstellen des Manuskriptes überhaupt erst ermöglichte. Und schließlich sei auch denjenigen gedankt, die die Entwicklung des Skripts langfristig mit kritischem Blick und manch aufmunterndem Kommentar begleiteten (2006: „Wieweit bist Du denn schon?“, 2007: „Ist denn wenigstens ein Kapitel fertig?“, 2008: „Hast Du schon angefangen?“, 2009: „Naja, wenn der Maya-Kalender mit 2012 Recht hat, musst Du Dich jetzt auch nicht mehr beeilen“).
v
Inhaltsverzeichnis
1 Einleitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2 ActionScript: Geschichte, Versionen, Ausblick .. . . . . . . . . . . . . . . . . . . . . . . . 5 3 Programmentwicklung und Programmiersprachen . . . . . . . . . . . . . . . . . . . 3.1 Ansätze (Top-Down, Bottom-Up) .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.2 Algorithmus als Problemlösung .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3 Hilfsmittel zur Entwicklung von Algorithmen .. . . . . . . . . . . . . . . . . . 3.3.1 Flowcharts .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.3.2 Pseudo-Code .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.4 Beispiel-Algorithmus „Kaffee kochen“ .. . . . . . . . . . . . . . . . . . . . . . . .
11 11 12 13 14 15 16
4 Programmierung und ActionScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 5 Framework und Codeorganisation .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1 Skriptarten (Objekt- und Bildskript) . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.2 Anzahl der Bildskripte .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.3 Aufbau eines Skripts .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35 35 37 37
6 Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1 Zuweisungsoperator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.2 Arithmetische Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.3 Inkrement-/Dekrementoperator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.4 Stringoperatoren .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.5 Vergleichsoperatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.6 Logische Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.7 Bit-Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.8 Sonstige .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41 42 42 43 44 46 48 50 51
7 Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.1 Deklaration .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.2 Wertezuweisung .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.3 Reichweite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.4 Parameter und Zählvariablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7.5 Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55 55 58 60 62 62
8 Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 8.1 Boolean . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 8.2 Number . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 vii
viii
Inhaltsverzeichnis
8.3 8.4 8.5 8.6 8.7
String . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Null, undefined .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . MovieClip .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Object .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Void .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
67 67 68 68 68
9 Arrays 9.1 9.2 9.3 9.4 9.5 9.6 9.7 9.8 9.9
Arrays einrichten und die Länge definieren .. . . . . . . . . . . . . . . . . . . Arrays bei der Deklaration füllen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zugriff auf Inhalte .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arrays dynamisch füllen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Löschen von Elementen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Arrays sortieren und durchsuchen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . Weitere Methoden .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mehrdimensionale Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Assoziative Arrays .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
69 69 70 70 72 74 77 80 81 82
10 Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.1 Funktionsdeklaration und -aufruf . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.2 Funktionen ohne Bezeichner .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.3 Gültigkeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.4 Verschachtelung von Funktionen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.5 Parameter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10.6 Funktionen mit Rückgabewert (return) .. . . . . . . . . . . . . . . . . . . . . . . 10.7 Von Flash zur Verfügung gestellte Funktionen .. . . . . . . . . . . . . . . . 10.8 Rekursion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
85 85 86 87 88 89 91 92 93
11 Kontrollstrukturen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 11.1 Bedingungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 11.1.1 Die if-Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 11.1.2 Verschachtelte if-Anweisungen, logisches und, logisches oder . 98 11.1.3 if else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99 11.1.4 else if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 11.1.5 switch, break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 11.2 Schleifen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 11.2.1 for-Schleife .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104 11.2.2 break, continue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 11.2.3 while-Schleife .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108 11.2.4 do while-Schleife .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 11.2.5 for in-Schleife .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 11.2.6 Endlosschleifen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 12 MovieClip-Klasse .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.1 Eigenschaften von MovieClips .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.1.1 Adressierung von MovieClips . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.1.2 _x, _y, _xmouse, _ymouse, _width, _height .. . . . . . . . . . . . . . . . . . 12.1.3 _xscale, _yscale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.1.4 _rotation, _alpha, _visible . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.1.5 blendMode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.1.6 _currentframe, _totalframes .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.2 Ereignisse .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
113 113 113 119 123 125 128 130 130
Inhaltsverzeichnis
ix
12.2.1 12.2.2 12.2.3 12.3 12.3.1 12.3.2 12.3.3 12.3.4 12.3.5 12.3.6 12.4
onEnterFrame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Schaltflächenereignisse von MovieClips . . . . . . . . . . . . . . . . . . . . . . Maus-Ereignisse .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . MovieClip-Methoden .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Zeitleistensteuerung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Objekte dynamisch einfügen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Drag and Drop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kollision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Maskierung .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ausdehnung und Koordinaten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Mutter aller MovieClips: _root (_level0) .. . . . . . . . . . . . . . . . .
131 132 135 136 136 138 147 148 150 151 153
13 Zeichnungsmethoden der MovieClip-Klasse .. . . . . . . . . . . . . . . . . . . . . . . . 13.1 Linien, Kurven und Füllungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2 Verlaufsfüllungen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.3 Ereignisse, Methoden, Eigenschaften . . . . . . . . . . . . . . . . . . . . . . . . . 13.4 Geometrische Grundfiguren (Kreis, Oval, Polygon, Stern) .. . . .
155 156 160 165 165
14 String, Textfield, Textformat .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.1 Strings erzeugen und String-Wert auslesen .. . . . . . . . . . . . . . . . . . . 14.2 Länge .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.3 Verkettung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.4 Escape-Sequenzen und Sonderzeichen . . . . . . . . . . . . . . . . . . . . . . . . 14.5 ASCII-Zeichensatz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.6 Teilstrings extrahieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.7 Teilstrings ermitteln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.8 Groß-/Kleinbuchstaben .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.9 Text, Textfelder .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.10 Textauszeichnung/-formatierung .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.10.1 TextFormat .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.10.2 Formatierung mit HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.10.3 Formatierung mit CSS .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.11 Darstellung größerer Textmengen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.12 Eingabetext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
173 173 174 174 174 176 178 179 179 180 184 184 188 190 192 193
15 Math-Klasse .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.1 Eigenschaften: Konstanten .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2 Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2.1 Auf- und Abrunden .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2.2 Zufallszahlen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2.3 Weitere Methoden .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
199 199 200 200 201 205
16 Color-/ColorTransform-Klasse .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.1 Klassenpakete und Instanziierung von Klassen .. . . . . . . . . . . . . . . 16.2 Vollständiges Einfärben .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16.3 Einfärben mit Hilfe einzelner Farbkanäle . . . . . . . . . . . . . . . . . . . . .
207 207 208 209
17 Maus und Tastatur .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.1 Die Mouse-Klasse .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.1.1 Eigene Cursor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.1.2 Mausereignisse .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.2 Tastatur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
213 213 213 215 218
x
Inhaltsverzeichnis
18 BitmapData- und Filter-Klasse .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.1 Bitmap versus Vektor .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.2 Instanziierung der BitmapData-Klasse . . . . . . . . . . . . . . . . . . . . . . . . 18.3 Eigenschaften der BitmapData-Klasse . . . . . . . . . . . . . . . . . . . . . . . . 18.4 Methoden der BitmapData-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.4.1 Dispose() .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.4.2 FillRect(), floodFill() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.4.3 GetPixel(), getPixel32(), setPixel(), setPixel32() . . . . . . . . . . . . . . 18.4.4 LoadBitmap(), draw(), copyPixels(), clone() . . . . . . . . . . . . . . . . . . 18.4.5 Noise(), perlinNoise() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.5 Optimierung mit Hilfe von BitmapData .. . . . . . . . . . . . . . . . . . . . . . 18.6 Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.6.1 Bevel-Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.6.2 Blur-Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.6.3 DropShadow-Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.6.4 Glow-Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.6.5 GradientBevel-Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.6.6 GradientGlow-Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.6.7 ColorMatrix-Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.6.8 Convolution-Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.6.9 DisplacementMap-Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
221 221 223 224 224 225 226 227 229 232 235 235 236 237 238 238 239 239 240 241 242
19 Sound 19.1 19.2 19.3
245 245 248 249
20 Externe Assets .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20.1 Laden externer swf-Dateien .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20.2 Eigenschaften der geladenen Dateien . . . . . . . . . . . . . . . . . . . . . . . . . 20.3 Anzeigen der Ladekontrolle (Preloader mit getBytesLoaded()) . 20.4 Überblenden bei Ladevorgängen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20.5 Alternative Ladekontrolle (Preloader mit der MovieClipLoaderKlasse) .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20.6 Beispiel modulare Website .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20.7 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
251 252 257 263 270
.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sound abspielen und stoppen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einen Sound loopen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Externe Sounds .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
275 276 280
21 XML .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.1 Aufbau von XML-Dokumenten .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.2 Laden von XML-Dokumenten (new XML, load(), onLoad, onData) .. . . . . . . . . . . . . . . . . . . . . . . . . 21.3 Zugriff auf den gesamten Inhalt einer XML-Datei (firstChild, ignoreWhite) .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21.4 Verarbeitung einzelner Daten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
281 281
22 Tween- und TransitionManager-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22.1 Tween-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22.2 Beschleunigungsklassen und -methoden . . . . . . . . . . . . . . . . . . . . . . 22.3 Eigenschaften (duration, finish, FPS, position, time) .. . . . . . . . . . 22.4 Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
284 286 287 291 291 292 294 294
Inhaltsverzeichnis
xi
22.5 22.6 22.7 22.8 22.9
Ereignisse .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sukzessive Animation verschiedener Eigenschaften . . . . . . . . . . . TransitionManager-Klasse .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eigenschaften, Methoden und Ereignisse .. . . . . . . . . . . . . . . . . . . . . Übergangstypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
295 296 298 299 300
23 Debugging .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23.1 Fehlertypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23.2 Erste Fehlerkontrolle mit Auto-Format .. . . . . . . . . . . . . . . . . . . . . . . 23.3 Das Nachrichtenfenster .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23.4 Der integrierte Debugger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
303 303 304 308 312
24 Geskriptete Animationen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.1 Animationskategorien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.2 Einmalige Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.3 Animation mit onEnterFrame und fester Schrittweite .. . . . . . . . . 24.4 Animation mit setInterval() und fester Schrittweite .. . . . . . . . . . . 24.5 Zeitsteuerung .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.6 Dynamische Schrittweite . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.7 Feste Schrittanzahl . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.8 Fehlerquelle Schleife . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.9 Überprüfung von Grenzwerten .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.10 Grenzbedingung und Verwendung von Animationsobjekten . . . 24.10.1 Löschen des Objekts .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.10.2 Bouncing off . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.10.3 Screen wrap .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24.10.4 Neue Zufallsposition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
317 317 318 319 325 329 330 333 337 338 339 340 342 343 344
25 Trigonometrie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25.1 Einige Grundlagen der planen Trigonometrie .. . . . . . . . . . . . . . . . . 25.2 Abstände zwischen Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25.3 Kreise und Ellipsen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25.4 Spiralen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25.5 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25.6 Spiralförmige Animation eines Objekts . . . . . . . . . . . . . . . . . . . . . . . 25.7 Sinustäuschung .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25.8 Drehung eines Objekts zu einem anderen Objekt .. . . . . . . . . . . . . 25.9 Interaktiver Schlagschatten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25.10 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
345 345 347 349 351 354 354 357 358 360 362
26 Effekte (Text) .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26.1 Text vergrößern und ein-/ausblenden .. . . . . . . . . . . . . . . . . . . . . . . . . 26.2 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26.3 Einzelne Zeichen einfärben .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26.4 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26.5 Einzelne Zeichen vergrößern und verkleinern . . . . . . . . . . . . . . . . . 26.6 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26.7 Text mit Farbverlauf .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26.8 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26.9 Schreibmaschineneffekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
365 365 371 372 375 377 378 378 383 385
xii
Inhaltsverzeichnis
26.10 26.11 26.12
Text einblenden aus Zufallsbuchstaben .. . . . . . . . . . . . . . . . . . . . . . . 386 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 389 Weitere Effekte .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
27 Effekte (Grafik) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27.1 Einfärben über einzelne Farbkanäle .. . . . . . . . . . . . . . . . . . . . . . . . . . 27.2 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27.3 Organische Moleküle .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27.4 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27.5 Beleuchtungseffekt mit Maske .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27.6 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27.7 Überblendeffekt mit Maske .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27.8 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27.9 Mosaik-Effekt .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27.10 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
391 391 397 399 402 402 405 406 409 410 413
28 Effekte (Maus) .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28.1 Maus(ver)folger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28.2 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28.3 Initialisierung von Animationen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28.4 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28.5 Eigenschaftsänderungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28.6 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
415 416 426 428 432 433 435
29 Interface und Navigation .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29.1 Check Boxes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29.2 Radio Buttons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29.3 Combo-Box . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29.4 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29.5 Variante . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29.6 Slider . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29.7 Drag and Drop-Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29.8 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29.9 Einfaches Fenster-System mit scale9Grid . . . . . . . . . . . . . . . . . . . . . 29.10 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29.11 Varianten und Erweiterungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
437 437 440 442 446 447 448 449 453 454 460 462
30 Menü-Systeme .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30.1 Kreismenüs .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30.2 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30.3 Elliptisches Menü . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30.4 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30.5 Akkordeon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30.6 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30.7 Drop Down-Menü .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30.8 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
463 464 471 473 479 480 485 486 490
31 Bildergalerien . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.1 Einfache Galerie mit Slider und internen Assets .. . . . . . . . . . . . . . 31.2 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31.3 Eine Variante .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
493 494 502 503
Inhaltsverzeichnis
xiii
31.4 31.5 31.6 31.7
Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Galerie mit externen Assets .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einige Varianten .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
506 506 518 520
.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Pairs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Puzzle .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
521 521 527 528 539
33 Ressourcen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33.1 Literatur .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33.2 Lernvideos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33.3 Webseiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
543 543 545 546
32 Spiele 32.1 32.2 32.3 32.4
Sachverzeichnis .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 551
Abbildungsverzeichnis
Abbildung 1: Abbildung 2: Abbildung 3: Abbildung 4: Abbildung 5: Abbildung 6: Abbildung 7: Abbildung 8: Abbildung 9: Abbildung 10: Abbildung 11: Abbildung 12: Abbildung 13: Abbildung 14: Abbildung 15: Abbildung 16: Abbildung 17: Abbildung 18: Abbildung 19: Abbildung 20: Abbildung 21: Abbildung 22: Abbildung 23: Abbildung 24: Abbildung 25: Abbildung 26: Abbildung 27: Abbildung 28: Abbildung 29: Abbildung 30: Abbildung 31: Abbildung 32: Abbildung 33: Abbildung 34: Abbildung 35:
Standardsymbole zur Entwicklung eines Flowcharts . . . . . . . . . 14 Beispielhaftes Flussdiagramm (Programmlogik) . . . . . . . . . . . . . 15 Variablen- und Funktionsdefinition . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Anweisungsblock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Argument, Parameter, Schleife .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 Anweisung, Bedingung, Funktionsaufruf .. . . . . . . . . . . . . . . . . . . . 25 Integrierter AS-Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Zuweisung eines Objektskripts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Fixieren des Skriptfensters .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Operanden und Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 Codehinweise zu Datentypen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Arraylänge, -index und -inhalte .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 Verwendung einer parametrisierten Funktion .. . . . . . . . . . . . . . . . 90 Vorgegebene Funktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Fehlermeldung bei Endlosschleife . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Eigenschaftsfenster eines MovieClips auf der Bühne .. . . . . . . 113 Verschachtelter MovieClip auto .. . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 Pfadhierarchie .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116 Relative Adressierung mit _parent . . . . . . . . . . . . . . . . . . . . . . . . . . 119 Kartesisches Koordinatensystem .. . . . . . . . . . . . . . . . . . . . . . . . . . . 119 Kartesisches Koordinatensystem in Flash (Hauptzeitleiste) .. 120 Skalieren eines MovieClips .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123 Spiegelung mit _xscale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124 Positionierung der MovieClips .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 Überlappende MovieClips .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 Beispielhafter Aufbau der Übung . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 Kollisionserkennung mit hitTest() .. . . . . . . . . . . . . . . . . . . . . . . . . . 150 Ergebnis von getBounds() bei verschiedenen Koordinatensystemen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152 Beispiel einer geskripteten Zeichnung . . . . . . . . . . . . . . . . . . . . . . 157 Haus und Grundstück des Autors . . . . . . . . . . . . . . . . . . . . . . . . . . . 158 Zur Funktionsweise von curveTo() .. . . . . . . . . . . . . . . . . . . . . . . . . 159 Zweifarbiger, radialer Farbverlauf Rot – Orange . . . . . . . . . . . . 161 Koronaartiger Grafikeffekt mit Verlaufsfüllung .. . . . . . . . . . . . . 163 Verschiebung des Verlaufsmittelpunkts . . . . . . . . . . . . . . . . . . . . . 163 Oval . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 xv
xvi
Abbildung 36: Abbildung 37: Abbildung 38: Abbildung 39: Abbildung 40: Abbildung 41: Abbildung 42: Abbildung 43: Abbildung 44: Abbildung 45: Abbildung 46: Abbildung 47: Abbildung 48: Abbildung 49: Abbildung 50: Abbildung 51: Abbildung 52: Abbildung 53: Abbildung 54: Abbildung 55: Abbildung 56: Abbildung 57: Abbildung 58: Abbildung 59: Abbildung 60: Abbildung 61: Abbildung 62: Abbildung 63: Abbildung 64: Abbildung 65: Abbildung 66: Abbildung 67: Abbildung 68: Abbildung 69: Abbildung 70: Abbildung 71: Abbildung 72: Abbildung 73: Abbildung 74: Abbildung 75: Abbildung 76: Abbildung 77: Abbildung 78: Abbildung 79: Abbildung 80: Abbildung 81: Abbildung 82: Abbildung 83:
Abbildungsverzeichnis
Händisch erstelltes Textfeld .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Textformatierung mit der TextFormat-Klasse .. . . . . . . . . . . . . . . Erweiterte Textformatierung mit der TextFormat-Klasse .. . . . Verwendung der Komponente UIScrollBar .. . . . . . . . . . . . . . . . . Eingabefeld für die Mail-Adresse .. . . . . . . . . . . . . . . . . . . . . . . . . . Einfärben eines MovieClips . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Erweiterte Farboptionen einer MovieClip-Instanz .. . . . . . . . . . Eigener Mauszeiger in Aktion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Erzeugung eines Punktrasters mit setPixel() .. . . . . . . . . . . . . . . . Erzeugung eines zufälligen Musters mit setPixel32() .. . . . . . . Ausschnittweises Kopieren einer Grafik .. . . . . . . . . . . . . . . . . . . . Erzeugung einer Störung mit Hilfe der perlinNoise()Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Skinners „Pflanzengenerator“ .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bevel-Filter . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bevel-Filter mit maximaler Stärke . . . . . . . . . . . . . . . . . . . . . . . . . . GradientBevel-Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 × 5-Matrix des ColorMatrix-Filters .. . . . . . . . . . . . . . . . . . . . . . . DisplacementMap-Filter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Einstellungen für das Sound-Verhalten .. . . . . . . . . . . . . . . . . . . . . Weitere Einstellungen für das Sound-Verhalten . . . . . . . . . . . . . Externer Ladevorgang in der Hauptzeitleiste . . . . . . . . . . . . . . . . Auswirkungen von _root auf externe Ladeprozesse .. . . . . . . . . Verschachtelte Ordnerstruktur für externe Ladevorgänge . . . . Fehlermeldung bei fehlgeschlagenem Ladeaufruf . . . . . . . . . . . Eigenschaftsänderungen bei externen Ladevorgängen . . . . . . . Fehlerhafte Positionierung einer extern geladenen Grafik .. . . Bandbreiten-Profiler zur Kontrolle des Ladevorgangs . . . . . . . Konfiguration der Download-Simulation .. . . . . . . . . . . . . . . . . . . Simulation eines externen Ladevorgangs .. . . . . . . . . . . . . . . . . . . Zeichnen der Elemente des Ladebalkens . . . . . . . . . . . . . . . . . . . . Aufbau der Anwendung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Phase 1 des Ladevorgangs .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Phase 2 des Ladevorgangs .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Phase 3 des Ladevorgangs .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Datei literatur1.xml im Internet Explorer .. . . . . . . . . . . . . . . . . . . Fehlerhaftes XML-Dokument im Internet Explorer .. . . . . . . . . Beschleunigungseffekt Back.easeIn .. . . . . . . . . . . . . . . . . . . . . . . . Beschleunigungseffekt Bounce.easeIn . . . . . . . . . . . . . . . . . . . . . . Beschleunigungseffekt Elastic.easeIn . . . . . . . . . . . . . . . . . . . . . . . Beschleunigungseffekt Regular.easeIn . . . . . . . . . . . . . . . . . . . . . . Beschleunigungseffekt Strong.easeIn . . . . . . . . . . . . . . . . . . . . . . . Sukzessives Ausführen mehrerer Tweens .. . . . . . . . . . . . . . . . . . . Abprallen am rechten Rand .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fehlermeldung im Compiler-Fenster (1) . . . . . . . . . . . . . . . . . . . . Fehlermeldung im Compiler-Fenster (2) . . . . . . . . . . . . . . . . . . . . Fehlermeldung im Compiler-Fenster (3) . . . . . . . . . . . . . . . . . . . . Fehlermeldung im Compiler-Fenster (4) . . . . . . . . . . . . . . . . . . . . Fehlermeldung im Compiler-Fenster (5) . . . . . . . . . . . . . . . . . . . .
180 186 186 193 196 208 209 214 228 228 231
234 236 237 237 239 240 243 246 247 253 255 255 256 258 260 261 262 262 269 273 274 274 274 282 283 293 293 293 293 293 297 297 304 305 306 306 307
Abbildungsverzeichnis
xvii
Abbildung 84: Abbildung 85: Abbildung 86: Abbildung 87: Abbildung 88: Abbildung 89: Abbildung 90: Abbildung 91: Abbildung 92:
Auflistung aller Variablen zur Laufzeit .. . . . . . . . . . . . . . . . . . . . . Abspeichern des Inhalts des Nachrichtenfenster .. . . . . . . . . . . . Debugger-Fenster . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Animation durch permanente Änderung der Drehung . . . . . . . Horizontale und vertikale Bewegung .. . . . . . . . . . . . . . . . . . . . . . . Bewegung und Vorzeichenumkehr .. . . . . . . . . . . . . . . . . . . . . . . . . Positionsberechnung über wechselnde Entfernung (1) .. . . . . . Positionsberechnung über wechselnde Entfernung (2) .. . . . . . Berechnung der Schrittweite aus Schrittanzahl und Entfernung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 93: Bewegung und Grenzbedingung . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 94: Überschreiten des Grenzwertes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 95: Animationsobjekt innerhalb/außerhalb der Bühne .. . . . . . . . . . Abbildung 96: Abprallen am rechten Screenrand .. . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 97: Rechtwinkliges Dreieck . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 98: Abstandsmessung zwischen zwei beliebigen Punkten und der Satz des Pythagoras .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 99: Trigonometrische Funktionen und Kreisberechnung .. . . . . . . . Abbildung 100: Der Einheitskreis .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 101: Abstandsmessung zwischen MovieClips . . . . . . . . . . . . . . . . . . . . Abbildung 102: Spirale mit linear anwachsendem Radius .. . . . . . . . . . . . . . . . . . . Abbildung 103: Spirale mit exponentiell anwachsendem Radius .. . . . . . . . . . . . Abbildung 104: Spiralförmig zu animierende Rakete . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 105: Spiralförmig animierter Bleistift . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 106: Sinustäuschung .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 107: Ausrichtung des Fahrzeugs vor der Drehung zur Maus . . . . . . Abbildung 108: Veränderung des Schlagschattens per Maus . . . . . . . . . . . . . . . . . Abbildung 109: Animierter Text (Änderung von Größe, Deckkraft, Position) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 110: Einbetten eines Fonts in der Bibliothek . . . . . . . . . . . . . . . . . . . . . Abbildung 111: Animiertes Einfärben einzelner Buchstaben .. . . . . . . . . . . . . . . . Abbildung 112: Text mit statischem Farbverlauf .. . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 113: Farbverlauf Rot – Gelb – Grün .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 114: Verschiedene Ausrichtungen des Farbverlaufs .. . . . . . . . . . . . . . Abbildung 115: Aufbau der Animation mit Farbverläufen . . . . . . . . . . . . . . . . . . . Abbildung 116: Schreibmaschineneffekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 117: Text einblenden aus Zufallsbuchstaben .. . . . . . . . . . . . . . . . . . . . . Abbildung 118: Färben über einzelne Farbkanäle .. . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 119: Einfärbung eines per Filter-Klasse erstellten Schattens . . . . . . Abbildung 120: Organische Erscheinung per Ebeneneffekt . . . . . . . . . . . . . . . . . . Abbildung 121: Simulation eines Spotlights .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 122: Überblendeffekt mit Hilfe einer Maske . . . . . . . . . . . . . . . . . . . . . Abbildung 123: Mosaik-Effekt (1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 124: Mosaik-Effekt (2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 125: Ermittlung eines Farbpixels .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 126: Text als Mausverfolger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 127: Uhr in Ruhestellung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 128: Uhrteile folgen der Maus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 129: Aufbau der Uhr ohne Zeitanzeige .. . . . . . . . . . . . . . . . . . . . . . . . . .
313 313 314 319 320 323 332 332
334 339 339 341 343 345
346 346 347 347 352 353 355 356 358 359 359
365 366 373 379 380 381 382 385 387 391 397 399 403 406 410 410 412 416 419 419 422
xviii
Abbildung 130: Blätteranimation nach Mausbewegung .. . . . . . . . . . . . . . . . . . . . . Abbildung 131: Alternative Objektformen .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 132: Formänderung nach Mausbewegung .. . . . . . . . . . . . . . . . . . . . . . . Abbildung 133: Check Boxes des Workshops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 134: Aufbau von mcRecht .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 135: Radio Buttons .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 136: Aufbau von mcKreis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 137: Combo-Box des Workshops . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 138: Bibliotheks-Elemente der Drag and Drop-Übung .. . . . . . . . . . . Abbildung 139: Einzelelemente eines Fensters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 140: Positionsbestimmung der Fensterteile .. . . . . . . . . . . . . . . . . . . . . . Abbildung 141: Kreisförmiges Menü .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 142: Aktiviertes Kreismenü .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 143: Aufbau des elliptischen Menüs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 144: Anzeigen der Menüelemente .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 145: Scheitelpunkte der Animation .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 146: Akkordeon-Menü des Workshops .. . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 147: Aufbau des MovieClips mcFenster .. . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 148: Drop Down-Menü des Workshops . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 149: Originalgrafik (verkleinert) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 150: Thumbnail-Typ 1 .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 151: Thumbnail-Typ 2 .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 152: Thumbnail-Typ 3 .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 153: Infografik mit Bildergalerie .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 154: Aufbau des MovieClips mcBild .. . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 155: Aufbau des MovieClips mcSlider .. . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 156: Aufbau des MovieClips mcMaske . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 157: Variante der Bildergalerie .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 158: Aufbau des MovieClips mcMaske bei nicht aktivierter Maske .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 159: Aufbau der Galerie mit externen Assets .. . . . . . . . . . . . . . . . . . . . Abbildung 160: Ordnerstruktur der Galerie .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 161: Aufbau der Thumbnails .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 162: Aufbau Memory-Spiel .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 163: Karten und Bilderset .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 164: Zufallsverteilung der Karten in aKarten .. . . . . . . . . . . . . . . . . . . . Abbildung 165: Puzzlespiel .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 166: Umrisse der Puzzleteile .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 167: Registrierungspunkt und gedachtes Quadrat .. . . . . . . . . . . . . . . . Abbildung 168: Puzzleteile entsprechend der Array-Elemente . . . . . . . . . . . . . . . Abbildung 169: Begrenzungsrechteck und Registrierungspunkt von mcPuzzle6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 170: Puzzleteile ohne Maske .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Abbildung 171: Puzzleteile mit BevelFilter .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Abbildungsverzeichnis
428 433 433 437 438 440 441 442 449 455 456 464 466 473 475 477 480 481 486 494 494 494 494 495 496 496 497 503
504 507 507 514 521 522 523 528 529 529 530
533 535 536
1
Einleitung
ActionScript (kurz: AS), die in Flash integrierte Script-Sprache, hat sich im Laufe der Zeit zu einem unverzichtbaren Tool im Arsenal des Webentwicklers gemausert. Interaktive Anwendungen aller Art bis hin zu sogenannten Rich Internet Applications lassen sich mittlerweile mit Hilfe dieser Sprache realisieren. Diese Mächtigkeit hat allerdings ihren Preis, steigt doch mit den Möglichkeiten zugleich auch die Lernkurve, um sich AS anzueignen, mit jeder FlashVersion weiter an. Darum möchte Sie das vorliegende Buch bei der Hand nehmen und behutsam durch den Script-Dschungel führen. Einsteiger-Bücher gibt es viele, und wenn man eines kennt, kennt man die meisten anderen auch. Deren größtes Manko ist der Versuch, möglichst viele AS-Befehle abzudecken. Das sieht zwar im Inhaltsverzeichnis beeindruckend aus, doch spätestens dann, wenn man an einem konkreten Projekt sitzt, werden einem allzu schmerzhaft die Lücken bewusst, die man sich angeeignet hat. Denn derartige Bücher führen in die Themen anhand von Code-Schnipseln ein, die zwar leicht verdaulich sind, aber kaum in die Tiefe gehen. Außerdem stehen diese Schnipsel oft zusammenhanglos und mitunter etwas verlegen im Raum herum. Für den Anfänger fehlt daher nicht selten die Orientierung, wo man sie in einem konkreten, dem Produktionsalltag nahe stehenden Zusammenhang einsetzen kann. Das ist etwa so, als wollte man für eine Reise nach Frankreich Französisch lernen und würde dabei ein Lexikon verwenden anstelle eines Sprachführers. Daher verfolgt das vorliegende Buch einen anderen Ansatz. Wir verzichten hier auf eine lexikalische Auflistung und Behandlung aller AS-Sprachelemente. Für diesen Zweck genügt zumindest prinzipiell die Flash-Hilfe. Statt dessen konzentrieren wir uns auf ausgesuchte Konzepte und Elemente sowie deren Be-
deutung in der Praxis. Eine derartige Auswahl muss notgedrungen subjektiv ausfallen. Immerhin spiegeln sich in ihr zahlreiche Jahre Programmier- und Lehrerfahrung wieder, so dass sie nicht völlig willkürlich ist. Die Konzepte und Elemente werden zwar am konkreten Beispiel von ActionScript behandelt, besitzen jedoch zu einem beträchtlichen Teil sprachunabhängig Gültigkeit. Dazu gehören beispielsweise Variablen, Arrays oder Kontrollstrukturen, die von der Idee her auch in anderen Sprachen wie PHP und Javascript vorkommen. Ebenso ist die Beschreibung des Entwicklungsprozesses eines Programms nicht an Flash gebunden. Damit werden Sie in die Lage versetzt, sich im Fall der Fälle relativ einfach in andere Sprachen einzuarbeiten, falls Sie sich die vorgestellten Konzepte angeeignet haben. Denn es gehört zu den angenehmen Seiten der Programmierung, dass zahlreiche Sprachen mit vergleichbaren Konzepten arbeiten, so dass solide Kenntnisse in einer Sprache das Wechseln zu einer anderen durchaus erleichtern können. Man kann mitunter sogar Lösungen aus einer Sprache in eine andere übertragen, ohne diese Lösung selbst von Grund auf neu entwickeln zu müssen. Der erste Teil des Buches beginnt mit einem Überblick über die Geschichte von ActionScript, gefolgt von drei Kapiteln, die über die Vorgehensweisen bei der Programmentwicklung u. a. anhand eines in Pseudo-Code formulierten Programms zum Kochen von Kaffee – Genuss für Geist und Körper gleichermaßen – informieren. Gerade die relative Zugänglichkeit von AS verleitet schnell dazu, einfach mal „drauf los zu programmieren“ – und spätestens dann, wenn man den eigenen Code nach einigen Wochen erweitern oder ändern muss, türmt sich ein Berg an Problemen auf, weil man ihn aufgrund mangelhafter Planung und Strukturierung weder richtig versteht noch
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
1
2
einfach modifizieren kann. Daher kann die Bedeutung sinnvoller Planung im voraus gar nicht oft genug betont werden. Anschließend folgen kapitelweise Beschreibungen einzelner Sprachelemente und Strukturen wie Operatoren, Variablen, Kontrollstrukturen sowie der wichtigsten Klassen wie MovieClip, BitmapData, XML etc. Besonderen Raum nimmt die MovieClip-Klasse ein, da kaum eine Applikation ohne sie auskommen kann. Diese Kapitel stellen das jeweilige Element vor und geben einzelne Codebeispiele, um ihren Einsatz zu verdeutlichen. Damit man auch tatsächlich erfährt, was es mit diesen Elementen in der freien Wildbahn da draußen auf sich hat, bietet der zweite Teil zahlreiche Workshops, in denen sie immer wieder in den verschiedensten Zusammenhängen auftauchen. Dadurch gewinnt das ansonsten recht abstrakte Erlernen von „Vokabeln“ einen unmittelbaren Praxisbezug, denn die behandelten Themen kommen zu weiten Teilen so oder in einer abgewandelten Form auch tatsächlich im Produktionsalltag vor. Aus didaktischen Gründen musste zwar manches gekürzt oder geändert werden, da der Platz im vorliegenden Buch einfach nicht ausreichte, alles erschöpfend zu behandeln. So manches dort verwendete Skript lässt sich noch optimieren. Damit ginge jedoch die relativ leichte Zugänglichkeit und Verständlichkeit verloren. Dessen ungeachtet bieten die Workshops genügend Masse, um einerseits die Sprache am lebenden Objekt einzuüben und andererseits Einblick in die tägliche Praxis der programmierorientierten Flash-Entwicklung zu gewinnen. An dieser Stelle bemerkt der Autor zugegebenermaßen mit einer gewissen inneren Freude, dass man Skripte aus einem Buch nicht durch schlichtes Kopieren in Flash einfügen kann, sprich: Das Lernen mit diesem Buch macht nur Sinn, wenn Sie den angesprochenen Code wirklich selbst eingeben. Wer Buchstabe für Buchstabe alles abtippt, wird eher darüber nachdenken, was er tut, als wenn man einfach nur kopiert. Kopieren und kapieren sind zwei verschiedene Dinge. Um das Schreiben zu erleichtern, befindet sich am Ende der meisten Workshops des zweiten Teils eine komplette Auflistung des aktuell behandelten Codes. Innerhalb der Workshops wird er abschnitt-, mitunter auch zeilenweise entwickelt und jeweils besprochen. Einige Übungen im ersten und nahezu alle Beispiele im zweiten Teil arbeiten mit einer sogenannten
Kapitel 1 Einleitung
Standarddatei. Damit ist lediglich eine simple FlashDatei gemeint, die über folgende Eigenschaften verfügt:
• Größe 800 × 600, • Bildwiederholrate 18 BpS, • Hintergrundgrafik mit vertikalem Farbverlauf hel-
les zu dunklem Blau, • Hauptzeitleiste mit den Ebenen actions, objects, gegebenenfalls zusätzlich bgd, • Als Grundobjekt ein einfacher MovieClip in Form einer Kugel (Kreis mit radialem Farbverlauf). Selbstverständlich sehen Ihre Flash-Dateien im Produktionsalltag anders aus: Mal muss ein Banner in der Größe 720 × 90, mal eine Site im Seitenverhältnis 16 × 9, mal eine interaktive Animation mit 25 BpS vor einem schwarzen Hintergrund erstellt werden – kurz, es gibt endlos viele verschiedene Möglichkeiten, mit welchen Einstellungen man im Einzelnen angemessen arbeitet. Da es im vorliegenden Buch um die Aneignung von Techniken, nicht jedoch um die optimale Realisierung spezifischer Applikationen geht, verwenden wir einfach eine Standardeinstellung, die Sie Ihren besonderen Bedürfnissen im Einzelfall anpassen müssen. Obgleich das Buch aus zwei Teilen besteht, ist es nicht notwendig, erst Teil 1 und anschließend Teil 2 zu goutieren. Wer die theoretischen Grundlagen beherrscht, kann gerne direkt mit dem Workshop-Teil arbeiten. In dem Fall sollte man allerdings das Kapitel zu Framework und Code-Organisation kennen, da dort eine Vorgehensweise erläutert wird, die in den Workshops zur Anwendung kommt. Wer keinerlei Skriptkenntnisse besitzt, sollte dagegen systematisch alle Kapitel des ersten Teils durcharbeiten, wobei es sich empfiehlt, die vorliegende Reihenfolge einzuhalten, da sie aufeinander bezogen sind. Die Kapitel des zweiten Teils dagegen liegen unabhängig voneinander vor, so dass ein beliebiges Kapitel zum Lernen verwendet werden kann. Wer sich für Texteffekte interessiert, schlägt eben dort nach, ohne vorher etwa das Kapitel Grafikeffekte gelesen haben zu müssen. Die Workshops fassen thematisch zusammenhängende Übungen in Kapiteln zusammen. So finden sich etwa im Kapitel Effekte (Maus) verschiedene Effekte, die in irgendeiner Form auf den Cursor bezogen sind. Die Kapitel werden mit einigen allgemeinen Hinweisen zur jeweiligen Bedeutung der konkreten Themen
und Übungen eingeleitet. Danach folgt ein Überblick über das Ziel der Übung sowie eine sukzessive Auflistung der Arbeitsschritte in Form von Handlungsanweisungen. Die Schritte sind zumeist in einzelne Blöcke unterteilt, die anschließend erläutert werden. Besonderes Gewicht wird dabei auf die zugrunde liegende Logik gelegt. Sie ist letztlich wichtiger als die korrekte Syntax: Versteht man nicht, wie ein Problem gelöst wird, nützt eine fehlerfreie Syntax nichts, weil man nie in die Lage versetzt wird, eine eigene Problemlösung zu entwickeln. Eine Syntax zu erlernen ist letztlich ein reiner Gedächtnisvorgang, während die Logik einer weit darüber hinausgehenden Verstandesleistung bedarf. An manchen Stellen wird eine konkrete Übung durch Hinweise auf Varianten ergänzt, deren Code mitunter vorgestellt wird und mitunter auch nur erwähnt wird. Solche Stellen sollten Sie als Aufforderung verstehen, um eigene Lösungen zu entwickeln.
3
Der in den meisten Praxisbeispielen vorgestellte Code lässt sich zwar mit einigen Änderungen direkt auf eigene Projekte übertragen, doch sollte man sich im Klaren sein, was er im Einzelnen bedeutet. Dieses Buch ist in erster Linie als Lehrbuch konzipiert und nicht als Kochbuch mit einfach zu kopierenden Rezepten bzw. Codeschnipsel. Viele Probleme ermöglichen mehrere Lösungswege und so kann hier vorgestellter Code nur als ein möglicher, in der Praxis bewährter Weg, aber beileibe nicht als allein selig machender verstanden werden. Gerne würde der Autor behaupten, er habe aus didaktischen Gründen bewusst einige Fehler eingebaut. Tatsächlich sind derartige Fehler bei dem vorliegenden Seitenumfang einfach unvermeidlich und der menschlichen Schwäche des Autors geschuldet. Dafür sei der Leser hier bereits um Entschuldigung gebeten. Happy Coding!
ActionScript: Geschichte, Versionen, Ausblick
Flash stellt eine vollständige Entwicklungsumgebung und ein Autorensystem für Online- und Offlineanwendungen nahezu jeder Art dar. Aufgrund seiner umfassenden Funktionalität hat es sich innerhalb relativ weniger Jahre von einem ursprünglich doch recht simpel gestrickten Animationsprogramm zum Standard insbesondere bei der Entwicklung von hoch interaktiven, multimedialen Anwendungen gemausert. Zu diesem enormen Erfolg trug ganz wesentlich die integrierte Skriptsprache ActionScript bei, die mittlerweile annähernd so mächtig geworden ist wie eine vollwertige Programmiersprache. Dabei wiesen die sehr bescheidenen Anfänge von ActionScript keinesfalls in die Richtung, die die Sprache nach einigen Jahren eingeschlagen hat. Denn Flash erblickte unter dem Namen FutureSplashAnimator als nettes kleines Animationsprogramm in Konkurrenz zu animierten gif-Dateien das Licht der Welt. 1996 wurde das Programm samt deren Entwickler von der im sonnigen Kalifornien ansässigen Firma Macromedia übernommen. Da der Schwerpunkt auf Animation lag, war die Scriptfähigkeit notwendigerweise bedeutungslos. In den ersten drei Flash-Versionen bestand sie lediglich aus sehr einfachen Befehlen, mit denen man Navigationen ähnlich einer Videosteuerung vornehmen konnte. So war es beispielsweise möglich, eine Zeitleiste anzuhalten und abzuspielen oder zu einem bestimmten Frame zu springen. Dabei handelte es sich um weitgehend vorgefertigte Verhaltensweisen, also fertigen Skripten, die man mit Hilfe von Eingabefeldern parametrisieren konnte. Ein direkter Zugriff auf den Code existierte nicht. Erst mit Flash 4 schlug gewissermaßen die Geburtsstunde von ActionScript als einer eigenständigen Scriptsprache mit Elementen, wie man sie von anderen Sprachen her gewohnt ist. Dazu gehören Schleifen,
2
Variablen und Ausdrücke, was erstmals das Erstellen umfangreicher, durchaus anspruchsvollerer Skripte ermöglichte. Offiziell firmierte die Sprache unter der Bezeichnung actions, intern verwendete man schon den heute geläufigen Namen ActionScript . Ab dieser Version wurde die Sprache recht konsequent ausgebaut und immer näher an internationale Standards herangeführt. Bereits Flash 5 erlaubte den uneingeschränkten Zugriff auf den Code mit Hilfe eines integrierten Editors, ohne jedoch auf die von den Vorgängern her bekannte Möglichkeit eines simplen Zusammenklickens von Codeblöcken völlig zu verzichten. Dahinter stand die eigentlich bis Flash 9 bzw. CS 3 gültige Philosophie, zwei verschiedene Zielgruppen anzusprechen: Einerseits den Programmierer, der nicht davor zurückschreckt, komplexe Skripte zu entwickeln, und den Designer, der sich zwar mehr auf die Animationsfähigkeiten von Flash konzentriert, aber gleichzeitig auf eine einfache Art Interaktionen skripten möchte. Da die Sprache am ECMA 262- bzw. ECMAScript3-Standard ausgerichtet wurde, fanden sich Programmierer, die zuvor insbesondere mit Javascript gearbeitet hatten, recht schnell zurecht. Ein positiver Nebeneffekt war die relativ leichte Portierbarkeit von Code aus einer Sprache in eine andere. Um die Entwicklung zu vereinfachen, konnte man zudem in sogenannte Smart-Clips Code und Objekte hineinpacken, so dass sie sich als fertige Elemente einsetzen ließen, ohne dass man auf den Code zugreifen musste. Ein bisschen erinnerte dieses Konzept an die Behaviors von Director, des damals noch großen Bruders von Flash, das ebenfalls von Macromedia entwickelt wurde. Mit der Versionsnummer 5 wurde Flash, rückblickend betrachtet, tatsächlich erwachsen. Denn es gelang, das Image eines vornehmlich für zappelige Intros
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
5
6
und Animationen geeigneten Entwicklungstools abzulegen (tatsächlich waren diese zappeligen Intros aber weniger dem Tool als vielmehr den betreffenden Entwicklern anzulasten). Nicht zuletzt eben ActionScript zeigte, was, wenn es voll ausgereizt wurde, möglich war – von kompletten, durchaus userfreundlichen, hoch interaktiven Websites über datenbankgestützte Anwendungen bis hin zu den auch heute noch allgegenwärtigen Spielen reichte die Anwendungspalette. Beflügelt – oder muss man sagen: berauscht? – vom Erfolg schob Macromedia mit Flash MX, entstanden zu einer Zeit, als es plötzlich unmodern geworden war, Versionsnummern zu verwenden, eine stark erweiterte Version von ActionScript nach. Die wichtigste, die Sprache partiell bis heute prägende Neuerung war ein alternatives Ereignismodell, das es erlaubte, in einem einzigen Bildscript allen Objekten die benötigten Ereignisse und Aktionen zuzuweisen. Die Verwendung eigenständiger Objektscripte wurde damit prinzipiell obsolet. Dadurch gewann der Sourcecode an Übersichtlichkeit, denn die endlos verschachtelten Objekte (Movieclips und Buttons) machten es bisher ausgesprochen leicht, Code irgendwo regelrecht zu vergraben. Die Standardisierung von AS wurde vorangetrieben durch eine noch striktere Beachtung des ECMA-Standards. Neben einer erweiterten, umfangreichen Funktionsbibliothek verfügte Flash MX über eine neue API, die es erlaubte, dynamische Vektorgrafiken zu programmieren. Aus den Smart-Clips entwickelten sich die komplexeren Komponenten, die mitunter schon den Charakter eigenständiger Anwendungen annehmen konnten. Als kleines Schmankerl bot man Programmieren die Möglichkeit prototypbasierter objektorientierter Programmierung, wenn auch in einer recht rudimentären Form. Der starke Ausbau der Scriptmöglichkeiten verdeutlichte zugleich, in welche Richtung man sich die Weiterentwicklung des Programms dachte – letztlich eine Grundsatzentscheidung, die im Großen und Ganzen bis zur aktuellen Version 11 bzw. CS5 beibehalten wurde. Denn während jede neue Version immer ausgeklügeltere Features für den Programmierer bot, wurden die Designer hinsichtlich der Grafikwerkzeuge bis heute nur mit wenigen interessanten Neuerungen bedacht, nämlich die längst überfälligen Ebeneneffekte in Flash 8, die in der Tat beeindruckende Videounterstützung ab Flash MX 2004 und BonesAnimation sowie (leider nur halbausgegorenes) 3D in
Kapitel 2 ActionScript: Geschichte, Versionen, Ausblick
CS4. Mittlerweile scheint auch die Unterstützung der Designer in der Programmierung zu bröckeln, wird doch ActionScript mit jeder Version komplexer und für einen eher unbedarften Neuling undurchdringlicher. Dabei waren es gerade die Designer, die Flash einst entdeckt hatten! Schon damals wurde als Parole die Entwicklung von RIA, Rich Internet Applications, ausgegeben, also komplette, nicht selten datenbankgestützte OnlineAnwendungen. Dem war jedoch aller Euphorie zum Trotz Flash (noch) nicht gewachsen, wie der OnlineAuftritt von Macromedia selbst bewies. Der Nachfolger Flash MX 2004 machte einen weiteren großen Schritt nach vorn, indem neben die bestehende ActionScript -Version das neue AS 2.0 trat, das u. a. eine bessere Fehlerkontrolle durch eine strikte Typisierung, ein leistungsfähigeres, wenn auch gegenüber vorher umständlicheres Listener-Ereignismodell und vor allem eine an höhere Programmiersprachen angelehnte objektorientierte Programmierung ermöglichte. War Flash mit Version 5 erwachsen geworden, so geschah dasselbe bei ActionScript mit Version 7 (bzw. MX 2004). Hier wie auch an anderer Stelle musste man als Entwickler jedoch der Entwicklungsgeschichte des Programms Tribut zollen: Um die Abwärtskompatibilität zu älteren Flash-Playern zu bewahren, erfolgte die Kontrolle der Typisierung ausschließlich in der Autorenumgebung, während sich der Player wie schon zuvor genügsam mit, um es böse zu formulieren, „Codesalat“ zufrieden gab. Faktisch bedeutete dies, dass AS 2.0 Code durch die AVM (ActionScript Virtual Machine), die den erstellten Code interpretiert, in AS 1.0 umgewandelt wurde. All die schönen neuen Möglichkeiten, die eine an höhere Programmiersprachen angelehnte Struktur erlaubten, wurden damit partiell zunichte gemacht, und auch die Performance entsprach nicht dem, was man erwarten darf. Erst Flash 9 hat hier zu einer grundlegenden Änderung geführt. Aus Entwicklersicht bot ActionScript damit immerhin eine stabilere Programmierung und eine größere Funktionalität. Andererseits allerdings stieg die Lernkurve für Scriptanfänger stark an. Gerade der leichte Zugang zum Scripting war ja bisher eine der wesentlichen Stärken von Flash gewesen. Dessen eingedenk entschied sich Macromedia zu einem Beibehalten von AS 1.0. Konsequenter als der Vorgänger ermöglichte Flash 8, das sich wieder mit einer Versionsnummer
schmücken durfte, objektorientiertes Programmieren. Die Entwickler spendierten dem Programm eine umfangreiche Klassenbibliothek insbesondere zur direkten Manipulation von Bitmapgrafiken auf Pixel ebene sowie zum Up- und Download von Dateien. Der Zugriff auf Bitmaps erlaubte geskriptete Effekte, die zuvor gar nicht oder nur unter erheblichem Aufwand möglich waren. Man denke nur an Bumpmapping, eine Technik, die auf zweidimensionalen Objekten eine dreidimensionale Oberfläche durch Tiefeninformationen simuliert. Allerdings erwiesen sich insbesondere die neuen Filter-Klassen im Gebrauch als recht sperrig. So verlangen sie nach – salopp formuliert – einem Dutzend Pflicht- und einem weiteren Dutzend optionaler Parameter, deren Sinn nicht immer leicht zu erkennen ist. Eigentlich sollte in einem derartigen Fall die Programmhilfe erschöpfend Auskunft erteilen. Aber Flash 8 präsentiert sich als die erste Version, deren Dokumentation neuer Befehle und Elemente so unverständlich ist und deren Beispiele so unglücklich gewählt wurden, dass sie für Skript-Einsteiger faktisch fast schon wertlos ist. Man gewinnt den Eindruck, sie sei von Spezialisten für Spezialisten geschrieben – was aber gerade dem Sinn einer Hilfe widerspricht und angesichts der aufgrund der Sprachkomplexität steil angestiegenen Lernkurve schlicht eine Fehlentscheidung darstellt. Flash 9, 2007 auf dem deutschen Markt erschienen, stellt vielleicht nicht nur einen neuen Meilenstein, sondern sogar einen Wendepunkt dar. Denn diese Version wartet mit einer stark überarbeiteten, neuen Version der Scriptsprache, nämlich AS 3.0 auf, die konsequent objektorientiert aufgebaut ist und sich eigentlich kaum mehr von einer Hochsprache unterscheidet. Ihr wurde im Flash-Player eine eigene Virtual Machine, AVM2, gewidmet, die unabhängig neben der aus Kompatibilitätsgründen beibehaltenen AVM1 enthalten ist. Sie zeichnet sich endlich durch die längst fällige höhere Performance aus, setzt aber eben auch das erheblich komplexere AS 3.0 voraus. Die Version 10 baut AS 3.0 aus, bietet die Möglichkeit, Objekte statt Frames zu tweenen, verfügt über einen modifizierten Editor für Tweens und beschenkt den Entwickler, wie erwähnt, mit inverser Kinematik und 3D. Dessen ungeachtet bleibt noch Raum für viele weitere Features, die zukünftige Versionen von Flash sicher bringen werden. Interessanterweise hat der deutsche Markt nach Angaben des Herstellers so an Bedeutung zugenommen, dass die lokalisierte Ver-
7
sion von Flash diesmal früher erschienen ist als sonst üblich. Im Rückblick zeigt sich eine permanente, durchaus viele Wünsche der Flash-Entwickler berücksichtigende Weiterentwicklung von AS, das sich im Laufe der Jahre zu einer mächtigen Skriptsprache mauserte. Sie steht zur Zeit noch konkurrenzlos da – auch wenn sich am Horizont in Form von Microsofts Silverlight sowie HTML5 dunkle Wolken andeuten. Der enorme Erfolg von AS birgt gleichzeitig ein Risiko in sich, das in keiner Version so deutlich zum Vorschein trat wie bei Flash 9 bzw. AS 3.0. Denn diese Sprachversion ist derart umfangreich geworden, dass sich ein Einsteiger mit einer fast ähnlich steilen Lernkurve herumschlagen muss wie bei einer höheren Programmiersprache. Insofern steht Flash an einem Scheideweg: Sollen zukünftige Versionen die Funktionalität der Scriptsprache wie bisher weiter ausbauen? Wie wäre es beispielsweise mit einer echten 3D-Unterstützung, wie sie bereits seit Jahren der langsam in die Vergessenheit sinkende, einst große Bruder von Flash, nämlich Director, beherrscht? Das häufig gehörte Argument, damit wachse der Player notwendigerweise auf eine Größe von mehreren MB an, wird immer unbedeutender angesichts der Tatsache, dass DSL mittlerweile als Standard gelten darf. Das würde natürlich alle professionellen Entwickler und Programmierer freuen, geschähe aber zum Leidwesen der Designer und Gelegenheitsprogrammierer. Oder soll man stärker die Designer berücksichtigen, die nach Jahren erst wieder mit den Ebeneneffekten in Flash 8 und der IK sowie einem überarbeiten Motion-Editor in Flash 10 ein Zückerchen geboten bekamen, während sich die meisten anderen neuen Features eher auf die Programmierung bezogen? So mancher Designer würde sich eine bessere Unterstützung bei Animationen wünschen. Schön wären beispielsweise verschiedene Kamera-Perspektiven, wie sie etwa in ToonBoom-Studio schon lange integriert sind. Sie würden die Erstellung von Animationen nicht unwesentlich erleichtern. Unabhängig davon, wie zukünftige Flash-Versionen aussehen, sind heutige „Flasher“ zunehmend gezwungen, mit ActionScript umzugehen. Denn eben weil es nicht mehr das simple Animationstool von einst ist, sondern eine mächtige Entwicklungsumgebung, deren Potential aber erst wirklich ausgeschöpft wird, wenn man sich der Scriptsprache bedient, werden selbst Designer mit Programmierung konfrontiert. Moderne
8
Webseiten glänzen nicht alleine durch attraktives Design, sondern bieten ein hohes Maß an Funktionalität, das sich nur noch über Programmierung herstellen lässt. Das gilt erst recht für Applikationen, also vollständige Anwendungen, die, glaubt man den Auguren der Branche, dereinst Desktop-Applikationen weitgehend ersetzen werden (auch wenn das noch eher Zukunftsmusik darstellt). Aus dem Flash-Designer wird ein Flash-Entwickler, für den solide Grundkenntnisse in ActionScript unverzichtbar sind. Damit stellt sich die Frage nach der AS-Version, mit der man arbeiten soll. Für den gestandenen Programmierer kann die Antwort nur lauten: AS 3.0. Denn so lassen sich endlich Konzepte umsetzen, die aus der professionellen Softwareentwicklung bekannt und jahrelang erfolgreich eingesetzt wurden. Gerade größere, komplexere Projekte profitieren von den Möglichkeiten dieser Version. Die Mehrzahl der Flash-Entwickler gehört jedoch (immer noch) nicht zu dieser Kategorie, sondern stammt zumeist aus dem Bereich des Designs und der Web-Entwicklung. Ihnen fehlen daher oft die formalen Voraussetzungen, um problemlos mit einer derart komplexen, an eine Hochsprache angelehnten Scriptsprache zu arbeiten. Lohnt sich der Einstieg in AS 3.0? In den meisten Fällen lautet die Antwort derzeit wohl: Nein. Das mag überraschen, zumal in einem Buch, das sich mit Programmierung befasst. Doch sollte man mit beiden Füssen auf dem Boden der Realität bleiben:
• Für
einen Nicht-Programmierer steigt die Lernkurve bei AS 3.0 so stark an, dass man sich genauso gut direkt auf eine Hochsprache wie Java stürzen kann. Wie viel Web-Entwickler, prozentual gesehen, brauchen Java wirklich? Für ein „normales“ Web-Projekt lohnt sich der Aufwand nicht, eine Hochsprache zu erlernen, und AS 3.0 liegt, wie gesagt, aufgrund seiner Komplexität recht nahe an einer derartigen Hochsprache. • Der Erstellungsaufwand für eine Flash-Anwendung erhöht sich zunächst, da AS 3.0 einen recht konsequenten objektorientierten Ansatz verfolgt. So verlangt nun das früher so einfache Zuweisen eines Ereignisses, abgehandelt in einer einzigen Zeile, umständlich nach einem Listener sowie gegebenenfalls dem Import von Klassen. • Für AS 3.0 reicht es nicht aus, einfach nur neue Befehle zu erlernen. Vielmehr steckt eine konsequente Objektorientierung dahinter, die dazu zwingt, vor
Kapitel 2 ActionScript: Geschichte, Versionen, Ausblick
einem Projekt eine saubere objektorientierte Analyse zu erstellen. Sonst kann es schnell geschehen, dass man viele verschiedene Klassen anlegt, die mehrfach dasselbe tun – als Ergebnis erhält man mindestens eben so gefährlichen Codesalat, wie er gerne von gestandenen Programmierern den AS1Skriptern vorgeworfen wird. Eine saubere Klassenstruktur erhält man nicht en passant, sondern bedeutet einen enormen Lernaufwand. Aus jahrelanger Dozententätigkeit weiß der Autor, dass gerade dieser Punkt vielen Webentwicklern enormes Kopfzerbrechen bereiten kann und dass viele sich gar nicht die Zeit nehmen (können), um hier richtig einzusteigen. • Flash hatte den enormen Vorteil, dem Designer die Möglichkeit zu bieten, relativ einfach Interaktionen und komplexere Anwendungen zu programmieren. Es konnte zumindest prinzipiell alles aus einer Hand entstehen. Wagner spricht zutreffend von der Ein-Mann-Show (Mobile Games mit Flash, p. 8). Das ist zweifelsohne eine der Gründe für die enorme Popularität von Flash als Autorensystem. Mit AS3 ändert sich das grundlegend: die Entwickler werden zu einem Spezialistentum gezwungen, bei dem eine deutliche Trennung zwischen Coder (Programmierer im eigentlichen Wortsinne) und Designer entsteht. AS3 ist schlicht zu komplex, um auf die Schnelle eine Applikation zu skripten und dabei wirklich zu wissen, was man tut. OOP ist eben nichts, was man mal en passent erledigt. Den Informatiker freut’s, den Designer graust’s. Schon aus rein pragmatischen Gründen wird sich die Mehrzahl der Flash-Entwickler daher sinnvollerweise eher mit AS 2.0 anstatt 3.0 befassen müssen (wollen, sollen). Und dem trägt auch dieses Buch Rechnung, indem es sich weitgehend auf AS 2.0 beschränkt. Bereits Version 2.0 bietet so viele Möglichkeiten, dass sie i. d. R. gar nicht ausgeschöpft werden. Auch vor diesem Hintergrund macht es wenig Sinn, sich eine neue, noch mehr (genau so wenig ausgereizte) Features bietende Version anzueignen. Es ist schon erstaunlich, wie groß beispielsweise die Kluft zwischen den Möglichkeiten, welche die in Flash 8 eingeführte BitmapData-Klasse bietet, und deren konkrete Verwendung ist. Anstelle der ColorTransform-Klasse findet nach wie vor die veraltete Color-Klasse Verwendung. Und die Rechnung mit einer Matrix, die uns in AS 2.0 an mehreren Stellen begegnet und Potential vor allem bei der Programmierung grafischer Effekte
besitzt, wird so wenig eingesetzt, weil sie für viele Flash-Entwickler eher ein Mysterium Tremendum als ein sinnvolles Hilfsmittel darstellt. Das Problem der richtigen Sprachversion hat wie kaum ein anderes die Gemüter der Flash-Entwickler erhitzt, und so manches Flash-Urgestein wie etwa Philipp Kerman entscheidet sich bei kleineren bis mittleren Projekten zugunsten von 2.0 anstatt von 3.0. Zudem wartet die neue Sprachversion, falls man nicht mit CS 4, sondern dem Vorgänger arbeitet, mit einigen Ungereimtheiten auf, die das Programmieren partiell erheblich erschweren. Dazu gehört insbesondere das gegenüber bisher ausgesprochen unglücklich gehandhabte Entfernen extern geladener swf-Dateien sowie eine deutlich optimierfähige Garbage Collection. Kein Geringerer als Colin Moock hat sich ausführlich mit derartigen Problemen beschäftigt, und einen Einblick in die Diskussion findet der geneigte Leser unter: C. Moock: „The Charges Against ActionScript 3.0“, www.insideria.com/2008/07/the-charges-against-actionscri.html.
9
Brendan Hall und Joshua Davis, beides profilierte Flash-Entwickler, die schon frühzeitig das Potential von Flash sowohl in programmiertechnischer wie designerischer Hinsicht ausreizten, wobei insbesondere Davis beide Bereiche zu verknüpfen suchte, urteilen über den heutigen Stand: „Flash has matured incredibly in the past decade, but it has done so in a way that has blocked non-developers from even getting started. The simple fact of the matter is that with older versions of Flash you could learn the tool and how to program ActionScript almost entirely through creative play. With the latest iterations, unless you have a background in object-oriented programming, that method of learning by doing is simply not an option.“ Als Konsequenz aus dieser Malaise entwickelten sie unter dem Stichwort „Hype“ ein Framework, das jedem Interessierten den schnellen und einfachen Zugriff auf die Programmierfähigkeiten von Flash erlaubt, ohne sich in eine komplex gewordene Programmiersprache einarbeiten zu müssen.
Programmentwicklung und Programmiersprachen
Programmieren ist eine Wissenschaft für sich – das mag banal klingen, aber gerade der Neuling, der sich naturgemäß kopfüber ins Programmieren stürzt, wird sehr schnell an Grenzen stoßen. Einerseits hat sich ActionScript zu einer recht komplexen, mächtigen Sprache entwickelt, andererseits erfordern heutige FlashProjekte, die rasch auf viele Hundert oder Tausend Zeilen Quellcode anwachsen, sorgfältige Planung, will man nicht in einem Wust aus Spaghetti-Code und geskriptetem Nonsens untergehen. Angemessene Programmentwicklung setzt i. d. R. strukturiertes, überlegtes Vorgehen und systematische Planung voraus, um einerseits möglichst effizient und andererseits möglichst fehlerfrei arbeiten zu können. Insofern gleicht das Programmieren einer Anwendung dem Bau eines Hauses: Bevor man die Baugrube ausheben kann, muss man sich erst einmal einen Konstruktionsplan zurecht legen, an dem sich die einzelnen Bauphasen orientieren können.
3.1 Ansätze (Top-Down, Bottom-Up) Es gibt keinen Königsweg zum guten Programm, aber im Laufe der Zeit haben sich einige Vorgehensweisen herausgebildet, um einem die Arbeit zumindest zu erleichtern. Zu den erfolgversprechendsten Methoden gehören der Top-Down- und der Bottom-Up-Ansatz. Der Top-Down-Ansatz versucht, unabhängig von konkreten Details einen Gesamtüberblick über ein Problem zu erhalten. Im nächsten Schritt wird es in möglichst wenige, z. B. drei oder vier, Teilprobleme zerlegt. Auch auf dieser Stufe geht es noch nicht vornehmlich um einzelne Details. In einem weiteren Schritt wird jedes Teilproblem seinerseits in einige
3
wenige Teile zerlegt. Dies führt man solange durch, bis eine weitere Unterteilung nicht mehr möglich erscheint. Dann befindet man sich auf der untersten Stufe und damit zugleich bei kleinsten Details. Dieser Ansatz beschreibt ein Problem in einer Art Pyramide und arbeitet sich von allgemeinen Fragestellungen vor bis hin zur kleinsten Einzelheit. Insofern stellt dieser Ansatz eine große Kiste zur Verfügung, in die wir alle Elemente hineinpacken, die ein Problem oder eine Aufgabenstellung charakterisieren. Ein zeitlicher Ablauf oder eine logische Verknüpfung zwischen den einzelnen Elementen wird nicht definiert. Die Programmierung kann erst einsetzen, nachdem die Analyse vollständig abgeschlossen wurde. Der Vorteil besteht darin, dass man zu jedem Zeitpunkt einen vollständigen Überblick besitzt, ohne von Detailfragen abgelenkt zu werden. Dem stehen allerdings einige Nachteile gegenüber wie insbesondere die zumeist geringe Flexibilität. Denn wenn sich im Laufe der Entwicklung die Problemstellung aus welchem Grund auch immer ändert, ist man gezwungen, die Top-Down-Analyse erneut durchzuführen, da sich die Beziehung der Elemente untereinander, u. U. auch die Elemente selbst nun geändert haben. Das Gesamtgefüge der ursprünglichen Analyse stimmt nicht mehr mit der aktuellen Situation überein. Der Bottom-Up-Ansatz dreht den analytischen Prozess um und beginnt am Boden der Pyramide. Er greift sich ein Element (z. B. Objekt, Klasse, Methode etc.) heraus und analysiert alle notwendigen Details möglichst unabhängig von der Frage, wie sich dieses eine Element in den Gesamtzusammenhang fügt. Dann folgt die Analyse des nächsten Elements. Dies geschieht solange, bis sich aus den einzelnen Elementen der Gesamtzusammenhang erschließen lässt. Ist ein Element ausreichend definiert, kann man zu-
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
11
12
Kapitel 3 Programmentwicklung und Programmiersprachen
mindest prinzipiell schon mit der Programmierung beginnen, obwohl die gesamte Analyse noch nicht abgeschlossen ist. Zu den unbestreitbaren Vorteilen zählt die größere Flexibilität. Weil zunächst kein übergreifender Zusammenhang definiert wird, kann man auch zu einem späteren Zeitpunkt relativ problemlos weitere Elemente hinzufügen, falls es die Situation erforderlich macht. Zudem kann bereits zu einem frühen Zeitpunkt Programmcode geschrieben werden. Allerdings läuft man durch die im Anfangsstadium erfolgende Konzentration auf einzelne Details Gefahr, sich darin zu verlieren und nie den Gesamtüberblick zu erhalten, der aber notwendig ist, um alle Teile im Rahmen eines Programms zusammenfügen zu können. Zu frühes Programmieren birgt zudem das Risiko in sich, dass der aktuelle Code nicht optimal auf den später hinzukommenden Code abgestimmt ist. Nach der Problemanalyse, die zu einer Beschreibung des Programms führt, folgt die Entwicklung der Programmlogik. Während die über die erwähnten Ansätze realisierte Programmbeschreibung festlegt, was das Programm tun soll, beschreibt die Logik, wie es das tun soll. Anders als zuvor spielt nun die Reihenfolge einzelner Arbeitsschritte eine entscheidende Rolle.
3.2 Algorithmus als Problemlösung Aus der Problemanalyse muss sich in irgendeiner Form eine Lösung erarbeiten lassen. Sie wird als Algorithmus bezeichnet, worunter man eine endliche (begrenzte) Anzahl an Schritten zur Lösung eines exakt formulierten Problems versteht. Ein Algorithmus gibt also Handlungsanweisungen und legt so den Ablauf eines Prozesses fest. Formal gesehen verfügt eine derartige Lösung über bestimmte Eigenschaften, zu denen u. a. zählen:
• Ausführbarkeit.
Selbstverständlich müssen die Handlungsanweisungen ausführbar sein, also z. B. von einer Maschine befolgt werden können. Das ist dann der Fall, wenn sie möglichst kleinschrittig sind und in einer spezifischen, für die Maschine verständlichen Form wie z. B. einer Programmiersprache dargeboten werden. Dazu gehört auch das eindeutige Vorhandensein aller Informationen, um
den entsprechenden Lösungsprozess ausführen zu können. • Endlichkeit. Die Lösung muss über eine feste Länge an Schritten verfügen. Ein Prozess, den wir mit unendlich vielen Schritten beschreiben müssten, ließe sich nie als Algorithmus umsetzen. • Terminierung. Der Lösungsprozess muss über eine Endebedingung verfügen, d. h. es muss festgelegt sein, wann die Ausführung zu Ende ist. Dieser Punkt bezieht sich auf die Ausführung, während der vorhergehende Punkt eine Aussage zur Beschreibung trifft. • Determinismus. Bei jedem beliebigen Schritt des Algorithmus muss eindeutig feststellbar sein, an welcher Stelle man sich gerade befindet. Zusätzlich muss bei gleichen Ausgangsbedingungen bzw. Eingaben auch immer das gleiche Ergebnis erzielt werden. Das klingt alles recht abstrakt, beschreibt aber in allgemeiner Form jede Art von Handlungsanleitungen, egal ob ein Kochrezept, eine Wegbeschreibung oder eine Bedienungsanleitung für ein neues Handy. Ja, auch für ein Handy benötigen wir mittlerweile eine Anleitung. Früher konnte man damit einfach telefonieren, doch das moderne Handy ist längst zum multifunktionalen Äquivalent des Schweizer Taschenmessers im Kommunikationsbereich mutiert und widersetzt sich zunehmend einer einfachen Bedienbarkeit nach der klassischen Trial & Error-Methode. Die Problemstellung „Handy bedienen“ resultiert daher in einem umfangreichen Algorithmus „Bedienungsanleitung“, der recht komplex ausfallen kann. Diese Bedienungsanleitung muss in einer verständlichen Sprache abgefasst sein, damit die Einzelschritte befolgt werden können (Ausführbarkeit). Was so selbstverständlich klingt, gelingt leider nicht in jedem Fall, weder in der Programmierung noch bei Bedienungsanleitungen, oder ist Ihnen klar, wie folgende Anweisung zu deuten ist: „Wenn das Wetter kalt ist, wird die Puff Unterlage sich langsam puffen. Entrollen die Puff Unterlage und liegen auf ihr, dann wird sie von der Wärme sich Inflationen bekommen“ (Jürgen Hahn: Jetzt zieh den Zipfel durch die Masche; Zürich 1994)? Verständlicherweise sollten wir bei einem Handy mit zwei, drei Schritten, jedenfalls mit einer fest definierten und möglichst geringen Anzahl, in die Lage versetzt werden, einen Gesprächspartner anzurufen
3.3 Hilfsmittel zur Entwicklung von Algorithmen
(Endlichkeit). Selbst ein Schweizer Taschenhandy, mit dem wir gleichzeitig im Internet surfen, unsere Umgebung filmen und dem Kühlschrank zu Hause die Anweisung geben, fehlende Lebensmittel automatisch per E-Mail zu ordern, erlaubt es, zu telefonieren. Das Ergebnis der Arbeitsschritte ist entweder ein Telefongespräch oder eine Mitteilung über die (momentane) Unerreichbarkeit des gewünschten Gesprächspartners (Terminierung). In jedem Fall wird klar, wann der Prozess des Telefonierens endet. Unabhängig davon, wann man sein Handy zückt und versucht, zu telefonieren, ist das Ergebnis immer dasselbe, nämlich so, wie im vorhergehenden Abschnitt angesprochen (Determinismus). Der Prozessablauf ist exakt festgelegt, bei jedem Einzelschritt ist dem Anwender erkennbar, wo er sich gerade befindet. Und das Ergebnis des Prozesses ist vorhersagbar. Wir müssen nicht damit rechnen, dass unser Handy bei Betätigen das Haus des Nachbarn sprengt oder unser Auto startet (nun ja, Letzteres ist sicher nur noch eine Frage der Zeit). Prinzipiell funktioniert ein Algorithmus für ein Computerprogramm nicht anders. Wenn es uns dennoch leichter fällt, Handlungsanleitungen bezüglich des Telefonierens zu verstehen als Programmcode zu lesen, so liegt das lediglich an der Form, in der uns die Anweisungen für den Computer begegnen. Sie liegen in einer jeweils eigenen Sprache vor, die über eine spezifische Syntax (Struktur, Grammatik) und Semantik (Bedeutung) verfügt. Genau das aber gilt auch für die erwähnte Handlungsanleitung, die nur deshalb von uns so einfach verstanden werden kann, weil uns die Syntax und Semantik der verwendeten Sprache vertraut sind. Würde die Anleitung beispielsweise in Suaheli abgefasst, sähe sie genau so unverständlich aus wie Programmcode. In einer grundlegenden Hinsicht unterscheiden sich allerdings Handlungsanleitung und Programmcode voneinander: Menschen sind in der Lage, mit unvollständigen Informationen umzugehen, Computer dagegen nicht (an dieser Stelle wollen wir den spannenden Bereich der Fuzzy Logic unbeachtet lassen). Wenn jemand die Anweisung erhält, für einen Besucher eine Tasse zu holen, damit dieser sich Kaffee hinein schütten kann, dürfen wir in den meisten Fällen davon ausgehen, eine saubere Tasse zu erhalten. Ist sie es nicht, wird sie gespült, oder es erfolgt solange eine Suche, bis eine saubere Tasse aufgetrieben werden konnte. In einem Computerprogramm dagegen müsste die Tasse
13
spezifiziert werden: „Sauber“ könnte man als Eigenschaft der Tasse bezeichnen, die true, also wahr sein müsste, bevor sie verwendet werden dürfte. Andernfalls wäre das Programm nicht in der Lage, zu entscheiden, welche Art von Tasse gewünscht wird. So wichtig dieser Punkt ist, so häufig machen gerade Anfänger den Fehler, Bedingungen vorauszusetzen, die einem Programm eigentlich explizit mitgeteilt werden müssten. Damit schleichen sich Fehler ein, die ausgesprochen schwer zu bereinigen sind, weil sie zwar der Logik, nicht jedoch der Syntax der verwendeten Programmiersprache widersprechen. Syntaxfehler lassen sich durch Tools i. d. R. recht einfach automatisiert finden, Logikfehler dagegen nicht. An dieser Stelle künden sich schon lange Nächte an.
3.3 Hilfsmittel zur Entwicklung von Algorithmen Gerade am Anfang stellt sich die Frage, wie man denn für ein Problem eine angemessene Lösung entwickeln kann, bevor man den betreffenden Programmcode schreibt. Es existieren mehrere Vorgehensweisen, die mit wechselnden Abstraktionsleveln und partiell mit visuellen Hilfsmitteln arbeiten. Dazu gehören:
• Flowchart. Der Ablauf eines Programms wird mit
genormten Symbolen visualisiert. Wer beispielsweise aus dem Design-Bereich zur Programmierung findet, kennt eine ähnliche Vorgehensweise. Dort dienen Flowcharts dazu, um Strukturen wie etwa den hierarchischen Aufbau eine Site zu erfassen. In der Programmierung bildet ein Flowchart dagegen einen Prozess ab. • Natürliche Sprache. In dieser Form haben wir im vorhergehenden Absatz bereits das Telefonieren angesprochen. Gemeint ist, dass man eine Problemlösung in beredter Prosa formuliert, ohne sich an eine allzu strenge Form zu halten. Der Vorteil besteht in der Möglichkeit, schnell eine Handlungsanleitung zu erhalten, da die verwendete Sprache unserer Alltagssprache entspricht. • Pseudo-Code. Wer bereits über Programmierkenntnisse verfügt, kann mit einem Zwitter zwischen beredter Prosa und halbwegs formaler Syntax eine Problemlösung erarbeiten und beschreiben. Solcher Code soll möglichst unabhängig von der
14
Kapitel 3 Programmentwicklung und Programmiersprachen
Syntax einer konkreten Programmiersprache formuliert werden. Wir wollen uns die Verwendung von Flowcharts und Pseudo-Code etwas genauer anschauen. Dabei konzentrieren wir uns auf die klassische funktionale Programmierung, objektorientierte Programmierung dagegen wird nicht weiter berücksichtigt. Wir wählen diese Vorgehensweise, um die Lernkurve nicht allzu steil ansteigen zu lassen.
3.3.1 Flowcharts Sucht man eine Vorgehensweise, die einerseits ohne allzu intensive Programmierkenntnisse funktioniert und andererseits trotzdem die gewünschte Programmfunktionalität verständlich abbildet, so empfiehlt sich ein Flowchart (Flussdiagramm). Es existieren zahlreiche spezialisierte Programme, die genormte Symbole zur Erstellung anbieten. Leistungsfähig ist beispielsweise SmartDraw, das sich neben leichter Bedienbarkeit durch eine ausgesprochen große Symbolbibliothek auszeichnet. Natürlich existieren wie immer im Freeware- und Open Source-Bereich ebenfalls Programme, die durchaus Vergleichbares leisten. Notfalls kann man sich auch mit Flash (bzw. einem beliebigen Vektor-Grafikprogramm) begnügen, denn
die wichtigsten Symbole lassen sich schnell als MovieClip oder Grafik-Objekt erstellen. Um die Entwicklung verständlicher Flowcharts zu ermöglichen, legt die Norm DIN 66001 fest, welche Art von Symbolen verwendet werden sollte. In den meisten Fällen reicht es aus, wenn Sie mit den in den Abbildungen 1 und 2 gezeigten Symbolen arbeiten. Ein Diagramm könnte beispielhaft so aussehen, wie in Abbildung 2 gezeigt. Diese Abbildung visualisiert in vereinfachter Form den eigentlich erstaunlich komplexen Vorgang des allmorgendlichen Aufbruchs zur Arbeitsstelle. Sobald wir über die Türschwelle treten, beginnen die Probleme, dargestellt an nur einem exemplarisch behandelten Beispiel. So müssen wir uns die Frage stellen, ob es regnet oder ob im Laufe des Tages Regen zu erwarten ist. Falls ja, nehmen wir einen Regenschirm und marschieren anschließend zur Bushaltestelle. Falls nein, gehen wir zu Fuß zum Ziel. In beiden Fällen endet der Prozess zumeist mit der Ankunft an der Arbeitsstelle. Flussdiagramme sollten bestimmten Regeln folgen, die zwar nicht zwingend vorgeschrieben sind, deren Einhaltung aber das Lesen derartiger Grafiken erleichtert: 1. Der Programmablauf (zumindest die allgemeine Logik) erfolgt von oben nach unten und von links nach rechts. Im Einzelfall mag es notwendig sein,
Abbildung 1: Standardsymbole zur Entwicklung eines Flowcharts
3.3 Hilfsmittel zur Entwicklung von Algorithmen
15
mal einen Schritt von rechts nach links zu visualisieren, aber das sollte möglichst eine Ausnahme bleiben. Andernfalls fällt es schwer, den Ablauf korrekt zu erfassen. Die Ablaufrichtung entspricht damit unserer gewohnten Leserichtung. 2. Es sollten nur Standardsymbole verwendet werden. Natürlich existieren mehr als die oben vorgestellten Symbole, aber schon mit diesen 8 können Sie sehr komplexe Programmabläufe visualisieren. Weitere Symbole lassen sich z. B. für SmartDraw auf der Webseite des Herstellers herunterladen (oder einsehen, falls Sie nur mit Flash arbeiten). Widerstehen Sie auf jeden Fall der Versuchung, eigene Symbole zu kreieren – denn diese können nur Sie selbst verstehen. 3. Jedes Symbol hat entweder nur einen Eingang, nur einen Ausgang, nur einen Ein- und einen Ausgang oder einen Eingang und zwei Ausgänge. 4. Ein Entscheidungssymbol enthält eine Frage, die nur mit Ja/Nein beantwortet werden kann. Für jemanden, der es nicht gewohnt ist, mit Flussdiagrammen zu arbeiten, stellt dies sicher die größte Herausforderung dar. Flussdiagramme bieten den unschätzbaren Vorteil, dass sie einen zwingen, die gesamte Programmlogik in sehr kleine Schritte zu zerlegen. Mag einem vorher die Logik schon im Großen und Ganzen klar gewesen sein, so ist es dennoch nicht selten, dass man beim Programmieren feststellen muss, irgend etwas übersehen oder nicht tief genug durchdacht zu haben. Vor solchen Stolpersteinen schützt ein sauber ausgearbeitetes Flussdiagramm. Ist diese Arbeit geleistet, fällt das eigentliche Programmieren insgesamt leichter, da einem nun klar ist, was genau wann geschieht. Als nachteilig erweist sich jedoch der relativ hohe Zeitaufwand, der mit dem Erstellen von Flowcharts verknüpft ist, und die eher geringe Flexibilität, da nachträgliche Änderungen im Programmablauf dazu zwingen können, weite Strecken des Diagramms neu erstellen zu müssen.
3.3.2 Pseudo-Code Eine in Pseudo-Code (auch: Schein-Code) formulierte Lösung orientiert sich strukturell allgemein an Programmiersprachen, formuliert aber die einzelnen
Abbildung 2: Beispielhaftes logik)
Flussdiagramm
(Programm
Arbeitsschritte in einer relativ nahe an der Alltagssprache liegenden Form. Derartiger Code bedient sich bestimmter festgelegter Schlüsselwörter, um die Funktionsweise eines Algorithmus’ oder, allgemeiner, den Ablauf eines Programms sprachunabhängig abzubilden. Sprachunabhängig bedeutet natürlich, dass sich dieser Code nicht an eine spezifische Programmiersprache anlehnt (andernfalls würde man ja schon das konkrete Programm schreiben). Eine einfache if-else-Abfrage würde in Pseudocode beispielsweise folgendermaßen geschrieben: wenn (Bedingung) Anweisung(en) sonst
Anweisung(en)
16
Das ActionScript -Äquivalent würde so aussehen: if(Bedingung){
Kapitel 3 Programmentwicklung und Programmiersprachen
3.4 Beispiel-Algorithmus „Kaffee kochen“
Anweisung(en) }else{
Anweisung(en) }
Da Pseudocode über das Einrücken und über einige Schlüsselbegriffe Programmiersprachen näherungsweise abbildet, ist es zumindest prinzipiell relativ einfach, daraus den benötigten korrekten Code zu erzeugen. Stärker als Flussdiagramme setzt Pseudocode jedoch eine gewisse Erfahrung im Programmieren voraus, um sinnvoll Logiken abbilden zu können. Das obige Beispiel könnte man etwa so formulieren: function zurArbeitGehen(){ gehe zur Tür hinaus
wenn(Wetter sieht regnerisch aus){ nehme Schirm;
gehe zur Bushaltestelle;
Der Volksmund weiß, dass Programmierer dickbäuchig, männlich und exzessive Kaffeetrinker sind. Lediglich das letztgenannte Vorurteil stimmt, weswegen wir uns am konkreten Beispiel des Kaffeekochens anschauen wollen, wie sich ein komplexeres Programm in Pseudo-Code entwickeln lässt. Wir wählen eine Top-Down-Vorgehensweise.
• Aufgabenstellung/Problemstellung:
Einen leckeren Kaffee kochen und ihn Gästen servieren. • Annahme: Der Kaffee wird von einer Maschine gekocht, die exakte Anweisungen von uns benötigt. Wasser und Strom sowie Tassen und Kanne sind vorhanden, die durstigen Gäste natürlich auch. Außerdem verfügen wir über notwendiges Kleingeld, um gegebenenfalls in einem gut sortierten Laden um die Ecke Besorgungen durchzuführen.
}sonst{
In der allgemeinsten Form bzw. auf dem höchsten Abstraktionslevel besteht der fragliche Prozess aus zwei Teilen:
}
Kaffee kochen
}
Fertigen Kaffee servieren
Die einzelnen Schritte bleiben gegenüber dem Flowchart notwendigerweise gleich. In beiden Fällen kontrollieren wir beispielsweise das Wetter. Nur sieht die Struktur, in der die Schritte wieder gegeben werden, durchaus ähnlich aus, wie sie auch in einem konkreten Programm zu finden wäre:
Das reicht allerdings bei weitem nicht aus, um den betreffenden Prozess zielgerichtet in Gang zu setzen. Es fehlen nämlich konkrete Einzelschritte bzw. Handlungsanweisungen. Jeder dieser Teile umfasst eine Reihe von Schritten, die in manchen Fällen von spezifischen Bedingungen abhängen. Betrachten wir den ersten der genannten Teile, lässt er sich aufsplitten in die Phasen:
fahre zum Ziel; gehe zum Ziel;
• Anweisungen werden in der Reihenfolge der Aus-
führungen untereinander geschrieben. Ein horizontales Abweichen wie beim Flowchart (Ja, Nein) existiert hier nicht. • Logisch aufeinander bezogene Anweisungen werden in einem eigenen Block, einer Funktion, erfasst. • Bedingungen werden ebenfalls in einem eigenen Block, der if-Bedingung, erfasst. In einem „richtigen“ Code müsste abschließend noch der Aufruf der Funktion zurArbeitGehen() erfolgen, aber auch so ist der Ablauf unserer Lösung zu erkennen und nachzuvollziehen.
vorbereiten aufbrühen
Ähnlich können wir den zweiten Teil untergliedern in: vorbereiten servieren
In beiden Fällen gehen dem Ergebnis der Teilprozesse – fertiger Kaffee, „vollendeter Genuß“ TM – vorbereitende Schritte voraus. In Bezug auf das Aufbrühen handelt es sich um:
3.4 Beispiel-Algorithmus „Kaffee kochen“
17
Kanne mit Wasser füllen
vorbereiten
Kanne in Maschine stellen
Zucker nehmen
Wasser in Maschine füllen Filter in Maschine einsetzen Pulver in Filter füllen einschalten
Selbst an dieser Stelle haben wir noch nicht die kleinstmögliche Schrittfolge erreicht. Weitere Unterteilungen sind möglich. So besteht der erste Punkt, das Füllen der Kanne, aus folgenden Einzelhandlungen: Kanne fassen
Zum Wasserhahn gehen
Unter Wasserhahn halten Wasser fließen lassen Wasser stoppen
Kanne zur Maschine bringen
Dieses Verfeinern wird solange durchgeführt, bis man ganz kleinschrittige Anweisungen findet, die sich nicht mehr unterteilen lassen. Für unsere Zwecke reicht der erzielte Detailgrad aus. Zu Übungszwecken können Sie jedoch gerne alle weiteren Schritte soweit ausarbeiten, bis sie sich nicht mehr in kleinere Häppchen unterteilen lassen. Auf dieser Basis lässt sich ein Algorithmus angemessen formulieren. Er sieht zum aktuellen Zeitpunkt folgendermaßen aus: Kaffee kochen vorbereiten
Kanne mit Wasser füllen Kanne fassen
Zum Wasserhahn gehen
Unter Wasserhahn halten Wasser fließen lassen Wasser stoppen
Kanne zur Maschine bringen Wasser in Maschine füllen Kanne in Maschine stellen
Filter in Maschine einsetzen Pulver in Filter füllen einschalten aufbrühen
Fertigen Kaffee servieren
Tassen nehmen Milch nehmen servieren
Kaffee in Tasse füllen Zucker geben Milch geben
Wir könnten so fortfahren, bis wir eine zwar endliche, aber aufgrund der vielen Schritte endlos lang erscheinende Liste mit Instruktionen erhalten würden. Wirklich lesbar und damit verständlich ist eine solche Form allerdings nicht. Daher fügen wir eine formale Gliederung ein, die durch eingerückte Zeilen eine hierarchische Abhängigkeit anzeigt: Kaffee kochen
vorbereiten
Kanne mit Wasser füllen Kanne fassen
Zum Wasserhahn gehen
Unter Wasserhahn halten Wasser fließen lassen Wasser stoppen
Kanne zur Maschine bringen Wasser in Maschine füllen Kanne in Maschine stellen
Filter in Maschine einsetzen Pulver in Filter füllen einschalten aufbrühen
Fertigen Kaffee servieren vorbereiten
Tassen nehmen Zucker nehmen Milch nehmen servieren
Kaffee in Tasse füllen Zucker geben Milch geben
Wir können nun auf einen Blick erkennen, dass die Schritte vorbereiten bis aufbrühen im ersten Teil zum Kochen des Kaffees gehören. Solche Anweisungs-
18
Blöcke lassen sich in Programmiersprachen in sogenannten Funktionen erfassen. Dabei ist eine bestimmte Schreibweise bei der Wahl der Funktionsnamen zu berücksichtigen, die wir hier geflissentlich weitgehend ignorieren wollen, da es zunächst nur um das Prinzip geht. Wir kommen darauf in den nachfolgenden Kapiteln zu Variablen und Funktionen in ActionScript jedoch ausführlicher zu sprechen. Ändern Sie den Pseudo-Code so, wie es die fettgedruckten Zeilen anzeigen (aus Gründen der Übersichtlichkeit wollen wir ab dieser Stelle derartige Änderungen fett auszeichnen): function kaffeeKochen(){ vorbereiten
Kanne mit Wasser füllen Kanne fassen
Zum Wasserhahn gehen
Unter Wasserhahn halten Wasser fließen lassen Wasser stoppen
Kanne zur Maschine bringen Wasser in Maschine füllen Kanne in Maschine stellen
Filter in Maschine einsetzen Pulver in Filter füllen einschalten aufbrühen }
function fertigenKaffeeServieren(){ vorbereiten
Tassen nehmen Zucker nehmen Milch nehmen servieren
Kaffee in Tasse füllen Zucker geben Milch geben }
kaffeeKochen();
fertigenKaffeeServieren();
Formal gesehen umfasst unser Code nun zwei Funktionsdefinitionen, in denen teilweise ganz konkrete Anweisungen und Befehle enthalten sind, sowie danach
Kapitel 3 Programmentwicklung und Programmiersprachen
in den beiden letzten Zeilen folgend zwei Funktionsaufrufe. Diese Aufrufe sorgen dafür, dass die vorher definierten Anweisungen auch wirklich ausgeführt werden. Sie sind vergleichbar mit einer Aufforderung an eine andere Person, jetzt Kaffee aufzubrühen und zu servieren. Durch die Verwendung des Schlüsselworts function sowie der runden Klammern () machen wir deutlich, dass es sich hier um die Definition einer Befehlsfolge handelt. Mit den geschweiften Klammern {} kennzeichnen wir deren Anfang und Abschluss. Die Übersichtlichkeit erhöht sich weiter, wenn wir die sehr detaillierten Schritte, die zu einer übergeordneten Handlungsanweisung wie beispielsweise Kanne mit Wasser füllen gehören, ebenfalls in eine Funktion auslagern. Ändern Sie den Pseudo-Code: function kaffeeKochen(){ vorbereiten() aufbrühen }
function fertigenKaffeeServieren(){ vorbereiten
Tassen nehmen Zucker nehmen Milch nehmen servieren
Kaffee in Tasse füllen Zucker geben Milch geben }
function vorbereiten(){
Kanne mit Wasser füllen Kanne fassen
Zum Wasserhahn gehen
Unter Wasserhahn halten Wasser fließen lassen Wasser stoppen
Kanne zur Maschine bringen Wasser in Maschine füllen Kanne in Maschine stellen
Filter in Maschine einsetzen Pulver in Filter füllen einschalten
3.4 Beispiel-Algorithmus „Kaffee kochen“
19
}
function vorbereitenServieren(){
fertigenKaffeeServieren();
Zucker nehmen
kaffeeKochen();
Alle Schritte, die zum Vorgang vorbereiten gehören, haben wir in eine entsprechende Funktion verlagert. Jedes Mal, wenn wir Kaffee aufbrühen wollen, rufen wir diese Funktion auf, um alles Notwendige bereitzustellen. Schauen wir uns den bisherigen Code an, erkennen wir, dass es noch eine zweite Stelle gibt, an der die Anweisung vorbereiten zu finden ist, nämlich unmittelbar vor dem Servieren des fertigen Kaffees. In einem früheren Abschnitt wurde darauf verwiesen, dass wir mit zweideutigen Informationen i. d. R. umgehen können. Im aktuellen Beispiel würden Menschen aus dem jeweiligen Kontext darauf schließen, dass Vorbereiten einmal die Verwendung eines Filters, ein anderes mal die Suche nach sauberen Tassen umfasst. Eine Maschine bzw. ein Computer dagegen wäre zu einer derartigen Interpretation nicht in der Lage und würde statt dessen bei jeder Handlungsanweisung vorbereiten genau das tun, was in der gleichnamigen Funktionsdefinition bzw. Handlungsdefinition steht. Was für korrekten Programmcode gilt, sollte man sich daher auch für einen Pseudo-Code merken: Wenn möglich, sind verschiedene Handlungsanweisungen auch mit verschiedenen Begriffen zu formulieren, was es in formaler Hinsicht erleichtert, das spätere Skript fehlerfrei in der richtigen Syntax zu entwickeln. Wir könnten hier die verschiedenen Anweisungen des vorbereiten durch ein Suffix genauer kennzeichnen, z. B. vorbereitenKaffee und vorbereitenServieren. Dann sähe unser Code so aus: function kaffeeKochen(){ vorbereitenKaffee() aufbrühen }
function fertigenKaffeeServieren(){ vorbereitenServieren() servieren }
function vorbereitenKaffee(){ Kanne mit Wasser füllen … }
Tassen nehmen Milch nehmen } …
Die drei Punkte (…) deuten Auslassungen an, um nicht permanent den gesamten Code wiederholen zu müssen. Sie fügen an dieser Stelle gedanklich die bereits oben dargestellten Zeilen ein. Damit können wir nun eigentlich genüsslich Kaffee schlürfen. Allerdings bleiben in unserem Algorithmus noch einige Fragen offen. Ein typisches Problem stellen die Vorannahmen dar. Dort haben wir vorausgesetzt, dass Wasser, Kaffeepulver und Strom vorhanden sind. Doch was ist mit den für unseren Prozess ebenso unverzichtbaren Filtern? Wir verwenden sie einfach in unserem Programm, ohne zu prüfen, ob überhaupt welche vorhanden sind. Wir müssen daher unsere Funktion vorbereitenKaffee erweitern: function vorbereitenKaffee(){ …
Kanne in Maschine stellen
wenn(keine Filter vorhanden){ Filter kaufen gehen }
Filter in Maschine einsetzen Pulver in Filter füllen einschalten }
Auch an anderen Stellen greifen wir einfach auf Objekte zu, ohne uns zu vergewissern, ob sie vorhanden sind. Dasselbe gilt z. B. für Milch und Zucker. Dementsprechend sollte die Funktion vorbereitenServieren lauten: function vorbereitenServieren(){ wenn (Tassen unsauber){ dann spülen }
Tassen nehmen
wenn (kein Zucker){ Zucker kaufen
20
}
Zucker nehmen
wenn (keine Milch){
kaufen (bei den derzeitigen Preisen besser: Nachbarn fragen) }
Milch nehmen }
Damit erweitert sich unser Algorithmus um ein wichtiges Konzept, nämlich die Bedingung. Anweisungen wie das Kaufen von Zucker oder das Verwenden eines Filters werden von Voraussetzungen abhängig gemacht, die explizit im Programm als Bedingung formuliert werden. Es gibt noch weitere Stellen, an denen Bedingungen erfüllt sein müssen, ehe der nächste Arbeitsschritt ausgeführt werden kann. Dabei können diese Bedingungen beliebiger Art sein. Oft kommt es vor, dass zuerst ein bestimmter Arbeitsschritt beendet werden muss, bevor der nächste Schritt folgen kann. Das gilt in unserem Beispiel hinsichtlich des Servierens, denn selbstverständlich muss man erst einmal abwarten, bis der Kaffee schon fertig aufgebrüht ist, ehe man sich daran laben kann. Den aktuellen Status kann man an der Maschine ablesen, die uns beispielsweise durch ein Lämpchen mitteilt, wann der Kaffee fertig ist. Momentan ist es allerdings noch so, dass wir zunächst den Anweisungs-Block kaffeeKochen und dann unmittelbar danach den Block fertigenKaffeeServieren ausführen. Tatsächlich muss jedoch die zweite Anweisung von dem erfolgreichen Abschluss der ersten abhängig gemacht werden. Daher korrigieren wir unseren Code: function kaffeeKochen(){ vorbereitenKaffee() aufbrühen
wenn (Lämpchen brennt noch){ warten }sonst{
fertigenKaffeeServieren() } }
Zum Schluss nach allen Funktionsdefinitionen lautet der Aufruf nur noch: kaffeeKochen()
Kapitel 3 Programmentwicklung und Programmiersprachen
Nun bleibt noch der Vorgang des Servierens, der ebenfalls von Bedingungen abhängt und in eine Funktion ausgelagert werden kann. Ändern Sie daher den bestehenden Pseudo-Code in: function fertigenKaffeeServieren(){ vorbereitenServieren() servieren(); }
function servieren(){
Kaffee in Tasse füllen
wenn (Gast will Zucker){ Zucker geben }
wenn (Gast will Milch){ Milch geben } }
In beredter Prosa ergibt sich damit derzeit folgender Ablauf: Zuerst werden einzelne Arbeitsschritte, soweit sie von der Logik her zusammengehören, in Funktionen zusammengefasst. Wir verfügen jetzt über folgende Definitionen dieser Art:
• kaffeeKochen. Das ist die wichtigste Funktion,
die den gesamten Prozess in Gang setzt. Sie ruft in dieser Reihenfolge die Funktionen vorbereitenKaffee und aufbrühen auf. Anschließend kontrolliert sie das Lämpchen an der Maschine. Brennt es noch, müssen wir warten, da der Kaffee noch nicht fertig ist. Andernfalls wird die Funktion fertigenKaffeeServieren aufgerufen. • vorbereitenKaffee. Hier werden alle Schritte ausgeführt, die notwendig sind, bevor wir die Maschine einschalten können. Dazu gehört etwa das Einstecken eines Filters, das Einfüllen von Pulver etc. • aufbrühen. Hier haben wir nicht etwa gemogelt, schließlich fehlt doch eigentlich eine entsprechende Definition. Das Aufbrühen ist ein maschineninterner Vorgang, der ausgelöst wird, sobald wir den betreffenden Befehl geben. Es ist nicht notwendig, kleinschrittigere Anweisungen zu geben. Jede Script- und Programmiersprache verfügt über derartige vorgefertigte Funktionen (natürlich nicht zum Aufbrühen, sondern um anderweitige
3.4 Beispiel-Algorithmus „Kaffee kochen“
Aktionen auszuführen). Sie stellen die Grundbausteine dar, aus denen der Programmierer seine eigenen, komplexeren Funktionen aufbaut. • fertigenKaffeeServieren. Diese Funktion legt fest, was geschehen soll, nachdem der Kaffee fertig aufgebrüht wurde. Sie besteht aus den zwei Anweisungsblöcken vorbereitenServieren und servieren. • vorbereitenServieren. Wie bei aufbrühen bedarf servieren (s. u.) einiger vorbereitender Schritte, die hier definiert werden. • servieren. Diese Funktion umfasst alle Handgriffe, die ausgeführt werden müssen, bis die schon jetzt halb verdurstete Gästeschar endlich trinken darf. Abschließend geben wir den Befehl KaffeeKochen(), der die gesamte in den Funktionen beschriebene Kette an Aktionen auslöst. Bis zu diesem Zeitpunkt hat einer der Gäste den Raum unter Protest in unverständlichem Pseudo-Code verlassen, weil er immer noch nichts zu trinken bekommen hat. Jeder passionierte Kaffeetrinker weiß, dass die Menge des verwendeten Pulvers ebenso entscheidenden Einfluss auf den späteren Genuss ausübt wie die Menge der Milch und gegebenenfalls des Zuckers. Ein noch so geringes Abweichen von der Idealmenge vermag den Genießer in tiefste Depression zu stürzen. Um ein solches Problem zu umgehen, empfiehlt es sich, die Idealmenge irgendwo festzuhalten. In der Programmierung geschieht das mit Hilfe von Variablen. Ganz am Anfang des Codes fügen wir ein:
21
ter Variable festgehalten, in unserem konkreten Fall also 8 Löffel. Auf die gleiche Art und Weise müssten alle anderen Mengenangaben präzisiert werden, also z. B. Milch und Zucker. Für den Fall, dass Sie unterwegs verloren gegangen sind, hier der aktuelle Stand: mengeKaffee = 8 Löffel
function kaffeeKochen(){ vorbereitenKaffee() aufbrühen
wenn (Lämpchen brennt noch){ warten }sonst{
fertigenKaffeeServieren() } }
function vorbereitenKaffee (){ Kanne mit Wasser füllen Kanne fassen
Zum Wasserhahn gehen
Unter Wasserhahn halten Wasser fließen lassen Wasser stoppen
Kanne zur Maschine bringen Wasser in Maschine füllen Kanne in Maschine stellen
wenn (keine Filter vorhanden){ Filter kaufen gehen
mengeKaffee = 8 Löffel
}
Diese Information muss beim Einfüllen des Pulvers in den Filter berücksichtigt werden:
mengeKaffee Pulver in Filter füllen
function vorbereitenKaffee(){ …
Filter in Maschine einsetzen
mengeKaffee Pulver in Filter füllen einschalten }
Durch die Variable mengeKaffee haben wir exakt festgelegt, wie viele Löffel wir zu verwenden wünschen. Über die Funktion vorbereitenKaffee wird nun nicht mehr eine beliebige Menge Pulver in den Filter geschüttet, sondern genau so viel, wie in besag-
Filter in Maschine einsetzen einschalten }
function fertigenKaffeeServieren(){ vorbereitenServieren() servieren(); }
function servieren(){
Kaffee in Tasse füllen
wenn (Gast will Zucker){ Zucker geben
22
}
wenn (Gast will Milch){ Milch geben
Kapitel 3 Programmentwicklung und Programmiersprachen
Und der Befehl beim Aufruf dieser Funktion lautet: function kaffeeKochen(){
}
vorbereitenKaffee(„Segafredo Intermezzo“)
function vorbereitenServieren(){
}
}
wenn (Tassen unsauber){ dann spülen }
Tassen nehmen
wenn (kein Zucker){ Zucker kaufen }
Zucker nehmen
wenn (keine Milch){
kaufen (bei den derzeitigen Preisen besser: Nachbarn fragen) }
Milch nehmen }
kaffeeKochen();
Wäre nun nicht die richtige Zeit, um einen Schluck zu nehmen? Leider immer noch nicht – an dieser Stelle mag der eine oder andere Leser unzufrieden brummeln, denn jedermann dürfte klar sein, dass die richtige Kaffeesorte von elementarer Bedeutung für den „vollendeten Kaffeegenuß“ TM ist. Was geschieht jedoch, wenn jemand die verwendete Sorte keinesfalls goutieren möchte? In dem Fall bliebe zwar der gesamte Prozess vom Ablauf her der gleiche, aber eine Variable müsste mit einem anderen Inhalt belegt werden. In der Programmierung lässt sich das Problem z. B. mit Hilfe einer sogenannten parametrisierten Funktion lösen, also einer Funktion, der wir beim Aufruf eine Variable mit einem bestimmten Inhalt mitgeben. Dazu muss zunächst in der Funktionsdefinition festgelegt werden, dass wir eine derartige besondere Variable benötigen: function vorbereitenKaffee(welcheSorte){ …
mengeKaffee Pulver welcheSorte in Filter füllen … }
…
Wenn wir den Befehl zum Kaffee kochen an die Maschine ausgeben, teilen wir ihr gleichzeitig mit, welche Sorte denn gewünscht sei. In unserem konkreten Fall übergeben wir leckeren „Segafredo Intermezzo“. Wenn wir beim nächsten Mal anderen Kaffee wünschen, brauchen wir nur dementsprechend den Aufruf zu ändern: function kaffeeKochen(){
vorbereitenKaffee(„Pangalaktischer Donnergurgler Espresso“) … }
Obwohl uns bereits ein Gast verlassen hat, sind immer noch mehrere vorhanden. Daher reicht es nicht aus, abschließend die Funktion servieren in der momentanen Form aufzurufen. Denn die dort beschriebenen Arbeitsschritte müssen wir nicht nur einmal, sondern so oft ausführen, wie durstige Gäste anzutreffen sind, schließlich soll ja jeder einen Kaffee erhalten. Wiederholt auszuführende Anweisungen werden in der Programmierung mit Hilfe von Schleifen definiert. Ergänzen Sie dementsprechend die Definition von servieren: function servieren(){
für jeden Gast{
Kaffee in Tasse füllen
wenn (Gast will Zucker){ Zucker geben }
wenn (Gast will Milch){ Milch geben } } }
Auf diese Weise wiederholen wir für jeden einzelnen Gast das gesamte Prozedere des Verteilens unseres hoffentlich köstlichen Kaffees. Halten sich noch 5
3.4 Beispiel-Algorithmus „Kaffee kochen“
Gäste bei uns auf, führen wir die fraglichen Anweisungen fünfmal aus, sind 2 da, eben nur zweimal. Wenn man Code auf diese Weise entwickelt, kommt es vor, dass manche Information redundant und damit überflüssig ist, und dass der Programmablauf noch optimiert werden kann. So verfügen wir mit fertigenKaffeeServieren über eine Funktion, die nichts anderes tut, als ihrerseits weitere Funktionen, nämlich vorbereitenServieren und servieren aufzurufen. Wir können also auf fertigenKaffeeServieren verzichten und an deren Stelle direkt die beiden genannten Funktionen verwenden. function kaffeeKochen(){
vorbereitenKaffee(„Segafredo Intermezzo“) aufbrühen
wenn (Lämpchen brennt noch){ warten
Abbildung 3: Variablen- und Funktionsdefinition
23
} sonst {
vorbereitenServieren() servieren(); } }
Anschließend kann die Funktionsdefinition von fertigenKaffeeServieren gelöscht werden. Die bis hierher entwickelte Form des benötigten Algorithmus beschreibt bereits recht genau den Prozess, den wir programmiertechnisch abbilden wollen. Er enthält zahlreiche grundlegende Elemente, die in jedem Programm anzutreffen sind: Variable, Parameter, Aufruf bzw. Anweisung, Anweisungs-Block, Schleife und Bedingung. Die Abbildungen 3 bis 6 visualisieren sie in Kurzform, jeweils konkreten Elementen unseres Pseudo-Codes zugeordnet. Welche Bedeutung die jeweiligen Elemente im Einzelnen besitzen, werden wir uns in den nachfolgenden Kapiteln noch genauer anschauen.
24
Kapitel 3 Programmentwicklung und Programmiersprachen Abbildung 4: Anweisungsblock
Abbildung 5: Argument, Parameter, Schleife
3.4 Beispiel-Algorithmus „Kaffee kochen“
25 Abbildung 6: Anweisung, Bedingung, Funktionsaufruf
Unser gesamter Pseudo-Code sieht jetzt so aus: mengeKaffee = 8 Löffel
function kaffeeKochen(){
Wasser in Maschine füllen Kanne in Maschine stellen
wenn(keine Filter vorhanden){
vorbereitenKaffee(„Segafredo Intermezzo“)
Filter kaufen gehen
wenn (Lämpchen brennt noch){ warten
mengeKaffee Pulver welcheSorte in Filter füllen
vorbereitenServieren()
}
aufbrühen
}sonst{
}
Filter in Maschine einsetzen
einschalten
servieren();
function vorbereitenServieren(){
}
dann spülen
}
function vorbereitenKaffee(welcheSorte){ Kanne mit Wasser füllen Kanne fassen
Zum Wasserhahn gehen
Unter Wasserhahn halten Wasser fließen lassen Wasser stoppen
Kanne zur Maschine bringen
wenn (Tassen unsauber){ }
Tassen nehmen
wenn (kein Zucker){ Zucker kaufen }
Zucker nehmen
wenn (keine Milch){
26
kaufen (bei den derzeitigen Preisen besser: Nachbarn fragen) }
Milch nehmen }
function servieren(){ für jeden Gast{
Kaffee in Tasse füllen
wenn (Gast will Zucker){ Zucker geben
Kapitel 3 Programmentwicklung und Programmiersprachen
}
wenn (Gast will Milch){ Milch geben } } }
kaffeeKochen();
Hätten Sie jemals gedacht, dass Kaffeekochen so kompliziert und Programmieren so einfach sein könnte?
Programmierung und ActionScript
Mittlerweile haben Sie Ihren Kaffee aus dem vorhergehenden Kapitel genossen und stellen sich die Frage, ob man denn tatsächlich jedes Mal, wenn es einen nach Kaffee gelüstet, programmieren muss. Sicher nicht, dennoch sprechen zahlreiche Gründe für die Programmierung (zumindest in der Flash-Entwicklung):
• Eine programmierte Lösung reduziert die Dateig-
röße gegenüber einer rein grafischen Lösung zum Teil erheblich. • Interaktionen, also der Eingriff des Anwenders in den Ablauf Ihrer Applikation und die ZweiwegKommunikation mit ihm, ist nur per Programmierung möglich. Das beginnt schon bei der simplen Steuerung einer Animation, die ohne (allerdings ausgesprochen einfachen) Code nicht realisierbar ist. • Die Programmierung bietet erheblich mehr Flexibilität. Wenn wir beispielsweise eine Animation programmieren, können wir sie zeitleistenunabhängig sehr schnell über Variablen ändern und verschiedene Versionen austesten. Wir müssen keine Änderung an der Zeitleiste vornehmen und laufen so nicht Gefahr, unbeabsichtigt andere Animationen oder Objekte ebenfalls zu modifizieren. Ist der Code ordentlich geschrieben, reicht es, eine einzelne oder einige wenige Zahlen zu ändern und wir erhalten ein völlig neues Ergebnis. • Eine Applikation lässt sich durch externe Informationen anpassen, ohne dass wir die fla-Datei öffnen müssen. Sie wird damit erheblich leichter pflegbar und selbst Kunden können sie den eigenen Wünschen anpassen, ohne über Flash-Kenntnisse verfügen oder das Programm besitzen zu müssen. • Der Code lässt sich, falls korrekt geschrieben und strukturiert, leicht auslagern und in beliebigen Pro-
4
jekten wieder verwenden, was die Entwicklungszeit verkürzen kann. • Als zusätzlicher Benefit sollte nicht unerwähnt bleiben, dass das Erlernen einer Skriptsprache das Umsteigen auf eine andere oder sogar eine höhere Programmiersprache erheblich erleichtern kann. Je breiter man als Entwickler aufgestellt ist, desto bessere Berufschancen ergeben sich. Zudem profitieren auch gestandene Designer von Skriptsprachen, denn nahezu jedes Programm erlaubt die Vereinfachung des Workflows und die Automatisierung von Arbeitsschritten mit Hilfe von Skripten, sei es MEL-Skript in Maya, Expresso und Coffee bei Cinema, Javascript bei Photoshop oder gar JSFL bei Flash. • Weil der Chef es so will (das ultimative Motiv). Was genau versteht man nun unter Programmierung und wie gestaltet sie sich in Flash? Die Programmierung ist prinzipiell nichts anderes als die Entwicklung geeigneter Algorithmen, um ein gegebenes Problem zu lösen, so wie wir es uns zuvor an einem konkreten Beispiel angeschaut haben. Dazu verwendet man Skriptsprachen (z. B. Javascript, ActionScript, PHP) oder Programmiersprachen (z. B. Java, C++, C#). In solchen Sprachen erstellter Code wird nicht direkt von einer Maschine verstanden bzw. ausgeführt. Vielmehr muss er erst in eine für eine Maschine verständliche Zeichenfolge umgewandelt werden. Mit Maschine ist jedes Gerät gemeint, dass Code ausführen kann, in unserem Fall also der Computer. Programmiersprachen verwenden i. d. R. einen Kompiler, der den Code in Maschinensprache oder in eine maschinennahe Sprache übersetzt. Die meisten Skriptsprachen dagegen benötigen einen Interpreter, der den Code in einen Zwischencode oder P-Code übersetzt, der dann
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
27
28
Kapitel 4 Programmierung und ActionScript
von einer Virtuellen Maschine ausgeführt wird. Was zunächst umständlich aussieht, hängt einfach mit der Tatsache zusammen, dass ein Computer lediglich Binärcode versteht – die bekannten 0 und 1 –, für einen Menschen aber das Schreiben von Anweisungen mit derartigen Zeichen zu abstrakt und fehleranfällig wäre. Daher bieten uns die diversen Sprachen die Möglichkeit, Anweisungen in einer der natürlichen Sprache oder der Logik näher stehenden Form zu schreiben. Das Resultat wird dann automatisch umgewandelt, eben kompiliert oder interpretiert. Im Fall von ActionScript kommen derzeit zwei verschiedene Virtuelle Maschinen zum Einsatz, die wir uns nicht auswählen können, sondern die sich nach der verwendeten Sprachversion richten. Die AVM (ActionScript Virtual Machine, nachträglich auch AVM1 genannt) ist für AS 1.0 und 2.0 zuständig, während die AVM2 die Ausführung von AS 3.0 vornimmt. Eine derartige Trennung wurde notwendig, um den partiell grundlegenden Unterschieden der Sprachversionen gerecht zu werden. Diese AVMs sind in den FlashPlayer integriert und führen den von uns erstellten Code clientseitig, also auf dem Computer des Users, aus, sobald eine swf-Datei aufgerufen wird. Wie eine natürliche Sprache wird auch eine künstliche bzw. Programmiersprache durch ihre spezifische Semantik und Syntax bestimmt. Während sich die Semantik auf die Bedeutung der Elemente und Aussagen bezieht, ist mit Syntax die Struktur bzw. Grammatik gemeint. Denn die einzelnen Elemente ergeben nur dann einen Sinn, wenn sie in einer exakt definierten Weise Verwendung finden. Insofern gleichen sich natürliche und künstliche Sprachen. Tatsächlich hat das Erlernen einer Programmiersprache sehr viel gemein mit demjenigen einer Fremdsprache. Allerdings existieren auch bedeutende Unterschiede zu einer natürlichen Sprache:
• Ein
Programm enthält nur Anweisungen, nicht jedoch Beschreibungen. Eine Aussage wie „Das Auto hat eine rote Farbe“ lässt sich in Code nicht wiedergeben. Dort wäre es nur möglich, sich die betreffende Farbe ausgeben zu lassen oder die genannte Eigenschaft auf den gewünschten Farbwert zu setzen, also quasi das Auto einzufärben. In beiden Fällen läge eine Anweisung vor. Davon weichen Auszeichnungssprachen bzw. Markup Languages wie XML ab, die explizit eine Beschreibung zulassen. Näheres zu XML erfahren Sie weiter unten im gleichnamigen Kapitel.
• Code lässt keine Zweideutigkeiten zu. Wenn man
mit Hilfe von Anweisungen ein bestimmtes Ziel erreichen will, dann müssen diese so präzise wie möglich sein, wie bereits oben im vorhergehenden Kapitel gezeigt. Die natürliche Sprache dagegen strotzt geradezu von Zweideutigkeiten und unklaren Ausdrücken, die zu zahlreichen Missverständnissen führen (können), wie wir alle im täglichen Leben erfahren. Gleichzeitig ermöglichen sie jedoch etwas, zu dem eine Computersprache nie in der Lage wäre: Sprachwitze, die eben davon leben, dass Begriffe doppeldeutig sein können. Sie bereichern unsere Sprache, würden aber im Code nur zu Chaos führen (Stellen Sie sich vor, ein Computer müsste erst einmal überlegen, ob unsere Anweisung Ernst gemeint oder nur ein Scherz wäre!). • Ein Programm handelt nicht eigenständig, sondern folgt exakt unseren Anweisungen. Wenn sich ein Programm also fehlerhaft benimmt, dann i. d. R. weil unsere Anweisung falsch war, nicht jedoch weil der Flash-Player irgendwelche Fehler macht. • Die Reihenfolge, in der die Anweisungen in einem Skript auftreten, entspricht nicht zwangsläufig der Reihenfolge ihrer Ausführung. Zwar wird ein Skript beim Starten einer Flash-Datei zeilenweise von oben nach unten in den Arbeitsspeicher eingelesen, aber die konkrete Ausführung einzelner Anweisungen oder ganzer Code-Blöcke kann von Bedingungen und Ereignissen abhängig gemacht werden. ActionScript zeichnet sich durch verschiedene Eigenschaften aus, deren Kenntnis wichtig ist, wenn wir Code schreiben wollen. Dazu gehören:
• AS stellt eine ereignisorientierte Sprache dar, d. h.
Anweisungen werden nur dann ausgeführt, wenn bestimmte Ereignisse eintreten. Anweisungen können daher nie unabhängig von Ereignissen existieren. Das ist ein bisschen wie in unserem realen Leben: Wenn zu nachtschlafener Zeit gegen 5.45 Uhr der Wecker klingelt, quält sich der bemitleidenswerte Autor dieses Buches schlaftrunken aus dem Bett. Das Ereignis entspricht dem Klingeln des Weckers, während der Vorgang des Aufstehens aus Anweisungen und Befehlen an einzelne Körperteile oder allgemeiner aus Handlungen besteht. Nun klingelt in AS eher selten ein Wecker; dafür stehen dort jedoch zahlreiche vordefinierte Ereignisse wie onEnterFrame (der Abspielkopf betritt
den aktuellen Frame), onPress (auf das angegebene Objekt erfolgt ein Mausklick) oder onLoad (der Ladevorgang wurde abgeschlossen) zur Verfügung, die mit einer Ausnahme explizit angegeben werden müssen, bevor wir davon abhängige Anweisungen formulieren können. Die Ausnahme bezieht sich auf Code, der in einem Schlüsselbild steht. Der wird automatisch ausgeführt, da der Abspielkopf einen Frame zumindest einmal automatisch betritt (andernfalls wäre Flash gar nicht in der Lage, irgend etwas darzustellen). • Je nach Sprachversion ist AS nur objektbasiert oder aber objektorientiert. In einer objektbasierten Sprache stellen Objekte lediglich benamte Ansammlungen beliebiger Daten (bzw. Eigenschaften) und Funktionen (bzw. Methoden) dar. Diese Objekte werden i. d. R. einfach mit Hilfe assoziativer Arrays erstellt. Eine objektorientierte Sprache dagegen erlaubt die Erstellung und Manipulation von Objekten, die in hierarchischen Klassen organisiert sind. Letzteres trifft auf die AS-Versionen 2 und 3 zu. Alles, was sich in Flash per Code ansprechen lässt, besteht aus einem Objekt, sei es nun ein Textfeld, eine Schaltfläche, ein MovieClip oder auch nur ein Behälter für Daten wie eine Variable oder ein Array. Damit einher geht die Schreibweise, die als Dot-Syntax bekannt ist. Demzufolge müssen alle Elemente, die in einem Code angesprochen werden, eindeutig und einmalig benannt sein. Befehle, Ereignisse und Methoden werden ihnen zugeordnet, indem an die Namen von Objekten die gewünschten Elemente, getrennt durch einen Punkt, angehängt werden, z. B.: einArray.length, einButton.onRollOver oder einMovieClip.loadMovie(). Die in ActionScript zur Verfügung stehenden Objekte werden in Klassen erfasst, die sich in der FlashHilfe einsehen lassen. Bei einer Klasse handelt es sich um eine Art Blaupause, in der Struktur und Funktionalität der betreffenden Objekte festgelegt werden. Was so abstrakt klingt, entspricht vollkommen unserem Alltagsverständnis. Wenn wir beispielsweise ein Haus sehen, wissen wir, dass es sich um ein Objekt der Kategorie „Gebäude“, nicht jedoch um ein Objekt beispielsweise der Kategorie „Säugetier“ handelt. Von einem Haus erwarten wir ganz bestimmte Dinge, etwa, dass es über einen Zugang in Form einer Tür verfügt. Dagegen fehlen ihm beispielsweise Füße oder Flossen, die man eher bei einem Säugetier vorfinden würde (jedenfalls in dieser Ecke des Universums).
29
Wer ActionScript lernen möchte, sollte sich daher unbedingt mit der Flash-Hilfe vertraut machen und dort regelmäßig nachschlagen, bis man sich die wichtigsten Klassen angeeignet hat. Sie unterteilt die benötigten Informationen in sogenannte Bücher, die sich als pdf-Datei von der Seite des Herstellers herunterladen lassen. Für uns besonders wichtig ist die ActionScript 2.0 Language Reference. Betrachten wir darin beispielhaft die Klassendefinition für Buttons, zu finden unter ActionScript Classes – Button. Neben einer allgemeinen Erläuterung finden sich dort Tabellen mit einer Auflistung derjenigen Elemente, aus denen sich eine Klassenbeschreibung zusammen setzt. Dabei handelt es sich prinzipiell um:
• Eigenschaften. Sie legen fest, wie ein Objekt ist, ge-
ben mithin Auskunft auf die Wie-Frage. So verfügt eine Instanz der Klasse Button bzw. das konkrete Button-Objekt auf unserer Bühne über die Eigenschaften _width und _height, also eine horizontale und vertikale Ausdehnung gemessen in Pixeln. Sobald wir auf ein Objekt treffen, das dieser Klasse zugeordnet werden kann, wissen wir, dass es über eine Ausdehnung im Raum verfügt. Und diese lässt sich entsprechend der in der Klassendefinition enthaltenen Angaben behandeln. Prinzipiell möglich sind Auslesen (Rückgabe des Wertes einer Eigenschaft) und Setzen (Verändern des Wertes). Während wir alle Eigenschaften auslesen können, sind manche schreibgeschützt und lassen sich nicht ändern. Wer in AS 2 schreibt, muss sich leider mit einer Altlast von Flash herum schlagen. Denn in älteren Versionen eingeführte Eigenschaften wurden mit einem Unterstrich gekennzeichnet, während neuere ohne verwendet werden. So existieren bei der Button-Klasse die Eigenschaften _width und blendMode, konkret: einButton._width sowie einButton.blendMode. • Ereignisse. Sie definieren, wann die betreffende Klasse etwas tut bzw. wie man mit ihr interagieren kann. Zu einem Button gehört hier klassischerweise der Mausklick, syntaktisch korrekt geschrieben als einButton.onPress. Rein formal lassen sich Ereignisse an dem Präfix on, gefolgt von einer mit einer Binnenmajuskel eingeleiteten Zeichenfolge, erkennen. Zu einem Ereignis gehört i. d. R. mindestens eine Anweisung, die wir normalerweise als Zuweisung einer anonymen Funktion schreiben. Ein konkretes Beispiel könnte lauten: einButton.onPress = function(){Anweisung;}.
30
Kapitel 4 Programmierung und ActionScript
• Methoden. Sie beschreiben, was eine Klasse alles
tun kann. In dieser Hinsicht kommt die ButtonKlasse ausgesprochen bescheiden daher, während einen andere Klassen wie etwa der MovieClip geradezu überschütten mit einer Fülle an Möglichkeiten. Einen Button können wir anweisen, seine Tiefe anzugeben. Dazu müsste man die Methode einButton.getDepth() verwenden. Methoden werden syntaktisch durch nachgestellte runde Klammern () gekennzeichnet.
Nicht jede Klasse verfügt über alle genannten Elemente. So besitzt etwa die Array-Klasse keine eigenen Ereignisse. Trotzdem können wir sie verwenden, indem sie aufgerufen wird, wenn ein zu einer anderen Klasse gehöriges Ereignis eintritt, etwa der Klick auf einen Button. Klassen stellen also keine isolierten Elemente innerhalb eines Scripts dar, sondern sind zumeist aufeinander bezogen, und erst ihre Interaktion untereinander ergibt eine vollständige Applikation. Flash bietet zwei Möglichkeiten, um sie zu verwenden:
• Instanziierung. In den meisten Fällen erstellen wir
eine Kopie einer Klasse bzw. eine Instanz, die über alle Eigenschaften, Ereignisse und Methoden verfügt, die in der betreffenden Klasse festgehalten sind. Das macht dann Sinn, wenn mehr als nur ein Objekt zu einer bestimmten Klasse gehören kann. So sind Schaltflächen ihrer Natur nach sehr gesellig, kommen also i. d. R. mehr als einmal vor. Wenn wir Buttons verwenden, dann handelt es sich daher immer um eine Instanz der Button-Klasse. • Direkter Zugriff. Manche Klassen lassen keine Kopie zu, so dass wir direkt auf sie zugreifen müssen. Das ist etwa bei der Mouse-Klasse, also dem Cursor, der Fall – verständlicherweise, denn eine Anwendung, die gleichzeitig über mehrere Cursor verfügt, wäre nicht unbedingt sinnvoll und kaum benutzbar. Als Entwickler müssen wir uns nicht überlegen, wann eine Instanz eingesetzt werden soll und wann nicht. Diese Entscheidung nimmt uns Flash ab und informiert in der genannten Hilfe über den richtigen Weg. Dort finden sich zugleich Anwendungsbeispiele für die einzelnen Befehle, die man teilweise per copy-and-paste einfügen und unmittelbar ausprobieren kann. Leider hat die früher gute Flash-Hilfe seit Version 8 insofern stark nachgelassen, als Adobe wohl davon ausgeht,
dass sich nur noch Informatiker mit ihrem Produkt befassen. Einsteiger sind mit der Erläuterung vieler seit dieser Version hinzu gekommener Befehle überfordert, weil der verschwurbelte Beispielcode viele Nebelkerzen abbrennt, anstatt sich auf das Wesentliche zu konzentrieren. Es ist aus didaktischen Gründen eben nicht immer ideal, Programmierer eine Hilfe schreiben zu lassen (Wink an den Hersteller). All diese Elemente verfügen über einen Namen bzw. einen Bezeichner, so dass wir in der Lage sind, per Code darauf zuzugreifen. Wäre beispielsweise die Klasse Array namenlos, könnten wir nie ein neues Array einrichten, um darin Daten aufzunehmen. Aus diesem Grund ist es wichtig, dass wir für Objekte, die wir selbst einrichten, also beispielsweise eine Schaltfläche, keinen Namen verwenden, der ein von ActionScript vorgegebenes Element bezeichnet. Einem Button spaßeshalber den Instanznamen Array zu geben wäre zwar möglich, würde aber Flash in eine gewisse Verwirrung stürzen, da nun zwei verschiedene Dinge mit dem gleichen Namen versehen wären. Die oben erwähnte Eindeutigkeit ginge damit verloren, da Flash nicht mehr in der Lage wäre, genau zu erkennen, wen wir denn mit Array meinten. Glücklicherweise hilft uns das Programm, indem mit Hilfe des sogenannten Syntax-Higlighting oder Coloring automatisch all jene Begriffe farbig hervorgehoben werden, die als reserviert weil von ActionScript bereits verwendet gelten. Dazu gehören die Schlüsselwörter und die Bezeichner. Diese Begriffe dürfen also niemals dazu verwendet, ja geradezu missbraucht werden, um andere als von Flash vorgesehene Elemente zu kennzeichnen. Wir bereits im vorhergehenden Kapitel gesehen, besteht ein Programm nicht nur aus Klassen bzw. entsprechenden Instanzen, so wichtig sie auch sein mögen. Vielmehr existieren als zusätzliche Elemente einer Sprache Operatoren, Anweisungen, Konstanten und Funktionen, die es uns ermöglichen, die in den Klassen zur Verfügung gestellten Objekte zu verändern. Um Code schreiben zu können, stellt Flash einen (eher rudimentären) Editor zur Verfügung, zu finden unter bzw. mit dem Tastenkürzel . Abbildung 7 zeigt den Editor für Flash CS 4. Der Editor umfasst mehrere Fenster mit diversen Elementen (entsprechend der Nummerierung in der vorhergehenden Abbildung):
31 Abbildung 7: Integrierter ASEditor
1. Die ActionScript-Version. 2. Bietet Zugriff auf einzelne Codefragmente, um auf die Schnelle Begriffe, Eigenschaften, Methoden etc einzufügen. 3. Ruft eine mehr als rudimentäre Suchfunktion in einem – man glaubt es kaum – modalen Fenster auf. Das ist faktisch wertlos. Statt dessen sollte man über <Suchen und Ersetzen> ein komfortableres Fenster aufrufen. Achtung: das diesem Befehl eigentlich zugeordnete Tastenkürzel <Strg> öffnet das zuvor angesprochene Fensterlein. 4. Zeigt alle Pfade der momentan auf der Bühne befindlichen Objekte an – sowohl relativ wie auch absolut. 5. Dient zur Beruhigung insbesondere von Anfängern, da dieser Button in den allermeisten Fällen höflicherweise behauptet, Ihr Skript enthalte keine Fehler. 6. Kompiliert das Skript und zeigt zuverlässig Syntax-Fehler an. Leider ist manche Meldung anfangs etwas kryptisch. Beachten Sie, dass mehrere Fehlermeldungen auf einmal nicht unbedingt bedeuten, dass Sie großartigen Mist geschrieben haben. Alle Meldungen ab der 2. Zeile können Folgefehler sein, die automatisch verschwinden, sobald der Fehler in der ersten Zeile korrigiert wurde. Ein Doppelklick auf diese Meldung
springt im Code-Fenster direkt an die fragliche Stelle. Das Kompilieren führt automatisch zu einem Einrücken von Code-Blöcken. Gerade Designer, die zum ersten Mal mit einer Skriptsprache in Berührung kommen, betrachten das Einrücken gerne primär unter einem ästhetischen Aspekt. Tatsächlich dient es jedoch einer logischen Strukturierung, die uns ermöglicht, alleine durch die Position bereits Abhängigkeiten und Zuordnungen zu erkennen. Insofern ist sie ausgesprochen wichtig, um frühzeitig logische Fehler lokalisieren zu können. Da sich das Einrücken automatisch nach dem logischen Aufbau unseres Skriptes richtet, verbietet es sich von selbst, händisch mit der Tab-Taste Zeilen zu verschieben. Dadurch erhalten wir zwar ein ästhetisch optimiertes Ergebnis, dem aber nicht mehr zwangsläufig die Logik des Codes entspricht. 7. Zeigt manchmal Codehinweise. 8. Ermöglicht das Deaktivieren von Debug-Optionen. 9. Bei längeren Skripten kann es nützlich sein, ganze Blöcke quasi ein- und auszuklappen, so, wie Sie es bei einem Dateimanager gewohnt sind, der durch ein vorangestelltes Plus-Zeichen anzeigt, dass hier ein Objektbaum geöffnet oder durch ein MinusZeichen zeigt, dass ein Baum geschlossen werden kann.
32
10. Kommentaroptionen (einzeilig, mehrzeilig, Aufheben des Kommentars). 11. Zauberstab zum automatischen Erzeugen hochkomplexer Skripte – nein, nicht wirklich, sondern ein überflüssiger Modus, in dem man simple Skriptchen zusammen klicken könnte, wenn hier gezeigt würde, wie. Das verkneifen wir uns jedoch. 12. Optionen für die IDE wie etwa Zeichengröße, Syntaxhighlight, etc. 13. Hilfefunktion, die bis CS 3 brav und sinnvollerweise zu einem markierten Begriff die entsprechende Hilfe anzeigte. Seit CS 4 in der Standardinstallation ein Aufruf der Online-Hilfe, der mitunter über eine Online-Site meldet, es bestehe keine Internet-Verbindung. 14. Auflistung aller Sprachelemente, sortiert nach verschiedenen Kategorien. Nützt vor allem dann etwas, wenn man genau weiß, wonach man sucht, da erst die Kategorie bekannt sein muss, in der der entsprechende Begriff zu finden ist. 15. Zeigt die aktuelle Position innerhalb der gesamten Applikation (also Frame, Zeitleiste und Objekt) an. 16. Da man die unter 14 und 15 genannten Elemente nicht wirklich braucht, kann man sie hier bequem zur Seite schieben, um so für das eigentliche Skript mehr Platz zu erhalten. 17. Endlich – das eigentliche Skriptfenster, in dem Sie Ihren Code schreiben. Am linken Rand befindet sich die Zeilennummerierung, die bei einer Orientierung in längeren Skripten hilfreich ist. Wie in einem normalen Texteditor wird das Script zeilenweise von oben nach unten und von links nach rechts geschrieben. Allerdings entspricht diese Schreibweise nicht zwangsläufig nachher auch der Reihenfolge, in der Ihre Anweisungen ausgeführt werden. Denn schließlich können Sie Bedingungen und Ereignisse definieren, von denen die konkrete Ausführung abhängt. 18. Wohl ob seiner bescheidenen Größe völlig unterschätztes Feature, bietet doch die Pinnadel die Möglichkeit, ein aktives Skript zu fixieren. Damit bleibt der Editor immer auf den Frame ausgerichtet, auf den er festgelegt wurde, selbst dann, wenn Sie zwischenzeitlich auf einen anderen Frame oder ein Objekt klicken. Das ist deshalb wichtig, weil wir aus Gründen der Übersicht meistens in einem oder zwei Frames unsere Skripte schreiben,
Kapitel 4 Programmierung und ActionScript
aber während der Erstellung der Applikation ständig wechselnde Objekte aktivieren. Jeder Wechsel führt dazu, dass unser vorheriges Skript nun nicht mehr sichtbar ist, was gerade bei Anfängern schnell zur Verwirrung führt („Hilfe – Flash hat mein Skript gelöscht!“). Wie erwähnt wird Code einfach in das Textfenster des Editors geschrieben. Achten Sie dabei auf die richtige ActionScript-Version: unter 1 muss ActionScript 1 & 2 eingestellt sein. Ebenfalls ActionScript 2 muss unter <Einstellungen für Veröffentlichungen> festgelegt sein. Da Sie nun sicher darauf brennen, endlich mal eine Zeile Code zu schreiben, bitte schön: 1. Klicken Sie in der Zeitleiste in einen Frame hinein. Achten Sie darauf, dass in der linken oberen Ecke des Editors „Bild“ steht wie in der vorhergehenden Abbildung zu sehen. 2. Tippen Sie in den Editor: trace(„Hallo Welt“);
3. Klicken Sie <Strg><Enter>. Es poppt ein neues Fenster auf, das Sie mit einem fröhlichen „Hallo Welt“ begrüßt. Wenn Sie <Strg><Enter> drücken, wird die von Ihnen erstellte Flash-Datei in ein Format umgewandelt, das die oben erwähnte virtuelle Maschine verstehen kann. Dabei entsteht eine swf-Datei, die der Flash-Player ausführt. Sie wird standardmäßig im selben Ordner abgespeichert, in dem sich auch die zugehörige fla-Datei befindet. Ihr umfangreicher Code enthält die trace()-Anweisung, die dazu führt, dass Flash ein neues Fenster, das sogenannte Nachrichtenfenster, öffnet, um die innerhalb der Klammern in Anführungszeichen gesetzte Information anzuzeigen. Das Nachrichtenfenster dient uns in der Autorenumgebung von Flash dazu, die Funktionsweise eines Skripts auf die Schnelle zu testen. Es steht später in der fertigen Anwendung nicht mehr zur Verfügung; wir werden im Rahmen der einzelnen Kapitel sehr oft mit diesem Fenster arbeiten. Am Ende der Befehlszeile befindet sich ein Semikolon, mit dem wir Flash mitteilen, wo unsere Zeile endet. Das ist in Flash zwar nicht mehr unbedingt notwendig, da der Abschluss automatisch erkannt wird, aber es schadet auch nicht, zumal andere Skriptsprachen ihrerseits auf dieser Zuwendung bestehen. Um uns keine Ausnahmen merken zu müssen, setzen wir hier ebenfalls ein Semikolon.
Als ereignisorientierte Sprache benötigt Flash eben ein Ereignis, um unseren unerwartet spannenden Code ausführen zu können. Da wir kein Ereignis explizit nennen und es sich in vorliegendem Fall um ein dem Bild bzw. Frame zugeordnetes Skript handelt, tritt automatisch ein Ereignis ein, nämlich das Betreten des aktuellen Frames durch den Abspielkopf. Mit dem Abspielkopf ist die rote Markierung der Zeitleiste im aktuellen Frame gemeint. Er ist dafür verantwortlich, dass Flash – Sie erinnern sich: es stellte ursprünglich ein reines Animationsprogramm dar –, überhaupt einen zeitlichen Verlauf abbilden kann. Betritt der Abspielkopf einen Frame, werden alle dort vorhandenen Skripte in den Arbeitsspeicher geladen, die dortigen Anweisungen, soweit möglich, ausgeführt, die benötigten Berechnungen durchgeführt und auf ihrer Basis beispielsweise die Positionen von Objekten neu kalkuliert und erst dann, wenn all diese Prozesse abgeschlossen und ein Screen im Arbeitsspeicher komplett neu gezeichnet wurde, wird er für uns wahrnehmbar dargestellt. Das klingt nach viel Arbeit, die aber i. d. R. im Bruchteil einer Sekunde durchgeführt wird. Grob kann man sagen, dass sich diese Prozesse an der Bildwiederholrate des Films orientieren. Haben wir beispielsweise 25 BpS eingestellt, zeichnet Flash 25 Mal pro Sekunde den Screen neu. Allerdings bedeutet das nicht automatisch, dass alle vorhandenen Skripte ebenfalls neu in den Speicher geladen und ausgeführt werden. Das ist nur dann der Fall, wenn der Abspielkopf loopt, also wenn mindestens zwei Frames vorhanden sind und kein stop()-Befehl im Code existiert. Dann springt der Abspielkopf jedes Mal, wenn er den letzten Frame erreicht hat, zurück zum ersten und spielt wieder alle Frames einschließlich der dort enthaltenen Skripte ab. Existiert dagegen ein Stop-Befehl oder besteht die Anwendung nur aus einem Frame, wird dessen Code ein einziges Mal in den Speicher eingelesen und dort quasi vorrätig gehalten. Unabhängig davon zeichnet Flash die Bühne trotzdem permanent neu. Das macht Sinn, denn es ist ja durchaus möglich, beispielsweise innerhalb eines MovieClips eine mehrere Frames umfassende Animation zu erstellen, die auch ohne Code abspielen soll. Abgesehen von dem Betreten des Frames durch den Abspielkopf müssen alle anderen Ereignisse explizit genannt werden, unabhängig davon, ob es sich dabei um anwendergesteuerte oder automatisch vom Rechner auszuführende Ereignisse handelt. Sie wer-
33
den im Skript einfach in der Form geschrieben, die wir bereits oben kennen gelernt haben: einButton.onPress = function(){ Anweisung; }
Anders formuliert: Für Ihre Anweisungen besteht also nur die Möglichkeit, von einem automatischen Ereignis (Abspielkopf) oder von einem explizit erwähnten Ereignis (user- oder systemabhängig) ausgelöst zu werden. Im erstgenannten Fall werden die Anweisungen zeilenweise in der Reihenfolge des gewünschten Auftretens eingetragen, im zweitgenannten Fall muss einem benannten Objekt mit Hilfe der Dot-Syntax ein entsprechendes Ereignis zugewiesen werden, innerhalb dessen Anweisungs-Block die benötigten Befehle stehen. Dann muss das gewählte Ereignis blau gekennzeichnet sein, da nur vorgefertigte Ereignisse zulässig sind, die als Bezeichner gelten. Über die Färbung können Sie gerade am Anfang Ihrer Programmierkarriere schnell erkennen, ob ein Fehler vorliegt oder nicht. Dasselbe Ereignis kann theoretisch mehrfach geschrieben werden. Ein Skript ist jedoch nicht kumulativ, d. h. nur das zuletzt gefundene Ereignis wird dann auch tatsächlich ausgeführt; alle früheren werden von diesem schlicht überschrieben. Unsinn wäre also: einButton.onPress = function(){ ball.gotoAndPlay(5); }
einButton.onPress = function(){ dreieck.gotoAndStop(1); }
Syntaktisch sind diese Anweisungen korrekt, von der Logik her aber unsinnig, denn der Instanz mit dem Namen einButton wird zweimal dasselbe Ereignis onPress zugewiesen. Dabei wird das erste onPress faktisch durch das zweite ersetzt. Ein Mausklick würde also lediglich dazu führen, dass die Instanz mit dem Namen dreieck zurück auf dessen ersten Frame gesetzt würde. Mit ball dagegen würde nichts geschehen. Sollen trotzdem beide Anweisungen ausgeführt werden, schreibt man sie in ein gemeinsames onPress-Ereignis hinein: einButton.onPress = function(){ ball.gotoAndPlay(5);
34
Kapitel 4 Programmierung und ActionScript
dreieck.gotoAndStop(1); }
Denken Sie daran, dass dieser Code lediglich bedeutet, dass eine Instanz mit dem Namen einButton auf einen Mausklick reagiert. Wenn Sie beim Testen andächtig vor Ihrer Applikation sitzen, ohne den nach Zuwendung heischenden einButton mit der Maus anzuklicken, passiert nichts. Das ist der Grund, warum ActionScript als ereignisorientierte Sprache gilt. Was wir in unserer Genialität als Skript produzieren, möchten wir der Nachwelt erhalten. Dazu stehen je nach konkretem Einsatzzweck insbesondere folgende Dateitypen zur Verfügung:
• swf. Es klingt schon lustig, wenn man feststellen
muss, dass sich die IT-Geister darüber streiten, was denn mit diesem Kürzel gemeint sei. Steht es nun für „Small Web Format“ oder für „Shockwave Flash“? Richtiger scheint wohl die letztgenannte Variante zu sein, schließlich generiert Flash beim Erstellen einer HTML-Seite mit Code zum Einbetten des Flash-Players einen Bezug auf einen Shockwave-Ordner. Der von Macromedia verwendete Name sollte wohl betonen, dass es sich wie bei dem ursprünglichen Shockwave, einem mit dem Kürzel dcr versehenen Director-Format, um ein Webformat handle. Eine swf-Datei wird
vom Player verwendet und kann theoretisch nicht in der Flash-Autorenumgebung geöffnet werden. Diese Datei ist also Teil beispielsweise Ihrer Website und liegt auf dem Server, auf dem sich die übrigen Dateien wie etwa HTML befinden. Beim Aufruf einer Site im Browser wird diese Datei in den Browser-Cache des Clients geladen (falls das Cachen nicht explizit verhindert wird) und dort ausgeführt. Flash ist insofern eine clientseitige Technik im Gegensatz etwa zu PHP als einer serverseitigen Technik. • fla. Das offene Pendant zu swf steht in der Autoren umgebung zur Verfügung. Sie stellt die Arbeitsdatei dar, in der Sie Ihre Flash-Anwendung erstellen. Diese Datei wird nachher nicht auf den Server gestellt, sondern verbleibt auf Ihrer Festplatte, so dass sie bei Bedarf jederzeit wieder geöffnet und geändert werden kann. • as. ActionScript -Code lässt sich auslagern, um ihn in verschiedenen Dateien wieder zu verwenden. Die betreffende Dateiendung as verweist dabei auf eine reine Text-Datei, die sich, falls gewünscht, in beliebigen Programmen, die das txt-Format lesen können, öffnen lassen. Die as-Datei muss nicht auf dem Server abgelegt werden, denn Flash fügt ihren Inhalt beim Erstellen einer swf-Datei automatisch in diese Datei ein.
Framework und Codeorganisation
Die Mehrzahl der Flash-Entwickler hat als fachfremde Quereinsteiger oder als Designer ihren Zugang zum Metier gefunden. Das ist natürlich keineswegs verwerflich, hat jedoch zur Folge, dass die resultierenden Skripte insbesondere in formaler Hinsicht nicht dem entsprechen, was man von einem systematisch entwickelten Code erwarten könnte. Vorgehensweisen, die in der Softwareentwicklung zur Anwendung kommen und auch für Flash-Projekte zumindest ab einer bestimmten Größe geboten wären, sind unbekannt. Das führt nicht selten zu einem recht kunterbunten Gemisch an Code-Bausteinen, was sich spätestens dann als zeitraubendes Ärgernis entpuppt, wenn ein Skript überarbeitet werden muss. Mitunter ist es einfacher und schneller, eine komplette Applikation neu zu entwickeln, als den Code, den man von einem anderen Flash-Entwickler erhalten hat, zu überarbeiten. Dabei reicht es schon aus, einige sehr einfache Regeln zu beachten, um Code übersichtlicher zu gestalten. Dazu gehören beispielsweise Namenskonventionen, auf die wir an späterer Stelle noch ausführlicher eingehen, sowie Vorgaben hinsichtlich der Struktur des zu entwickelnden Programms.
5.1 Skriptarten (Objekt- und Bildskript) Historisch bedingt bietet Flash die Möglichkeit, sowohl ein Objekt- wie ein Bildskript zu schreiben. In Flash 5 und vorher war es unmöglich, ausschließlich mit Bildskripten zu arbeiten, da sich die Objekte nicht ansprechen ließen, um ihnen Ereignisse zuweisen zu können. Statt dessen musste man jedes Objekt markieren und das jeweils benötigte Skript einfügen. Auf
5
den ersten Blick mag das sogar sehr logisch erscheinen. Denn warum sollte man z. B. die Funktionalität einer Schaltfläche nicht unmittelbar an ihr selbst definieren? Würde man denn nicht erwarten, dass sich ein die Funktionalität eines Objekts definierendes Skript auch genau dort befindet, wo wir auf dieses Objekt treffen? Der große Nachteil einer derartigen Vorgehensweise besteht in der Unübersichtlichkeit. Denn je mehr Objekte vorliegen, desto mehr Speicherorte für die Skripte würden existieren. Um beispielsweise die Logik eines Programms zu verstehen, würde es nicht ausreichen, an einer oder zwei Stellen nachzuschauen, sondern man wäre gezwungen, im Extremfall auf Skripte an Dutzenden von Orten zugreifen zu müssen. Hinzu kommt, um es möglichst wertneutral zu formulieren, eine gewisse Geheimniskrämerei von Flash. Denn der Film-Explorer, dessen Aufgabe es eigentlich sein sollte, erschöpfend Auskunft über die Inhalte einer fla-Datei zu geben, weigert sich schon mal gerne, auf einen Blick alle Skripte anzuzeigen. Sind Ihre Code-Schnipsel verschachtelt und beispielsweise Objekten zugewiesen, die sich in anderen Objekten befinden, so bleibt Ihnen nichts anderes übrig, als wild auf alle Symbole zu klicken, bis sich das betreffende Skript offenbart. Nicht viel anders ergeht es Ihnen, wenn Sie mit verschiedenen Szenen arbeiten. Die bereits vor vielen Jahren von Director – zur Erinnerung: Director stammt vom selben Entwickler wie Flash – praktizierte Lösung mit der Aufnahme von Skripten in die Bibliothek wurde geflissentlich ignoriert. Daher empfiehlt es sich dringend, möglichst vollständig auf derartige Objektskripte zu verzichten bzw. sie auf ein absolutes Minimum zu reduzieren. In den in diesem Buch verwendeten Beispielen sowie den Workshops kommen keinerlei Objektskripte
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
35
36
Kapitel 5 Framework und Codeorganisation Abbildung 8: Zuweisung eines Objektskripts
zum Einsatz. Dennoch sollten Sie wissen, wie derartige Skripte zustande kommen für den Fall, dass Sie sich in den betreffenden Code einarbeiten müssen. Die Vorgehensweise ist einfach: Markieren Sie das zu skriptende Element, wobei es sich entweder um ein Schaltflächen- oder ein MovieClip-Symbol handeln kann. Der Editor zeigt in der Titelleiste den SymbolTyp an, wie auf Abbildung 8 zu sehen. In das Skriptfenster tragen Sie den gewünschten Code ein, beginnend mit einem Ereignis, z. B.:
Skript geöffnet hat, aber gleichzeitig Objekte markieren möchte. Um das Wechseln zu einem Objekt-Skript zu unterbinden, können Sie das aktive Fenster fixieren. Dazu reicht es, den Frame, in dem sich das gewünschte Skript befindet oder in dem es geschrieben werden soll, anzuklicken. Anschließend klicken Sie auf die Pinnadel am unteren Rand des Skript-Editors, wie in Abbildung 9 gezeigt.
on (press) {
trace(„Ich bin ein Objektskript“); }
Die Alternative besteht in sogenannten Bildskripten, also Code, der unmittelbar einem Frame zugewiesen wird. Dort lassen sich alle Funktionalitäten festlegen, die benötigt werden. Das gilt selbst für solche Objekte, die erst nach dem aktuellen Frame oder durch eine spätere Interaktion des Anwenders auftauchen. Die Zuweisung eines Skripts funktioniert ähnlich wie bei Objektskripten. Klicken Sie in den gewünschten Frame, wird er im Editor aktiviert und Sie können mit Ihrem Skript beginnen. Die Markierung eines Elements (Objekt oder Frame) legt also fest, welche Art von Skript Sie gerade schreiben. Nun kommt es oft vor, dass man ein
Abbildung 9: Fixieren des Skriptfensters
5.3 Aufbau eines Skripts
Nun bleibt das gewählte Bildskript immer im Vordergrund, selbst wenn ein beliebiges Objekt oder ein anderer Frame markiert wird. Möchten Sie trotz Fixierung zwischendurch einmal zu einem anderen Skript wechseln, können Sie dazu die Karteireiter am unteren Rand des Editors verwenden. Wird nämlich ein Element markiert, erscheint dort an erster Stelle ein Karteireiter mit Bezug auf dieses Element. Klicken Sie auf den Reiter, wechselt die Ansicht im Editor zu besagtem Element, ohne die vorherige Fixierung aufzuheben. Um zurück zu kehren, müssen Sie nur auf den zweiten Reiter klicken.
5.2 Anzahl der Bildskripte Wie bei der Frage nach dem richtigen Skript-Typ gilt auch hier: Je weniger desto besser. Wenn wir das Bildskript dem Objektskript vorziehen, weil sich damit die Zahl der Skripte reduziert, bevorzugen wir auch hier einen Frame gegenüber mehreren. Im Idealfall befindet sich ein einziges Bildskript in Frame 1 (oder Frame 2) der Szene 1 der Hauptzeitleiste. Die weitere Suche nach Code entfällt dann notwendigerweise automatisch. Dabei ist jedoch zu beachten, dass alle Objekte, die in einem derartigen Skript angesprochen werden, auch im ersten Frame existieren müssen. Es macht z. B. keinen Sinn, in Frame 1 eine Ereigniszuweisung für einen Button zu definieren, der erst in Frame 2 auftaucht. Dieses Ereignis würde von Flash logischerweise ignoriert. Leider gibt das Programm beim Kompilieren keine Fehlermeldung aus, wenn wir versuchen, auf ein nicht-existentes Objekt zuzugreifen. Oft besteht eine Anwendung jedoch aus mehreren Frames, in denen jeweils verschiedene Objekte existieren. So wird ein Film, der einen anderen, größeren Film lädt, einen Preloader benötigen. Die Elemente des Preloaders stehen dann im ersten Frame zur Verfügung, die externen Assets dagegen natürlich noch nicht. Oder es soll zunächst ein extern geladenes Intro abspielen, ehe das Hauptmenü erscheint. In solchen Fällen wird man seine Skripte auf mehrere Frames verteilen. So könnte Frame 1 das Preloader-Skript beinhalten, während in Frame 2 beispielsweise das Hauptskript steht. Das ist so lange unproblematisch, wie man nicht Dutzende von Bildskripten mit jeweils einigen Hundert Zeilen Code verwendet.
37
Eine ähnliche Fragestellung tritt auf, wenn unsere Webseite oder CD-ROM vollständig aus Flash besteht. Dann verwenden wir bekanntermaßen einen modularen Aufbau, der viele einzelne swf-Dateien umfasst. Prinzipiell wäre es möglich, den kompletten Sourcecode in einem einzigen Hauptfilm in dessen erstem Frame unterzubringen. Bei jedem Ladevorgang würde ein erfolgreicher Abschluss geprüft und anschließend den externen Assets die benötigte Funktionalität zugewiesen. In der Praxis erweist es sich jedoch meistens als einfacher, in diesem Fall den Code aufzusplitten. Alles, was sich auf die externen Assets bezieht (mit Ausnahme des Ladevorgangs natürlich), kann in den externen Dateien im ersten Frame definiert werden. Wenn wir dann beispielsweise den Namen eines Objekts ändern, können wir direkt innerhalb derselben Datei den Code anpassen ohne dafür eine zweite Datei, nämlich den Hauptfilm, öffnen zu müssen. Eine Alternative zu den Bildskripten besteht in der Möglichkeit, Code komplett auszulagern und ihn bei Bedarf über eine include- oder import-Anweisung zu laden. Damit gewinnt man insofern an Flexibilität, als einmal entwickelter, funktionierender Code leicht in anderen Projekten einsetzbar wird, ohne dass man gezwungen ist, alles neu zu entwickeln. Voraussetzung für eine derartige Vorgehensweise ist allerdings eine gewisse Erfahrung, da der externe Code so strukturiert sein muss, dass er in beliebige Applikationen hinein passt.
5.3 Aufbau eines Skripts Wenn wir, wie im vorhergehenden Abschnitt empfohlen, tatsächlich alles (oder möglichst viel) in einen einzigen Frame schreiben, schwillt ein derartiges Skript notwendigerweise rasch an. Umso mehr sind wir darauf angewiesen, den Code in eine gewisse Struktur zu zwingen, sonst geht schon alleine aufgrund der Fülle die Übersichtlichkeit wieder verloren. Grob kann man zwei Bereiche innerhalb einer solchen Struktur unterscheiden:
• Meta-Informationen über die Datei insgesamt. • Befehlszeilen, eingeteilt nach ihrer Funktionalität. An erster Stelle innerhalb des Skriptes sollten sich diejenigen Informationen befinden, die Auskunft über
38
die Flashdatei geben. Sie sollten mindestens umfassen:
• Name der aktuellen Datei. Die Namensangabe er-
Kapitel 5 Framework und Codeorganisation
- Beschreibung:
Hauptmenü in navFrame.htm; http:// www.brightfuture.info/htm/
folgt natürlich vollständig, d. h. zuzüglich der Dateiendung, da sich der Code ja nicht nur in einer fla, sondern auch beispielsweise in einer as-Datei befinden kann. Diese Angabe bezieht sich auf die editierbare Version der Datei. • Name des zugehörigen Projekts. • Verwendungszweck. Möglich sind beispielsweise CD/DVD (Standalone) oder Einsatz im Rahmen einer Webseite, wobei die URL mit anzugeben ist. Hier kann es Sinn machen, eine andere Datei mit anzugeben, von der die aktuelle Flashdatei abhängig ist, etwa wenn ein Menü erstellt wurde, das Sie später in eine html-Seite einbinden. Handelt es sich um eine as-Datei innerhalb einer Code-Bibliothek, entfällt dieser Punkt, da ja der Sinn einer derartigen Bibliothek eben in der Wiederverwendbarkeit besteht. • Kurzbeschreibung. Sie erläutert knapp, welchem Zweck die aktuelle Datei dient. • Autor oder Autoren. Wie im Fall der Dateinamen gilt hier, dass der vollständige, richtige Name angegeben wird. In einem Team mag es zwar momentan lustig klingen, wenn man als Autor „Skriptgott“ angibt, aber weiß man nach einem halben Jahr noch, wer damit gemeint war? • Versionen. Die Versionierung sollte auch im Dateinamen dokumentiert werden, etwa in der Form menuev100, menuev101, menuev200 etc.. In der finalen Version, also derjenigen Version, die unverändert im fertigen Projekt eingesetzt wird, entfernt man das Versionskürzel. Die zuletzt versionierte Datei ist damit logischerweise identisch mit der finalen Datei. Wer mag, kann sie noch mit einem zusätzlichen Suffix wie „fin“ versehen. Zur Versionierungsangabe gehören auch das Änderungsdatum sowie eine Kurzbeschreibung der durchgeführten Modifikationen.
- Zweck:
Solche Meta-Informationen könnten in einer extrem kurzen Form folgendermaßen aussehen:
//-------------- vars –---------------
/* –-------------- Datei –------------
var mRoot:MovieClip = this;
- Film:
menue.fla
- Projekt
Relaunch BrightFuture
enthält Hauptnavigation, lädt Unterseiten html bzw. swf - Autor:
Arno Kohl
- Versionen:
v100: 14.12.06
v101: 15.12.06 korrigierte Pfadangabe Laden externer swf Menüpunkte v200: 17.12.06 Funktionen Navi in dynmenue.as ausgelagert; final. */
Selbstverständlich müssen diese Informationen als mehrzeiliger Kommentar eingefügt werden, da Flash sie sonst als Befehlszeilen interpretiert und dementsprechend empört Fehlermeldungen ausspuckt. An nächster Stelle stehen alle Zugriffe auf externe Skript-Elemente, die für die weitere Ausführung benötigt werden. Dazu gehören Skripte, die per includeAnweisung geladen werden, was bei der Mehrfachverwendung von Code Sinn macht, sowie der Zugriff auf Klassen. Dazu ein Beispiel: //---------- import, include –-------import flash.display.*;
#include „animation.as“
Damit würden alle Klassen des Display-Pakets und die externe Skriptdatei animation.as geladen. Beachten Sie, dass die include-Zeile mit einem Sonderzeichen (Raute #) eingeleitet und nicht mit einem Semikolon abgeschlossen wird. Die zu ladende Datei muss als String, also innerhalb von Anführungszeichen, übergeben werden. Anschließend erfolgt die Deklaration und gegebenenfalls Initialisierung von Variablen und Arrays: //Zeitleiste //startlevel
var nLevel:Number; //spieler
var sSpieler:String;
5.3 Aufbau eines Skripts
Nicht in jedem Fall ist es notwendig oder möglich, anfangs eine Variable mit dem entsprechenden Wert zu belegen. In dem Fall sollte man sie lediglich deklarieren, also benennen und den Datentyp festlegen. Die Initialisierung, also die Wertzuweisung, kann dann zu einem späteren Zeitpunkt erfolgen. Eine derartige Vorgehensweise setzt voraus, dass die hier angelegten Variablen später funktionsübergreifend bekannt sein müssen. Wenn Sie statt dessen lediglich lokale Variablen innerhalb einer Funktion benötigen, sollten sie auch nur dort angelegt werden. Falls Sie aus Gründen der Übersichtlichkeit doch lieber einen derartigen Variablen-Block am Anfang des Skripts anlegen und konkrete Inhalte erst später zuweisen, dann empfiehlt es sich, nicht mehr benötigte Variablen zu löschen, um belegten Speicherplatz frei zu geben. Etwas anders gestaltet sich der Aufbau, wenn man sehr viel mit Objektvariablen arbeitet. In dem Fall muss natürlich zuerst das Objekt eingerichtet werden, bevor man ihm Variablen und Arrays zuweisen kann. Leider geht unter Flash dann die Möglichkeit, den Datentyp festzulegen, verloren. Die Initialisierung von Variablen würde dann nach der Einrichtung des Objekts ausgeführt. Prinzipiell spielt die Reihenfolge der Deklarationen innerhalb dieses Blockes keine Rolle, solange die Variablen nicht aufeinander bezogen sind. Es kann jedoch nicht schaden, etwa nach Datentypen oder nach Ablauflogik der Applikation vorzugehen. Im Rahmen unserer Workshops werden Sie sehen, das wir uns des Öfteren an folgender durch den zugewiesenen Datentyp vorgegebenen Reihenfolge orientieren: MovieClip, BitmapData, Number, String, Array und der ganze Rest. Das hat den Vorteil, dass man in einem sehr langen Skript von vorne herein ungefähr weiß, an welcher Stelle man beispielsweise die Deklarationen der MovieClip-Variablen finden kann. Danach werden alle Funktionen definiert, gegebenenfalls einschließlich der Ereigniszuweisungen, die ja Funktionsdeklarationen ohne Bezeichner darstellen können. Die Ereignisse greifen ihrerseits oft auf bestehende Funktionen zu, die natürlich ebenfalls in diesem Block definiert werden müssen. Dabei spielt es übrigens i. d. R. keine Rolle, wo dies geschieht, also vor oder nach der Ereigniszuweisung. Denn bevor Flash irgendwelchen Code ausführt, wird er komplett in den Arbeitsspeicher geladen. Damit stehen den Ereignissen auf jeden Fall die an anderer Stelle dekla-
39
rierten Funktionen zur Verfügung. Dieser Codeblock stellt i. d. R. den wichtigsten Bereich dar, denn hier werden die Aktionen des Programms und die Interaktionsmöglichkeiten des Users festgelegt. Dazu ein fiktives Beispiel: //------------- function –------------//Spielerfigur wird auf Bühne eingefügt
function einfuegeSpieler(pWo:MovieClip, pWelcher:String):Void{ var mSpieler = pWo. attachMovie(pWelcher,„spieler“, pWo. getNextHighestDepth()); mSpieler._x = Stage.width/2;
mSpieler._y = Stage.height/2; }
//Startfunktion legt Startwerte für Spiel fest function init(){ nLevel = 1; }
//Auswahlbutton weist Spieler Spielfigur zu btStart.onPress = function(){
einfuegeSpieler(mRoot,„rot“); }
Es kann durchaus sinnvoll sein, Ereigniszuweisungen in der init()-Funktion festzulegen anstatt sie unabhängig davon im Funktionsblock aufzuführen. In unserem Beispiel ergäbe sich dann folgende Änderung: //-------------- functions –----------//Spielerfigur wird auf Bühne eingefügt
function einfuegeSpieler(pWo:MovieClip, pWelcher:String):Void{ var mSpieler = pWo. attachMovie(pWelcher,„spieler“, pWo. getNextHighestDepth()); mSpieler._x = Stage.width/2;
mSpieler._y = Stage.height/2; }
//Startfunktion legt Startwerte für Spiel fest function init(){ nLevel = 1;
40
//Auswahlbutton weist Spieler Spielfigur zu btStart.onPress = function(){
einfuegeSpieler(mRoot,„rot“); } }
Der Inhalt bleibt derselbe, nur die Position des KlickEreignisses innerhalb des Codes ändert sich. Jetzt wird es erst nach Aufruf der init()-Funktion definiert. Die Aufgabe dieser Funktion besteht darin, alle vorbereitenden Arbeiten auszuführen, also z. B..:
• Variablen soweit wie möglich konkrete Inhalte zu-
weisen, bevor der Anwender mit der Applikation interagiert. • Benötigte Elemente aufrufen (i. S. von Positionieren und notwendige Aktionen auslösen wie z. B. eine Intro-Animation oder ein externer Ladevorgang). • Benötigte Eigenschaften initialisieren (z. B. die Deckkraft eines Elements auf 0 reduzieren, um es anschließend sukzessive einfaden zu lassen). • Gegebenenfalls den vorhandenen Elementen ihre Funktionalität zuweisen.
Kapitel 5 Framework und Codeorganisation
Manche Entwickler fügen zwischen den Blöcken „functions“ und „start“ noch einen eigenen Ereignis-Block ein, in dem dann das erwähnte onPressEreignis von btStart definiert werden müsste. In dem Fall würde das Ereignis natürlich nicht in die init()-Funktion mit übernommen werden. Wir verzichten jedoch auf diese Form und wählen die oben vorgestellte Struktur, da Ereigniszuweisungen der Definition anonymer Funktionen entsprechen und daher in dem Funktions-Block behandelt werden können. Beachten Sie, dass in den vorliegenden Code-Beispielen aus Gründen der Vereinfachung darauf verzichtet wird, Variablen, die nicht mehr benötigt werden, zu löschen, um Speicher freizugeben (falls es sich nicht um lokale Variablen handelt, die automatisch entfernt werden, vorausgesetzt, sie wurden nicht auf der Hauptzeitleiste eingerichtet). Hier noch mal die gesamte Codestruktur im Überblick: /* Meta-Informationen */
// Import und Include von Klassen und externen Skripten
// Deklaration und gegebenenfalls Initialisierung von Variablen
Ganz zum Schluss wird dann, falls vorhanden, die init()-Funktion aufgerufen, die den Anfangszustand des Films definiert.
// Funktionensdeklarationen
//-------------- start –---------------
Dieses Framework verwendet die AS-Version 2.0 sowie einen prozeduralen Skriptaufbau. Objektorientierte Programmierung bzw. AS 3.0 geht anders vor und ist nicht Gegenstand dieser Einführung.
init();
// Aufruf der init-Funktion und gegebenenfalls weiterer Funktionen
6
Operatoren
Operatoren sind Symbole, mit deren Hilfe Daten und Ausdrücke, also die sogenannten Operanden, verändert werden können. ActionScript kennt zahlreiche Operatoren, von denen uns einige wie die Addition (+) und Subtraktion (-) noch aus der Schulzeit geläufig sind. Diese gehören zu den sogenannten arithmetischen Operatoren, da sie es uns ermöglichen, Rechenoperationen mit Zahlen durchzuführen. In Abbildung 10 sehen Sie zwei Beispiele für die Veränderung von Operanden durch Operatoren. Ihre Anweisung muss nicht notwendigerweise nur einen einzigen Operator fassen, wie das zweite Beispiel in der Abbildung zeigt. Prinzipiell gibt es keine Obergrenze für die Anzahl, solange sich die Anweisung in einer einzelnen Befehlszeile schreiben lässt. Obwohl sich die Operatoren in ihrer Wirkungsweise sehr deutlich voneinander unterscheiden, gibt es einige Gemeinsamkeiten. Allen gemeinsam ist ihr geselliges Wesen, was dazu führt, dass sie notwendigerweise immer mit Operanden auftreten. Die Zahl der Operanden kann sich dabei voneinander unterscheiden, mitunter wird sie sogar verwendet, um die Operanden zu kategorisieren. Wenn ein Operator nur einen Operanden benötigt, spricht man von unären Operatoren. Wer schon etwas
Programmiererfahrung besitzt, dürfte direkt an das häufig verwendete Inkrementieren denken: nZahl++. Dabei wird die Variable nZahl um 1 erhöht. Bei zwei Operanden spricht man von binären Operatoren. Auch sie treten sehr häufig auf, etwa in Form der oben erwähnten Addition: nZahl + 5. Einige wenige Operatoren benötigen sogar mindestens drei Operanden. Man bezeichnet sie als ternäre Operatoren. Ein Beispiel wäre der Konditionaloperator ?:, der im letzten Abschnitt behandelt wird. Oft kommt es vor, dass in einem Ausdruck gleich mehrere Operatoren auftreten. Daher muss es eine Reihenfolge geben, in der sie verwendet werden. Ein simples, wiederum noch dunkel aus der Schulzeit bekanntes Beispiel wäre der Merksatz „Punktrechnung kommt vor Strichrechnung“. Dementsprechend ergibt 15 + 20 * 3 den Wert 75, während ein einfaches Abarbeiten in der Reihenfolge des Auftretens der Operanden den Wert 105 ergibt. Es erfolgt also zuerst die Multiplikation, zu deren Ergebnis dann der vor diesem Ausdruck genannte Operand addiert wird. Jeder Operator besitzt also automatisch einen Vorrang, der festlegt, mit welcher Priorität er gegenüber einem anderen Operator ausgeführt werden soll. Falls gewünscht, können wir auf diesen Vorrang Einfluss nehmen durch einen weiteren Operator, nämlich runde Klammern (): nErgebnis = (15 + 20) * 3;
Abbildung 10: Operanden und Operatoren
In diesem Fall legen wir fest, dass der Additionsoperator, zuerst zum Zuge kommen soll, obwohl er einen niedrigeren Vorrang besitzt als der Multiplikationsoperator. In komplexen Ausdrücken, die mehrere verschiedene Operatoren umfassen, kann es sogar sinnvoll sein, explizit mit einer derartigen Klammerung Blöcke zu kennzeichnen, um die Lesbarkeit zu erhö-
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
41
42
hen (natürlich vorausgesetzt, dass Sie nicht ungewollt den Vorrang fehlerhaft ändern). Auch zwischen Operatoren mit gleichem Vorrang existiert eine Reihenfolge, die sogenannte Assoziativität. Sie legt fest, ob die Operatoren in Leserichtung (Links-Assoziativität) oder entgegen der Leserichtung (Rechts-Assoziativität) ausgeführt werden. Die arithmetischen Operatoren beispielsweise für Addition und Multiplikation werden von links nach rechts, der Zuweisungsoperator dagegen von rechts nach links ausgeführt: nErgebnis = 4 + 5 + 6;
nAddition1 = nAddition2 = nErgebnis;
Zunächst addiert Flash die Zahlen 4 und 5. Dann wird die Summe um den Wert 6 erhöht. Abschließend weist Flash das Ergebnis der entsprechenden Variablen zu. Innerhalb der Addition werden die Berechnungen also von links nach rechts ausgeführt. Im zweiten Beispiel weist Flash den Inhalt von nErgebnis der Variablen nAddition2 und diesen der Variablen nAddition1 zu. Die Arbeitsschritte erfolgen von rechts nach links.
6.1 Zuweisungsoperator Von grundlegender Bedeutung für jede Skript- und Programmiersprache ist der Zuweisungsoperator, also das so bescheiden daher kommende Zeichen „=“. Er weist einer Variablen, einem Array oder einem Objekt einen Wert oder einen Ausdruck zu, z. B.: nZahl = 5;
Die Variable nZahl erhält den Wert 5. Egal, welchen Inhalt sie vor dieser Operation hatte, enthält sie nun den neuen Wert. Dabei gilt allgemein: Der Wert oder der Ausdruck, der auf der rechten Seite des Zeichens = steht, wird dem Element auf der linken Seite zugewiesen. Weitere Beispiele sind: aNamen[3] = „Hans“;
Im Array aNamen wird in die Indexposition 3 (also in die vierte Stelle – s. Kap. 9: Arrays) der Name „Hans“ geschrieben. nZahl1 = 5;
nZahl2 = 10;
Kapitel 6 Operatoren
nErgebnis = nZahl1 + nZahl2;
In der dritten Zeile wird dem Operanden auf der linken Seite nicht einfach ein Wert, sondern ein kompletter Ausdruck, nämlich eine Addition zugewiesen, so dass nErgebnis den Wert 15 erhält. Die Ausdrücke können dabei beliebig komplex sein, solange sie sich als einzelne Befehlszeile schreiben lassen. Mit dem Schlüsselwort function() lassen sich sogar ganze Befehlsblöcke zuweisen, die aus zahlreichen Befehlszeilen bestehen können. Selbst Mehrfachzuweisungen sind, wie wir bereits oben gesehen haben, möglich: nZahl1 = nZahl2 = nZahl3 = 5;
Diese Schreibweise ist identisch mit folgender Notation: nZahl3 = 5;
nZahl2 = nZahl3; nZahl1 = nZahl2;
Wer noch keine Skript- oder Programmiersprachen kennt, muss sich eventuell erst an die Begrifflichkeiten gewöhnen. Denn wir bezeichnen in der Umgangssprache das „=“ als „gleich“, z. B. „nZahl1 ist gleich nZahl2“ (nZahl1 = nZahl2). Tatsächlich ist jedoch eine Zuweisung gemeint, während das Gleichheitszeichen als „= =“ oder gar „= = =“ dargestellt wird (s. u. Vergleichsoperatoren). ActionScript bietet angenehmerweise eine Kurzform für Zuweisungen an, die mit einer arithmetischen oder Bit-Operation (Ausnahme: Komplement ~) verbunden sind. Sie werden am Ende des nachfolgenden Abschnitts bezüglich arithmetischer Operatoren näher erläutert.
6.2 Arithmetische Operatoren Arithmetische Operatoren führen numerische Berechnungen durch. In der simpelsten Form entsprechen sie den Grundrechenarten aus unserer Schulzeit. Ihre Operanden bestehen notgedrungen aus Zahlen, also dem Datentyp Number. In AS 3.0 existieren zusätzlich die numerischen Datentypen Uint und Int. Folgende Tabelle gibt einen Überblick über diese Operatoren.
6.3 Inkrement-/Dekrementoperator
43
Kurzform zugreifen, die als kombinierte Zuweisung bezeichnet wird. So ist:
Operator
Bezeichnung
Assoziativität
+
Addition
links rechts
Subtraktion
links rechts
nZahl1 = nZahl1 + nZahl2;
Multiplikation
links rechts
identisch mit:
Division
links rechts
Modulo
links rechts
nZahl1 += nZahl2;
* / %
Arithmetische Operatoren
Zu ihrer Verwendung muss wohl nicht viel gesagt werden, erfolgt sie doch prinzipiell in derselben Weise, wie wir sie noch aus unserer Schulzeit gewohnt sind. So lässt sich eine simple Addition schreiben als: nAddition = 5 + 4;
Das gleiche gilt natürlich für Subtraktion, Multiplikation und Division. Im Zeitalter der Kundenkarten mag man beim Zeichen % an irgendwelche Sonderkonditionen denken, doch wäre diese Assoziation in unserem Zusammenhang falsch. Gemeint ist nämlich der Modulo-Operator, der jeweils den Restwert einer Division zurück gibt, z. B.: nErgebnis = 5 % 4;
Die Berechnung ergibt 1, da wir die 4 einmal in die 5 hineinpacken können und dabei die 1 übrig bleibt. Dementsprechend erhalten wir bei: nErgebnis = 8 % 4;
als Ergebnis 0, denn wenn wir 8 durch 4 dividieren, bleibt 0 übrig. Dieser Operator, so merkwürdig er auch zunächst erscheinen mag, kann höchst nützlich sein. So ermöglicht er es, sehr einfach heraus zu finden, ob ein Wert eine gerade Zahl darstellt oder nicht. Ergibt die Division durch 2 einen Restwert von 0, liegt eine gerade Zahl vor, andernfalls nicht. Wie alle anderen arithmetischen Operatoren auch, kann Modulo mit Ganz- und mit Fließkommazahlen verwendet werden. Unter einer Ganzzahl versteht man einen Wert ohne Kommastelle (z. B. 5, –396), während eine Fließkommazahl über Werte hinter der Kommastelle verfügt (z. B. 3.14, –225.778). Beachten Sie, dass die Kommastelle entgegen der deutschen Bezeichnung durch einen Punkt gekennzeichnet wird. Im Englischen spricht man von einer float oder floating point. Bei einfachen numerischen Berechnungen, die mit einer Zuweisung verbunden sind, kann man auf eine
Nehmen wir an, nZahl1 enthalte den Wert 10, nZahl2 den Wert 5, dann erhöhen wir in beiden Fällen nZahl1 auf 15. Entsprechend kann man natürlich mit den übrigen Rechenarten verfahren (vgl. die Auflistung in der Tabelle). Zuweisung nZahl1 = nZahl1 + nZahl2
Kombinierte Zuweisung
nZahl1 += nZahl2
nZahl1 = nZahl1 – nZahl2
nZahl1 –= nZahl2
nZahl1 = nZahl1 / nZahl2
nZahl1 /= nZahl2
nZahl1 = nZahl1 * nZahl2
nZahl1 *= nZahl2
nZahl1 = nZahl1 % nZahl2
nZahl1 %= nZahl2
Kurzform Zuweisungsoperator
6.3 Inkrement-/Dekrementoperator Es kommt durchaus häufig vor, dass wir eine Variable jeweils um 1 hoch zählen wollen. Das ist insbesondere dann der Fall, wenn wir Befehle mit Hilfe einer Schleife mehrfach ausführen, etwa um eine Bildergalerie aufzubauen. Wie andere Sprachen auch bietet Flash dafür einen eigenen Operator, den InkrementOperator, an: nZahl = 1; nZahl++;
trace(nZahl);
Ausgabe im Nachrichtenfenster: 2 Zunächst weisen wir der Variablen nZahl den Wert 1 zu. Dann inkrementieren wir sie und lassen uns ihren neuen Wert ausgeben, der 2 entspricht. Die beiden nachgestellten Zeichen ++ sind identisch mit:
44
nZahl += 1;
bzw.: nZahl = nZahl + 1;
Achten Sie bei der Verwendung dieses Operators darauf, kein Leerzeichen zwischen den beiden + einzufügen. Ansonsten geht Flash davon aus, dass sie zweimal den Additions- bzw. Konkatenationsoperator einsetzen wollten, was in dieser Form aber syntaktisch falsch wäre, da zwischen beiden ein Operand fehlt. Sie erhalten dann eine Fehlermeldung. Das Pendant zum Inkrement stellt das Dekrement dar, bei dem jeweils eine 1 subtrahiert wird: nZahl = 1; nZahl--;
trace(nZahl);
Ausgabe im Nachrichtenfenster: 0 Da wir in beiden Beispielen den Operator dem Operanden nachgestellt haben, spricht man von einem Post-Inkrement bzw. Post-Dekrement. Eine Alternative dazu wäre das Prä-Inkrement bzw. Prä-Dekrement, bei dem zuerst der Operator, gefolgt vom zugehörigen Operanden auftritt:
Kapitel 6 Operatoren
In der zweiten Zeile inkrementieren wir die Variable. Da es sich jedoch um ein Post-Inkrement handelt, gibt Flash mit der trace()-Anweisung zuerst nur den vorher gültigen Wert, also die 1, zurück. Erst dann wird die Addition ausgeführt, so dass wir in der letzten Zeile den neuen, um 1 erhöhten Wert erhalten. Umgekehrt läuft die Berechnung im alternativen Fall ab: nZahl = 1;
trace(++nZahl); trace(nZahl);
Ausgabe im Nachrichtenfenster: 2 2 Durch das Prä-Inkrement addiert Flash zuerst eine 1 und gibt dann schon in der zweiten Zeile den neuen Wert zurück, also eine Zeile früher als im vorhergehenden Beispiel. Das gleiche funktioniert natürlich auch mit Dekrement. Am häufigsten verwendet man Post-Inkrement und Post-Dekrement, aber in den meisten Fällen ist es schlicht Geschmackssache, welcher Variante Sie den Vorzug geben wollen.
nZahl = 1;
6.4 Stringoperatoren
trace(nZahl);
Das Pluszeichen eignet sich nicht nur für Berechnungen mit Zahlen, sondern kann auch verwendet werden, um Strings, also beliebige Zeichenfolgen, miteinander zu verknüpfen. In dem Fall spricht man von einer Konkatenation bzw. dem Konkatenationsoperator. Beispielsweise ergibt der Befehl trace(„a“+„b“+„c“) im Nachrichtenfenster „abc“. Die Zeichen „a“, „b“ und „c“ werden einfach aneinander gehängt. Anstelle von Zeichen können natürlich auch Variablen, die Strings enthalten, auf die gleiche Weise miteinander verknüpft werden:
++nZahl;
Ausgabe im Nachrichtenfenster: 2 Das Ergebnis entspricht zunächst wenig überraschend demjenigen des Post-Inkrements. Tatsächlich läuft jedoch der Additions-Vorgang anders ab: Beim PostInkrement verwendet Flash zuerst die Variable mit ihrem alten Wert und erhöht ihn dann, während das Prä-Inkrement zuerst die Addition ausführt und dann das Ergebnis zurück gibt. Dieser vielleicht kryptisch anmutende Satz möchte Ihnen nur folgendes vermitteln: nZahl = 1;
trace(nZahl++); trace(nZahl);
Ausgabe im Nachrichtenfenster: 1 2
sVorname = „Zaphod“;
sNachname = „Beeblebrox“;
sName = sVorname + sNachname; trace(sName);
Im Nachrichtenfenster zeigt Flash „ZaphodBeeblebrox“. Das fehlende Leerzeichen bei der Ausgabe lässt sich leicht einfügen, indem man die Zuweisung von sName entsprechend ändert:
6.4 Stringoperatoren
sName = sVorname + „ “ + sNachname;
Nun ergibt der trace()-Befehl das lesbarere Ergebnis: „Zaphod Beeblebrox“. Mitunter ist es notwendig, den Konkatenationsund den Additionsoperator gleichzeitig zu verwenden, etwa wenn man einen Highscore in einem Spiel berechnen und ausgeben möchte. Dann muss Flash explizit mitgeteilt werden, welche Funktion das Pluszeichen übernehmen soll. Angenommen, wir wollen in einem Textfeld den aktuellen Punktestand zuzüglich von Bonuspunkte ausgeben:
45
cherweise nichts und Flash verwertet den kompletten Ausdruck wieder als String: tPunkte.text = „500“;
nPunkte = tPunkte.text; nBonus = 50;
tHighscore.text = „Punkte: “ + (nPunkte + nBonus);
Um die jetzt fehlerhafte Ausgabe von „50050“ zu korrigieren, müsste man entweder innerhalb der Klammer eine temporäre Typumwandlung vornehmen:
nPunkte = 500;
tHighscore.text = „Punkte: “ + (Number(nPunkte) + nBonus);
tHighscore.text = nPunkte + nBonus + „ Punkte“;
Oder man definiert alternativ bereits nPunkte bei der Initialisierung mit dem gewünschten Datentyp:
Im Textfeld erscheint erwartungsgemäß: „550 Punkte“. Die Werte in den beiden Variablen nPunkte und nBonus werden als Zahlen addiert und anschließend mit dem String „ Punkte“ verknüpft. Flash erkennt automatisch „ Punkte“ als String und verwendet den Konkatenationsoperator anstelle des zuvor benutzten Additionsoperators. Ein ganz anderes Ergebnis ergibt sich jedoch, wenn wir die Reihenfolge umkehren:
nPunkte = Number(tPunkte.text);
nBonus = 50;
tHighscore.text = „Punkte: “ + nPunkte + nBonus;
Nun zeigt das Textfeld: „Punkte: 50050“. Für einen Spieler mag das ein schmeichelhafter Punktestand sein, aber er entspricht nicht dem aus Programmiersicht erhofften Ergebnis. Sobald Flash auf den Konkatenationsoperator stößt, wird jedes weitere Auftreten des Pluszeichens in der gleichen Weise interpretiert, selbst dann, wenn als Datentyp Zahlen anstatt Strings auftreten. Flash führt eine temporäre Typumwandlung durch, so dass aus einer Zahl ein String wird. Um trotzdem eine korrekte Berechnung zu erhalten, muss der Teilausdruck, der die Zahlen umfasst, geklammert werden:
Bei beiden Beispielen gehen wir von der einfachen Annahme aus, dass sich im Textfeld ein String befindet, der ohne Weiteres in eine gültige Zahl umgewandelt werden kann. Wir erhalten jeweils wieder die gewünschte Angabe: „550 Punkte“. Die naheliegende Überlegung, zu einer Konkatenation mit dem Pluszeichen müsse es auch eine Subtraktion mit dem Minuszeichen geben, trifft (leider) nicht zu. Denn das Minuszeichen wird immer als arithmetischer Operator gewertet und erwartet zwei Zahlen als Operatoren. Liegt auch nur in einem Fall ein anderer Datentyp vor, gibt ActionScript NaN zurück: sString1 = „Zaphod“; sString2 = „aphod“;
trace(sString1 – sString2);
Im Nachrichtenfenster erscheint ein NaN, d. h. „Not a Number“, so dass keine Berechnung erfolgen kann. Lassen sich die verwendeten Operanden allerdings korrekt (temporär) in den Datentyp Number umwandeln, dann nimmt Flash sie automatisch vor und führt eine Berechnung durch:
tHighscore.text = „Punkte: “ + (nPunkte + nBonus);
sPunkte = „500“;
Jetzt entspricht die Textausgabe wieder der anfangs erzielten Variante: „550 Punkte“. Sollte allerdings mindestens eine der Variablen nPunkte und nBonus einen String enthalten – etwa weil ihr Inhalt aus einem anderen Textfeld ausgelesen wurde –, dann nützt auch die Klammerung verständli-
trace(sPunkte – nBonus);
nBonus = 50;
Ausgabe im Nachrichtenfenster: 450 Aus dem String „500“ extrahiert Flash per Typumwandlung eine korrekte Zahl, mit der eine mathemati-
46
Kapitel 6 Operatoren
sche Operation erfolgen kann. So bequem diese Vorgehensweise sein mag, so unsauber ist sie auch. Denn derartiger Code erweist sich als schwer lesbar. Wenn Sie also explizit eine Berechnung durchführen wollen, dann sollten Sie Flash auch mitteilen, dass eine temporäre Typumwandlung gewünscht wird, indem Ihr String als Number zu verwenden ist. Korrekter würde dann der vorhergehende Code lauten:
also wahr oder falsch sind. Folgende Tabelle gibt einen Überblick über derartige Operatoren. In der einfachsten Form können wir mit dem trace()-Befehl einen Vergleich durchführen:
sPunkte = „500“;
Wir weisen Flash an, zu ermitteln, ob die Zahl 3 kleiner ist als die Zahl 5. Da dies zutrifft, erhalten wir als Ergebnis true. Machen Sie die Gegenprobe:
nBonus = 50;
trace(Number(sPunkte) – nBonus);
Auch das sukzessive Erweitern eines Strings ist möglich: sText = „abc“; sText += „d“; trace(sText);
An den ursprünglich definierten String „abc“ wird der Buchstabe „d“ gehängt. Daraus lässt sich beispielsweise ein Schreibmaschineneffekt basteln, bei dem im gleichen zeitlichen Abstand immer mehr Buchstaben auftauchen. Zwar kennt ActionScript noch einige andere String-Operatoren wie add, eq etc. doch gelten diese als veraltet und sollten nicht mehr verwendet werden.
trace(3 < 5);
Ausgabe im Nachrichtenfenster: true
trace(5 < 3);
Ausgabe im Nachrichtenfenster: false Vertauschen wir die Zahlen miteinander, trifft der an trace() übergebene Ausdruck nicht mehr zu, so dass Flash im Nachrichtenfenster false ausgibt. Der Operator < vergleicht also zwei Operanden miteinander, wobei der erste kleiner sein muss als der zweite. So ähnlich funktionieren auch die Operatoren >= und = 2 * 3);
6.5 Vergleichsoperatoren
Ausgabe im Nachrichtenfenster: true
Die Vergleichsoperatoren ermöglichen naheliegenderweise Vergleiche. Verglichen werden Werte oder Ausdrücke miteinander, die immer true oder false,
Wir weisen der Variablen nZahl den Wert 6 zu und vergleichen sie anschließend mit dem Ergebnis der Multiplikation von 2 mit 3. Der trace()-Befehl gibt true zurück, da nZahl mindestens so groß ist wie
Operator
Bezeichnung
Bedeutung
Assoziativität
= == !=
=== !==
strikte Ungleichheit true, wenn Datentyp der Operanden nicht gleich
Vergleichsoperatoren
links rechts
6.5 Vergleichsoperatoren
47
das Ergebnis der Berechnung. Würden wir statt dessen den trace()-Befehl ändern in:
Sie wollten eine Zuweisung anstelle eines Vergleichs ausführen:
trace(nZahl >= 2 * 2);
nZahl = 10;
In dem Fall ist das Ergebnis ebenfalls true, weil nZahl zwar nicht gleich, aber größer als das Ergebnis der Multiplikation ist. Übrigens lassen sich auf diese Weise auch Strings vergleichen, denn sie werden programmintern über einen Zahlencode verwaltet. Beim Vergleich zweier Strings schaut Flash nach, welcher Zahlencode größer oder kleiner ist:
if (nZahl == 20) {
trace(„trifft zu“); }
trace(„nZahl: „+nZahl);
Ausgabe im Nachrichtenfenster: nZahl: 10
Da der Zahlencode der Majuskel „H“ höher ist als derjenige von „B“, gibt der Vergleich false zurück. Verwenden wir statt des Vergleichsoperators < (kleiner als) den Operator > (größer als), erhalten wir dagegen ein true:
Wir speichern in einer Variablen die Zahl 10 und fragen anschließend, ob ihr Wert der Zahl 20 entspricht. Nur wenn das zutrifft, soll Flash im Nachrichtenfenster die Information „trifft zu“ ausgeben. Da das nicht der Fall ist, erfolgt auch diese Ausgabe nicht. Unabhängig davon lassen wir uns am Ende des Skripts den konkreten Inhalt von nZahl anzeigen, der unverändert 10 beträgt. Wenn wir versehentlich den Zuweisungs- statt des Gleichheitsoperators verwenden, sieht die Sache anders aus:
trace(„Hans“ > „Bernd“);
nZahl = 10;
Ausgabe im Nachrichtenfenster: true
trace(„trifft zu“);
trace(„Hans“ < „Bernd“);
Ausgabe im Nachrichtenfenster: false
Eine derartige Operation kann beispielsweise dann sinnvoll sein, wenn Sie Strings, etwa Benutzernamen, in einer bestimmten Weise sortieren möchten. Der Gleichheitsoperator == gibt darüber Auskunft, ob die Werte zweier Operanden miteinander identisch sind: trace(2 * 5 == 4 * 2.5);
Ausgabe im Nachrichtenfenster: true In unserem Fall ergibt die Berechnung auf der linken und auf der rechten Seite des Vergleichsoperators dasselbe Ergebnis, nämlich 10. Dadurch lautet unsere Anweisung: trace(10 == 10);
Da es zutrifft, dass beide Werte miteinander identisch sind, erhalten wir im Nachrichtenfenster das erwähnte true. Für Anfänger erweist sich dieser Operator als etwas gewöhnungsbedürftig und man ist mitunter versucht, an seiner Stelle den Zuweisungsoperator zu verwenden. Das hat jedoch fatale Konsequenzen, weil Flash fälschlicherweise davon ausgeht,
if (nZahl = 20) { }
trace(„nZahl: „+nZahl);
Ausgabe im Nachrichtenfenster: trifft zu nZahl: 20 Flash meint, wir wollten in der zweiten Zeile den Inhalt von nZahl ändern und ersetzt daher den Wert 10 durch 20. Infolgedessen wird nun der trace()-Befehl in der if-Bedingung ausgeführt und wir erhalten zum Schluss als neuen Inhalt von nZahl den Wert 20 ausgegeben. Durch diesen scheinbar kleinen Fehler kann sich der komplette Ablauf eines Skriptes ändern. Leider gibt es keine automatische Möglichkeit, einen solchen fatalen Fehler zu unterbinden bzw. von der Flash-IDE als Fehler anzeigen zu lassen. Ebenfalls zu beachten gilt die Art, in der Werte vorliegen. So werden in Flash Arrays und Objekte per Referenz und nicht einfach nach als Inhalt vorliegenden Werten verglichen. Nehmen wir folgendes Beispiel: aEins = [1, 2, 3]; aZwei = [1, 2, 3];
48
Kapitel 6 Operatoren
if (aEins == aZwei) {
Ausgabe im Nachrichtenfenster: false
}
Der Wert NaN liefert immer false zurück, egal in welcher Form die Abfrage erfolgt. Statt dessen müssen Sie hier die globale Funktion isNaN verwenden:
trace(„gleich“);
Wir richten zwei Arrays ein, die exakt die gleichen Zahlen speichern. Dann fragen wir, ob beide Arrays einander gleich sind. Aus dem beharrlichen Schweigen von Flash nach dem Ausführen des Skripts müssen wir davon ausgehen, dass dem entgegen unserem Augenschein nicht so ist. Zwar sind die Zahlenwerte identisch, aber sie liegen unabhängig voneinander vor. Es besteht keinerlei Verknüpfung zwischen beiden Arrays, sie werden von Flash daher als verschieden bzw. nicht gleich interpretiert. Erst wenn ein Array auf das andere verweist, ändert sich das Ergebnis: aEins = [1, 2, 3]; aZwei = aEins;
if (aEins == aZwei) { trace(„gleich“); }
sZahl = „b5a“
trace(isNaN(sZahl))
Ausgabe im Nachrichtenfenster: true Da es stimmt, dass sich aus dem String sZahl keine gültige Zahl extrahieren lässt, lautet der Rückgabewert true. Wir haben hier in den meisten Beispielen mit einem simplen trace()-Befehl die Vergleiche ausgeführt. Insofern ein Vergleich prinzipiell zu zwei verschiedenen Ergebnissen führen kann, macht er aber am ehesten innerhalb einer if-Bedingung oder einer Schleife Sinn, um den weiteren Ablauf der Anwendung zu steuern.
Ausgabe im Nachrichtenfenster: gleich Das Array aZwei enthält nun eine Referenz auf das Array aEins, die wir mit Hilfe des Zuweisungsoperators herstellen. Wir können zwar momentan keinen Unterschied zur vorherigen Variante erkennen, da Flash, falls wir mit trace() auf die jeweiligen Inhalte zugreifen würden, dieselben Zahlen ausgeben würde. Dennoch verweisen in diesem Fall beide Arrays auf dasselbe Objekt bzw. auf dieselben Inhalte und es wird dementsprechend der abhängige trace()-Befehl ausgeführt. Dass beide Varianten in der Tat nicht identisch miteinander sind und Flash sehr wohl recht hat, sie verschieden zu behandeln, werden wir später im entsprechenden Kapitel über Variablen bzw. Arrays noch sehen. Last not least ist eine Besonderheit zu beachten, wenn Sie herausfinden wollen, ob ein String, der beispielsweise aus einem Eingabetextfeld extrahiert wird, keine gültige Zahl darstellt (z. B., um bei einer fehlerhaften Eingabe eine entsprechende Rückmeldung an den User zu geben). In einem derartigen Fall können Sie nicht mit dem Wert NaN vergleichen. Falsch wäre also: sZahl = „b5a“;
trace(sZahl ==„NaN“);
6.6 Logische Operatoren Die logischen Operatoren versetzen uns in die Lage, Boolsche Werte und/oder mehrere Ausdrücke in Vergleichen zu kombinieren. Flash stellt drei derartige Operatoren zur Verfügung, wie in der Tabelle zu sehen. Operator Bezeichnung Bedeutung
Assoziativität
&&
Logisches Und
true, wenn alle Ope- links rechts randen true ergeben
||
Logisches Oder
true, wenn mindes-
!
Logisches Nicht
true, wenn ein Ope- links rechts rand nicht true ist
tens einer der Operanden true ergibt
links rechts
Logische Operatoren
Die Verwendung des logischen Und ist immer dann sinnvoll, wenn Sie testen wollen, ob mindestens zwei Bedingungen zutreffen. Nehmen wir beispielsweise an, dass nur dann ein besonderer Cursor eingeblendet werden soll, wenn die horizontale Mausposition größer als 50 Pixel und kleiner als der rechte Rand minus
6.6 Logische Operatoren
50 ist – auf diese Weise würde rechts und links ein gleich großer Randabstand von 50 Pixel definiert. Die betreffende Abfrage könnte lauten: if(_xmouse> 50 && _xmouse<Stage. width-50){
Durch den Schlüsselbegriff if weisen wir Flash an, zu kontrollieren, ob der Ausdruck in der Klammer true ergibt. Ist das der Fall, wird der abhängige Code, der hier aus Gründen der Vereinfachung fehlt, ausgeführt, andernfalls nicht. Indem wir in der Klammer ein logisches Und verwenden, müssen beide Ausdrücke (_xmouse > 50 UND _xmouse < Stage.width – 50) zusammen zutreffen, also true ergeben, damit die gesamte Bedingung wahr wird. Wir können uns ein sehr einfaches Beispiel anschauen, bei dem die Mausbewegung kontrolliert wird: this.onMouseMove = function() {
if (_xmouse> 50 && _xmouse<Stage. width-50) { trace(true); } else {
trace(false); } };
Bei jeder Bewegung der Maus schaut Flash nach, ob sie sich horizontal innerhalb der angegebenen Koordinaten befindet (d. h. rechter und linker Rand von jeweils 50 Pixel). Ist das der Fall, erhalten wir im Nachrichtenfenster die Meldung true, ansonsten false. Das logische Oder dagegen wird dann verwendet, wenn wir kontrollieren wollen, ob mindestens eine von mehreren Bedingungen erfüllt ist. Wenn wir z. B. eine Animation skripten, bei der ein Objekt seine Bewegungsrichtung umkehrt, sobald es den linken oder den rechten Rand überschreitet, dann erweist sich der genannte Operator als sinnvoll: if(ball._x < 0 || ball._x > Stage. width){
Unsere Bedingung überprüft, ob die horizontale Position des Objekts mit dem Instanznamen ball kleiner als 0 oder größer als die Gesamtbreite der Bühne bzw. des Screens ist. Trifft das zu, wird der abhängige Code ausgeführt, sonst nicht. Beachten Sie, dass die Bedingung, die ein logisches Oder verwendet, anders als beim logischen Und bereits true ergibt, wenn der
49
erste Ausdruck wahr ist. Flash prüft dann den zweiten bzw. alle folgenden Ausdrücke nicht mehr. Auch wenn wir uns bei den bisherigen Beispielen nur auf zwei Ausdrücke innerhalb der if-Bedingung beschränkt haben, kann ihre Anzahl prinzipiell beliebig groß sein. Das logische Oder enthält eine kleine Stolperfalle, die gerne zu einer fehlerhaften Verknüpfung der zu prüfenden Operanden verführt. So könnte man versucht sein, eine Abkürzung für folgenden Code zu finden: nZahl = 5;
if(nZahl == 4||nZahl == 6){ trace(„true“); }
Wir speichern den Wert 5 in einer Variablen und fragen dann, ob diese Variable den Wert 4 oder 6 enthält. Falls ja, soll im Nachrichtenfenster die Ausgabe „ist gleich“ erscheinen. Da das nicht der Fall ist, schweigt Flash bei der Ausführung korrekterweise. Die erwähnte Abkürzung könnte lauten: nZahl = 5;
if(nZahl == 4||6){ trace(„true“); }
Ausgabe im Nachrichtenfenster: true Anstatt im Vergleich jedes mal nZahl == einzutragen, verwenden wir den Ausdruck einmal, während wir nach dem Oder nur noch den zu prüfenden Wert schreiben. Leider gibt Flash dann – korrekterweise – immer true aus. Denn zunächst wird nZahl == 4 getestet, was false ergibt. Anschließend prüft Flash die Zahl 6 als eigenständigen Ausdruck, nicht jedoch das von uns gemeinte nZahl == 6. Da jede Zahl, die ungleich 0 ist, als Bool’scher Wert true ergibt, erhalten wir als Ergebnis des gesamten Vergleichs innerhalb der runden Klammern true und die abhängige trace()Anweisung wird ausgeführt. Sie können das testen, indem Sie komplett jeden Bezug auf nZahl löschen: if(4||6){
trace(„true“); }
Ausgabe im Nachrichtenfenster: true
50
Kapitel 6 Operatoren
Wenn wir also nZahl testen wollen, gibt es keine Abkürzung: Wir müssen in jedem Ausdruck, der mit einem logischen Operator verknüpft wird, die gewünschte Variable aufführen. Die Auswertung von Vergleichen mit Hilfe der logischen Operatoren Und sowie Oder lassen sich in sogenannten Wahrheitstabellen darstellen, die das Ergebnis in Abhängigkeit des Wahrheitswertes der einzelnen Operatoren enthalten. Folgende beide Tabellen zeigen die Werte für das logische Und sowie das logische Oder.
Wir weisen einer Variablen den Wert true zu. In der Bedingung testen wir, ob diese Variable den Wert false enthält, was momentan jedoch nicht der Fall ist. Daher ignoriert Flash die abhängige trace()Anweisung und gibt uns nur den Befehl in der letzten Zeile aus, in der wir uns den Inhalt der Variablen anzeigen lassen. Eine Änderung der Variablen ändert auch den Programmablauf:
Operand 1
Operand 2
Auswertung
}
true
true
true
trace(schalterEin);
false
true
false
true
false
false
false
false
false
Ausgabe im Nachrichtenfenster: Das Licht ist ausgeschaltet false
Wahrheitstabelle logisches Und
Zu lesen ist eine derartige Tabelle folgendermaßen: Wenn der erste und der zweite Operand true ergeben, erhalten wir für den gesamten per && verknüpften Ausdruck ebenfalls true. Ist dagegen der erste oder der zweite Operand false, ergibt der gesamte Ausdruck false. Das gilt natürlich erst recht für den Fall, dass beide Operanden false sind. Operand 1
Operand 2
Auswertung
true
true
true
false
true
true
true
false
true
false
false
false
Wahrheitstabelle logisches Oder
Erheblich schlichter gestaltet sich die Verwendung des logischen Nicht:
schalterEin = false; if(!schalterEin){
trace(„Das Licht ist ausgeschaltet“);
Da die Variable von Anfang an den Wert false enthält, trifft die if-Bedingung zu und Flash führt die trace()-Anweisung sowohl innerhalb wie außerhalb der Abfrage aus. Dieser Operator ermöglicht es auch, den Bool’schen Wert einer Variablen umzukehren: schalterEin = true;
schalterEin = !schalterEin; trace(schalterEin);
Ausgabe im Nachrichtenfenster: false Wir weisen schalterEins den Wert true zu und kehren sie in der zweiten Zeile in ihr Gegenteil um. Dadurch gibt uns Flash in der trace()-Anweisung der dritten Zeile den Wert false im Nachrichtenfenster aus. Auf diese einfache Weise kann man beispielsweise einen Kippschalter realisieren, also ein Objekt, das genau zwei Zustände kennt. Das funktioniert natürlich nur mit Bool’schen Werten; würde unsere Variable Zahlen, Strings oder andere Datentypen enthalten, wäre eine solche Umkehrung sinnlos.
schalterEin = true; if(!schalterEin){
trace(„Das Licht ist ausgeschaltet“);
6.7 Bit-Operatoren
trace(schalterEin);
Mit den Bit-Operatoren stellt Flash eine Möglichkeit zur Verfügung, um auf der Ebene der Bits Zahlenmanipulationen durchzuführen. Das ist z. B. interessant, um in bestimmten Fällen eine schnellere Ausführung
}
Ausgabe im Nachrichtenfenster: true
6.8 Sonstige
51
Ihres Codes zu erreichen. Allerdings gehören sie zu den fortgeschrittenen Konzepten, die wir an dieser Stelle nicht näher behandeln wollen. Sie werden ihnen ohnehin höchstens in Ausnahmefällen begegnen, die tägliche Arbeit ist davon sicherlich nicht betroffen.
6.8 Sonstige Flash verfügt noch über eine ganze Reihe weiterer Operatoren, von denen Ihnen einige permanent über den Weg laufen werden, andere dagegen fristen eher ein Exotendasein. Die Tabelle vermittelt einen Überblick. Operator
Bedeutung
,
Aufzählung
.
Zugriff auf Objekteigenschaften, Ereignisse, Methoden
()
Gruppierung von Elementen Kennzeichnung von Funktionen/Methoden Spezifizierung von Schleifen und Bedingungen
{}
Definition von Anweisungs-Blöcken Initialisierung von Objekten
[]
Initialisierung von und Zugriff auf Arrays Auswertung eines Ausdrucks zur Referenzierung eines Objekts
new
Instanziierung von Klassen/Objekten
typeof
Ermittlung von Datentypen
instanceof
Ermittlung von Klassenzugehörigkeit
?:
Konditional-Operator, Kurzform für eine Bedingung
Weitere Operatoren
Nachfolgend erhalten Sie einen kurzen Einblick in die genannten Operatoren entsprechend der Reihenfolge in der Tabelle. Einige werden an anderer Stelle ausführlicher behandelt. Schlicht und undramatisch tritt das Komma auf. In gewohnter Weise dient es dazu, Ausdrücke aufzuzählen, die je für sich ausgewertet werden. Beispiele wären: var mClip1:MovieClip, mClip2:MovieClip; for(var i:Number = 0, j:Number = 0; i = 100){
In den ersten beiden Zeilen wird jeweils eine einfache, von Flash zur Verfügung gestellte Anweisung aufgerufen, einmal ohne, einmal mit Parameter (genauer: Argumenten). Die dritte Zeile zeigt eine selbst definierte, parametrisierte Funktion. In den Zeilen 4 und 5 sehen wir eine Schleife sowie eine Bedingung, deren Ausführung jeweils durch den Klammer-Ausdruck spezifiziert wird. Was es im Einzelnen mit derartigen Zeilen auf sich hat, erfahren Sie in den nachfolgenden Kapiteln; hier geht es nur um den prinzipiellen Einsatz des betreffenden Operators. Anders als die übrigen Operatoren kann dieser Operator eine beliebige Anzahl an Operanden umfassen. Funktionsdeklarationen, Schleifen und Bedingungen werden darüber hinaus mit einer geschweiften Klammer {} gekennzeichnet. Sie umfasst jeweils den Anweisungs-Block, der den betreffenden Elementen zugeordnet ist, z. B.: if(this._x >= 800){ this._x = 0; }
Trifft die in der ersten Zeile formulierte Bedingung zu, wird alles ausgeführt, was sich innerhalb der geschweiften Klammern befindet. Die Anzahl der dort enthaltenen Anweisungen kann beliebig groß sein. Wenn ein Objekt initialisiert wird, kann man ebenfalls die geschweifte Klammer verwenden, um seine spezifischen Eigenschaften, Methoden und Ereignisse festzulegen: auto = {raeder:4, tueren:2};
Kapitel 6 Operatoren
Wir legen ein Objekt namens auto an, das über die Eigenschaften raeder mit dem Wert 4 und tueren mit dem Wert 2 verfügt. Um sie abzufragen, würden wir den erwähnten Punkt-Operator verwenden: trace(auto.raeder);
Ausgabe im Nachrichtenfenster: 4 Eingedenk der überragenden Bedeutung von Arrays für Anwendungen wird Ihnen die eckige Klammer oft begegnen, denn sie ermöglicht es einerseits, derartige Arrays einzurichten, und andererseits, auf die in ihnen enthaltenen Elemente zuzugreifen. An dieser Stelle mögen zwei einfache Beispiele genügen: aAutomarken = [„Audi“,„VW“,„Porsche“, „Renault“]; trace(aAutomarken[0]);
Ausgabe im Nachrichtenfenster: Audi Wir richten ein Array, also eine Variable, die beliebig viele Inhalte aufnehmen kann, ein, wobei vier Strings mit den Bezeichnungen für beliebige Automarken übergeben werden. Anschließend greifen wir mit der trace()-Anweisung auf das Element an erster Stelle bzw. auf dem Index 0 zu, das Flash im Nachrichtenfenster anzeigt. Nicht selten kommt es vor, dass wir dynamisch Objekte erstellen müssen und daher nicht von vorne herein wissen, wie ihr konkreter Name lautet. In dem Fall hilft uns der genannte Klammeroperator, aus einem zusammengesetzten Namen das gewünschte Objekt zu ermitteln: for(var i:Number = 1; i 10 && nZahl10 && nZahl50) {
trace(„X-Position >= 50“); } else if (nPos >=10) {
trace(„X-Position >= 10“); }
Ausgabe im Nachrichtenfenster: X-Position >= 50 Um nachher einfacher darauf zugreifen zu können, speichern wir die aktuelle x-Position der Instanz kreis in der Variablen nPos. Anschließend testen wir, ob diese Variable größer als 50 oder größer/gleich 10 ist. Da sich der Kreis auf der Position 150 befindet, trifft beides zu. Wir möchten jedoch für die Objekte, deren x-Position zwischen 10 und 50 liegt, eine andere Aktion auslösen als für diejenigen Objekte, die sich jenseits von x = 50 befinden. Daher legen wir in der Abfrage durch else fest, dass nur eine der beiden Bedingungen als gültig angesehen wird. Da zuerst die if-Bedingung zutrifft (150, der Inhalt von nPos, ist größer als 50), wird die zugehörige Anweisung ausgeführt und der else-Fall ignoriert. Anders verhält es sich, wenn wir die Bedingungen mit Hilfe einfacher if-Anweisungen festlegen.
101
11.1.5 switch, break Wie erwähnt, können wir prinzipiell endlos viele ifAnweisungen hintereinander ausführen. Unser Code wird aber damit sehr schnell unübersichtlich. Glücklicherweise bietet AS mit der switch-Anweisung die Möglichkeit einer Vereinfachung an. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var sJahreszeit:String = „Herbst“; switch (sJahreszeit) { case „Frühling“ :
trace(„Wohl temperiert, Blumen blühen, selten über 30 Grad“); break;
case „Sommer“ :
trace(„So fühlt sich ein Frühstücksbrötchen beim Aufbacken“); break;
case „Herbst“ :
trace(„Umgekehrter Frühling, beginnt neuerdings erst Ende November“); break;
case „Winter“ :
if (nPos>50) {
trace(„Alten Sagen zufolge soll es früher wochenlang gefrorenes Wasser, ‚Schnee‘ genannt, geregnet haben“);
}
default :
trace(„X-Position >= 10“);
break;
9. Ergänzen Sie den Code um folgende Zeilen: trace(„X-Position >= 50“);
break;
if (nPos>=10) {
trace(„Die fünfte Jahreszeit“);
}
}
Ausgabe im Nachrichtenfenster: X-Position >= 50 X-Position >= 10
Ausgabe im Nachrichtenfenster: Umgekehrter Frühling, beginnt neuerdings erst Ende November
Zwischen beiden if-Bedingungen besteht keine Verknüpfung, sie werden also unabhängig voneinander ausgeführt. Da 150 sowohl größer als 50 wie auch größer als 10 ist, treffen beide Bedingungen zu und Flash führt zweimal einen trace()-Befehl aus. Auf diese Weise ist es nicht möglich, im konkreten Fall eine einzige, eindeutige Unterscheidung zu treffen.
Wir initialisieren die Variable sJahreszeit mit dem String „Herbst“. Danach testen wir ihren Inhalt mit Hilfe der switch-Anweisung. Da nur der Fall zutrifft, der unter case: „Herbst“ definiert wird, gibt Flash den String „Umgekehrter Frühling …“ aus. Beachten Sie, dass der Anweisungsblock innerhalb von jedem case-Fall mit einem break-Befehl
102
Kapitel 11 Kontrollstrukturen
schließen muss. Dieser Befehl führt dazu, dass die switch-Anweisung direkt abgebrochen wird. Wenn Sie auf break verzichten, werden alle nachfolgenden Anweisungen automatisch mit ausgeführt.
}
3. Kommentieren Sie testweise alle break-Anweisungen aus (Fettdruck):
}
//break;
Wie Sie sehen, erspart uns die switch-Anweisung nicht nur Tipparbeit, sondern sie strukturiert den Code, so dass wir auf den ersten Blick erkennen können, welche Elemente zusammen gehören. Das ist bei den mehrfachen if-Anweisungen nicht der Fall. Leider lässt switch keine Vergleiche zu, die getestete Variable darf also keinen Ausdruck enthalten, sonder nur einen Wert. Bei Vergleichen bleibt daher nur die Verwendung von if-Anweisungen mit oder ohne else, je nach Bedarf.
Ausgabe im Nachrichtenfenster: Umgekehrter Frühling, beginnt neuerdings erst Ende November Sagen zufolge soll es früher wochenlang gefrorenes Wasser, ‚Schnee‘ genannt, geregnet haben Die fünfte Jahreszeit Wie zuvor wird die Variable zunächst in der ersten Zeile der switch-Anweisung ausgewertet, anschließend springt Flash zu dem case-Fall, der zutrifft, also wieder case: „Herbst“. Da diesmal die breakAnweisungen fehlen, werden alle nachfolgenden Code-Zeilen ebenfalls ausgeführt, wodurch wir insgesamt drei String-Ausgaben erhalten. 4. Heben Sie die Kommentare der break-Befehle wieder auf. Am Ende befindet sich ein default-Fall, der dann ausgeführt wird, wenn keine der vorgenannten Bedingungen zu trifft. 5. Löschen Sie die Initialisierung von sJahreszeit, so dass nur noch die Deklaration bestehen bleibt: var sJahreszeit:String;
Ausgabe im Nachrichtenfenster: Die fünfte Jahreszeit Da die abgefragte Variable keinen der in den caseFällen definierten Inhalte besitzt, kommt der default-Fall zum Tragen und Flash gibt die dort definierte Nachricht aus. Offiziell empfiehlt Macromedia/ Adobe die Verwendung eines default-Falls. In der Praxis verzichtet man jedoch mitunter darauf, da es eben nur eine Option darstellt, ohne die der entsprechende Code ebenfalls korrekt funktioniert. Würden wir die switch-Anweisung mit einfachen if-Anweisungen schreiben wollen, sähe es so aus: if(sJahreszeit == „Frühling“){
trace(„Wohl temperiert, Blumen blühen, selten über 30 Grad“);
if(sJahreszeit == „Sommer“){
trace(„So fühlt sich ein Frühstücksbrötchen beim Aufbacken“); //usw.
6. Ergänzen Sie testweise folgenden Code: var nZahl:Number = 10; switch (nZahl){ case (>5):
trace(„nZahl ist größer 5“); break;
case (>10):
trace(„nZahl ist größer 10“); break; }
Ausgabe im Nachrichtenfenster: **Fehler** Szene=Szene 1, Ebene=Ebene Bild=1:Zeile 27: Unerwartetes Auftreten von ‚>‘ case (>5):
1,
Die case-Fälle lassen nur einen Wert bzw. einen Ausdruck zu, akzeptieren aber leider keinen Vergleichsoperator. Daher mahnt Flash die fraglichen Zeilen an und führt den Code nicht aus. Berechnungen dagegen sind möglich. 7. Ersetzen Sie die in Schritt 10 definierten caseFälle durch (Fettdruck): case (nZahl*1):
//Anweisung wie zuvor case (nZahl*2):
//Anweisung wie zuvor
11.2 Schleifen
Die Berechnung ergibt im ersten Fall 10, im zweiten 20, ist also identisch mit der Nennung der konkreten Zahlen als Bedingung. Auf den ersten Blick mag eine derartige Berechnung merkwürdig anmuten, sie macht aber durchaus Sinn. Stellen Sie sich vor, Sie programmieren ein Spiel mit verschiedenen Leveln. Abhängig von der erreichten Punktzahl wird der nächste Level gestartet. Das ließe sich mit einer derartigen switch-Anweisung realisieren, wobei nZahl der Basispunktezahl für einen Level entsprechen würde. Die in unserem Beispiel verwendeten Multiplikatoren 1, 2 usw. entsprächen dann der Levelnummer, die zugeordneten Anweisungen würden den betreffenden Level aufrufen. Wir haben break zwar nur in der switch-Anweisung eingesetzt, aber der Befehl lässt sich genau so gut auch in if-Anweisungen verwenden. Wenn wir trotzdem direkte Zahlenvergleiche mit Hilfe einer switch-Anweisung durchführen wollen, müssen wir einen kleinen Umweg nehmen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var nZahl:Number = 10;
var nGrenze:Number = 5;
switch ((nZahl-nGrenze)/Math. abs((nZahl-nGrenze))) {
103
Der Trick besteht darin, das Vorzeichen der Differenz zwischen der zu prüfenden Zahl und dem Grenzwert zu ermitteln. Das Vorzeichen einer Zahl erhalten wir, indem wir sie durch denselben Wert ohne Vorzeichen dividieren. Wird eine negative durch eine positive Zahl gleichen vorzeichenlosen Werts dividiert, erhalten wir eine negative Zahl. Dividieren wir positiv durch positiv, ist auch das Ergebnis positiv. In unserem konkreten Fall lautet die Berechnung im Klartext: (10–5)/Math.abs((10–5))
bzw.: 5/5
was 1 ergibt. Damit trifft der unter case (1) definierte Fall zu. 3. Ändern Sie die Initialisierung von (Fettdruck): var nZahl:Number = 1;
Wahrheitsgemäß meldet das Nachrichtenfenster, die getestete Zahl sei kleiner. Hier lautet die Berechnung im Klartext: (1–5)/Math.abs((1–5))
bzw.:
case (-1) :
-4/4
break;
was -1 ergibt, wodurch der unter case (-1) definierte Fall eintritt. Sind beide Werte dagegen gleich, führt switch den default-Fall aus.
trace(nZahl + „ ist größer „ + nGrenze);
11.2 Schleifen
trace(nZahl + „ ist kleiner „ + nGrenze); case (1) :
break;
default :
trace(nZahl + „ ist gleich „ + nGrenze); }
Ausgabe im Nachrichtenfenster: 10 ist größer 5 In diesem Beispiel greifen wir noch mal die Werte auf, die oben bei dem direkten und daher fehlgeschlagenen Vergleich benutzt wurden. Wir initialisieren den zu vergleichenden Wert mit 10 und legen den Grenzwert mit 5 fest. Danach greifen wir auf die switchAnweisung zu, die einen Ausdruck auswertet.
Während Bedingungen die Voraussetzungen definieren, die für das Ausführen von Code erfüllt sein müssen, legen Schleifen fest, wie oft bestimmte Aktionen aufgerufen werden sollen. Schleifen sind ebenso wie Bedingungen von enormer Bedeutung für die Programmierung. Wenn Sie beispielsweise sukzessive auf alle Inhalte eines Arrays zugreifen, nacheinander Objekte einblenden, Strings zeichenweise ändern wollen etc. – in jedem Fall kommen Schleifen zum Einsatz. Auch hier stellt Flash mehrere Varianten zur Verfügung.
104
11.2.1 for-Schleife Die bei weitem am häufigsten eingesetzte Version einer Schleife stellt die for-Schleife dar. Sie besitzt allgemein folgende Form: for(Initialisierung; Bedingung; Veränderung){ //Anweisungen; }
Um zu verstehen, wie sie funktioniert, wollen wir überlegen, wie wir einen Vorgang mehrfach ausführen. Eine besonders anspruchsvolle Aufgabe wählend nehmen wir an, wir wollen von 1 bis 10 zählen, der Zählvorgang entspricht also der zu wiederholenden Aktion. Wir beginnen mit 1, addieren jeweils 1 hinzu und hören dann auf, wenn wir 10 erreicht haben. Nichts anderes wird in der oben dargestellten Form festgelegt:
• Die Initialisierung entspricht unserem Startwert 1; • Die Bedingung entspricht allen Zahlen bis zu unse-
rem Endewert 10; • Das Addieren von 1, um zur nächst höheren Zahl zu gelangen, entspricht der Veränderung. Um den Zählvorgang in saubere ActionScript -Syntax zu gießen, gehen wir folgendermaßen vor. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: for(var i:Number = 1; i . 17. Ziehen Sie den Slider für den Rot-Offset (zweite Option in der ersten Reihe) ganz nach oben, so dass im Eingabefeld 255 angezeigt wird. Die Füllfarbe erhält eine rötliche Einfärbung, der ursprünglich schwarze Rahmen erstrahlt vollständig in Rot. Mit diesem Slider addieren wir zur bestehenden Farbe einen Wert zwischen 0 (logischerweise kein Effekt) bis 255. Diese Werte ergeben sich aus den möglichen Werten, die über einen einzelnen Farbkanal definiert werden können. Bei einem 8bit-Wert ergeben sich somit 256 verschiedene Werte, die von 0 bis 255 reichen. Da wir den größtmöglichen Wert für den Rotkanal addieren, ergibt sich als neue Füllfarbe 0xff99ff anstatt 0 × 0099 ff. Das können Sie mit Hilfe der Werkzeugpalette kontrollieren. 18. Bestätigen Sie die Änderung mit . 19. Aktivieren Sie den Farbchip für das Fülleimerwerkzeug 20. Klicken Sie auf die Füllung von recht2. Sie erhalten in der dann angezeigten Farbpalette exakt den eben angegebenen Farbwert.
16.3 Einfärben mit Hilfe einzelner Farbkanäle
21. Ändern Sie den Wert für den Rot-Offset per Slider auf –255. Die Grafik sieht nun wieder genauso aus wie zuvor, als der betreffende Offset 0 betrug. Sie können den gewünschten Wert natürlich auch numerisch eingeben, müssen dann aber per <Enter> bestätigen, während die Bedienung des Sliders jede Änderung bei geöffnetem Optionsfenster sofort anzeigt. Da wir nur 256 verschiedene Werte zuweisen können und schon bei 0 alle roten Farbtöne gegenüber der nicht veränderten Grafik vollständig entfernt sind, sieht recht2 genau so aus wie zu Beginn der Übung. 22. Setzen Sie den Wert für den Rot-Offset per Slider zurück auf 0. 23. Ziehen Sie den Slider für den Grün-Offset (zweite Option in der zweiten Reihe) nach unten, so dass im Eingabefeld -153 angezeigt wird. 24. Bestätigen Sie die Änderung mit . 25. Aktivieren Sie den Farbchip für das Fülleimerwerkzeug 26. Klicken Sie auf die Füllung von recht2. Erwartungsgemäß färbt sich unser Rechteck dunkelblau ein. Denn wir haben den vollständigen GrünAnteil, der dezimal 153 und Hexadezimal 99 betrug, vollständig herausgenommen. Wird der Grün-Offset dagegen auf einen Wert größer –153 reduziert, bleibt ein gewisser, aber nicht notwendigerweise sichtbarer Grünanteil erhalten. Wenn Sie z. B. –152 wählen, entsteht der Farbton 0 × 0001ff, der uns jedoch völlig blau erscheint. Wird ein Wert jenseits von 255 bzw. –255 gewählt, setzt ihn Flash automatisch auf den zulässigen höchsten bzw. niedrigsten Wert zurück. Das ist eine vereinfachte Erklärung, denn hinter dem ganzen Prozess steht eine Formel, die sich in der Flash-Hilfe zur ColorTransform-Klasse findet. Mit welcher Variante Sie konkret arbeiten (farbeMultiplier oder farbeOffset) hängt also wesentlich von der Ausgangsgrafik ab. 27. Setzen Sie alle Änderungen zurück, d. h., alle Werte für farbeMultiplier müssen 100 % und alle Werte für farbeOffset müssen 0 betragen. 28. Bestätigen Sie mit .
211
Genau diese Optionen stehen uns per Skripting mit Hilfe der oben bereits genannten Eigenschaften zur Verfügung. 29. Fügen Sie im Funktions-Block folgende Deklaration ein:
unction einfaerben(pZiel:MovieClip, f pWas:String, pWert:Number):Void{
ctColor = new ColorTransform();
ctColor[pWas] = pWert;
trMC = new Transform(pZiel);
trMC.colorTransform = ctColor; }
30. Fügen Sie nach dem Aufruf von init() folgenden weiteren Aufruf ein:
einfaerben(recht2,„redOffset“,255);
Als Ergebnis erhalten Sie ein rötlich eingefärbtes recht2, wie wir es händisch in Schritt 17 realisiert haben. Wir deklarieren eine parametrisierbare Funktion einfaerben(), die für die gewünschte Farbtransformation sorgen soll. Als Parameter verwendet sie den MovieClip, auf den die Transformation angewendet werden soll, die anzusprechende Eigenschaft sowie den ihr zuzuweisenden Wert. Wir richten wiederum ein neues ColorTransform– sowie ein neues Transform-Objekt ein, das mit dem Ziel-MovieClip verknüpft wird. Da Flash Objekteigenschaften in einem Hash verwaltet, können wir auf sie zugreifen, indem wir ihren Namen als String verwenden. Beim Aufruf von einfaerben() übergeben wir im konkreten Fall an den redOffset von recht2 den höchstmöglichen Wert von 255. Die Möglichkeit, ganz gezielt einen bestimmten Farbkanal zu beeinflussen, vereinfacht enorm das Erstellen diverser Effekte wie z. B. Farbübergänge zwischen Bildern innerhalb einer Animation oder beim Weiterschalten von einem Bild zum nächsten innerhalb einer Bildergalerie. Ebenso einfach lassen sich Slider realisieren, die ein stufenloses Einfärben eines Produktes simulieren können. Im Workshop zu Grafikeffekten finden Sie ein Beispiel, wie sich die Farbe einer Grafik beliebig einstellen lässt.
Maus und Tastatur
Als klassische Eingabegeräte für digitale Anwendungen gelten Tastatur und Maus. Flash bietet einfachen Zugriff auf beide, um User-Interaktionen definieren zu können. Eigenschaften, Methoden und Ereignisse für Tastatur und Maus werden in gewohnter Weise in jeweils einer eigenen Klasse definiert. Diese lässt sich im Gegensatz etwa zum MovieClip nicht instanziieren, sondern steht global zur Verfügung. Das ist auch sinnvoll, denn es existiert beispielsweise nur exakt eine Maus bzw. ein Cursor. Eine Instanz davon zu erstellen würde bedeuten, dass man über beliebig viele Kopien verfügen kann – was für jeden User eine echte Herausforderung darstellen würde. Gleiches gilt für die Tastatur, die von der Hardware vorgegeben ist, so dass beliebige Kopien nicht unbedingt sinnvoll wären. Tastaturabfragen spielen immer dann eine Rolle, wenn Informationen vom Anwender benötigt werden. Dazu gehören, um nur einige wenige Beispiele zu nennen, Passwortabfragen, persönliche Auskünfte wie Name und Adresse etc. Auch im Entertainment sind sie wichtig, etwa um Spiele-Objekte steuern zu können. Anmerkungen zur Bedeutung der Maussteuerung dürften sich wohl erübrigen, schließlich lässt sich jede Anwendung per Maus steuern, ist also Standard. In nahezu jedem hier verwendeten Skript, in dem der User eine Interaktion auslösen kann, kommt sie zum Einsatz. Hervorzuheben sind allenfalls spezielle Effekte, die von Mausaktionen abhängen und in den Workshops zu Effekten vorgestellt werden.
17.1 Die Mouse-Klasse Über diese Klasse lässt sich generell der Cursor Ihrer Flash-Anwendung steuern. Sie umfasst jedoch nicht
17
alle Eigenschaften, Methoden und Ereignisse, die mit dem Cursor verbunden sind, denn insbesondere die MovieClip-Klasse verfügt über entsprechende Sprachelemente wie _xmouse, onPress etc. Statt dessen bietet sie:
• Methoden, um in Zusammenarbeit mit MovieClips eigene Cursor verwenden zu können; um Mausereignisse wahrnehmen zu können.
• Methoden,
17.1.1 Eigene Cursor Zweifelsohne am interessantesten ist natürlich die Möglichkeit, eigene Cursor zu verwenden. Damit kann man dem User auf eine recht dezente und dennoch unübersehbare Weise zusätzliche Hinweise darauf geben, wie er momentan mit Ihrer Applikation interagieren kann. Das ist mittlerweile Standard in allen Anwendungen – man denke beispielsweise nur an den sich verändernden Cursor in Flash, je nachdem, über welchem Objekt er sich gerade befindet. Leider fehlt in Flash eine etwa Director vergleichbare Möglichkeit, unmittelbar den Systemcursor auszutauschen. Statt dessen muss man MovieClips als Hilfsmittel einsetzen, um den gleichen Effekt zu erzielen. 1. Erstellen Sie eine Standarddatei. 2. Erstellen Sie auf objects einen Kreis (50 × 50). 3. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKugel, Instanzname kugel1). 4. Erstellen Sie einen leeren MovieClip (Bibliotheksund Verknüpfungsname mcCursor). 5. Zeichnen Sie dort einen beliebigen Cursor (16 × 16, Registrierungspunkt mittig). 6. Kehren Sie zur Hauptzeitleiste zurück.
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
213
214
Kapitel 17 Maus und Tastatur
7. Fügen Sie in actions folgendes Bildskript ein: //------------ vars ----------------– var mCursor:MovieClip;
var nCursorTiefe:Number = 1000;
//---------- functions ------------- function init():Void {
mCursor = this.createEmptyMovieClip („behCursor“, nCursorTiefe); kugel1.onRollOver = cursorEin; kugel1.onRollOut = cursorAus; }
function cursorEin():Void {
mCursor.attachMovie(„mcCursor“, „cursor“, 1); mCursor._x = _xmouse; mCursor._y = _ymouse; mCursor.startDrag(); Mouse.hide(); }
function cursorAus():Void { mCursor.stopDrag();
mCursor.unloadMovie(); Mouse.show(); }
//----------–– start --------------- init();
Wie in Abbildung 43 zu sehen, wird der Mauscursor optisch durch unsere Grafik ersetzt, sobald er sich über der Kugel befindet. Wir legen eine Variable zur Referenzierung der eingefügten Cursorgrafik an und speichern einen Wert für ihre Tiefe. Dabei sollte man natürlich einen möglichst hohen Wert wählen, um zu gewährleisten, dass unser Cursor auch auf jeden Fall über allen anderen Objekten liegt. In init() erzeugen wir einen Behälter für den Cursor und weisen der Kugel zwei Ereignisse zu, bei deren Eintreten jeweils eine Funktion aufgerufen wird. Die Funktion cursorEin() fügt die vorbereitete Grafik aus der Bibliothek auf der Bühne ein und positioniert sie an der Stelle der Maus – schließlich soll sie diese ja ersetzen. Damit sie permanent an die Mausposition gebunden ist, rufen wir die startDrag()Methode auf. Damit kann man, wie im Kapitel MovieClip gesehen, beliebige Objekte ziehen. Anschließend
Abbildung 43: Eigener Mauszeiger in Aktion
wird die Maus mit Hilfe der Methode hide() einfach ausgeblendet. Damit entsteht der Eindruck, wir hätten den normalen Mauszeiger durch einen eigenen ersetzt. Faktisch existiert der normale Zeiger nach wie vor, er ist jedoch nicht mehr sichtbar. In der Funktion cursorAus() machen wir die Änderungen wieder rückgängig, indem das stopDrag() aufgerufen, unsere Grafik gelöscht und der Mauszeiger wieder eingeblendet wird. Alternativ kann man den Behälter für den Cursor bei jedem onRollOver-Ereignis neu erstellen und bei einem onRollOut-Ereignis wieder löschen. Faktisch macht diese Vorgehensweise jedoch keinen Unterschied zu der von uns gewählten Art. In rein optischer Hinsicht ist ein stopDrag() zwar nicht notwendig, da nach dem Löschen der Grafik ohnehin nichts mehr zu erkennen wäre, aber Flash würde dann zu überflüssigen Prozessen gezwungen, d.h. ein Ziehverhalten für ein nicht existierendes Objekt ausführen. In einem Fall hat unser Skript einen enormen Nachteil: Wenn das Objekt, über dem der geänderte Mauszeiger dargestellt wird, ebenfalls per Drag and Drop verschoben werden soll, benimmt sich der Cursor unbotmäßig. 8. Fügen Sie in der Deklaration von init() unmittelbar vor der schließenden Klammer ein: kugel1.onPress = kugel1.startDrag;
kugel1.onRelease = kugel1.onReleaseOutside = kugel1.stopDrag;
Wenn Sie bei geändertem Mauszeiger auf die Kugel klicken und mit gedrückter Maustaste ziehen, bleibt Ihr Cursor an Ort und Stelle stehen. Außerdem reagiert das stopDrag() von kugel1 nicht mehr korrekt. Leider lässt Flash zu einem beliebigen Zeitpunkt jeweils nur ein startDrag() zu. Da wir jeweils eines für den Cursor und für die Kugel einrichten, wird nur das zuletzt aufgerufene startDrag() halbwegs korrekt ausgeführt. Wir benötigen also eine andere Möglichkeit, um die gleichzeitige Bewegung mehrerer Objekte realisieren zu können.
17.1 Die Mouse-Klasse
215
9. Ersetzen Sie in der Deklaration von cursorEin() den Aufruf von mCursor.startDrag(); durch:
15. Erweitern Sie die Deklaration von mausbewegung() unmittelbar vor der schließenden Klammer um:
mCursor.onMouseMove = mausbewegung;
10. Fügen Sie folgende Deklaration in den FunktionsBlock ein:
function mausbewegung():Void{
this._x = _xmouse; this._y = _ymouse;
}
Nun funktioniert das Ziehverhalten wie gewünscht. Wir weisen unserem Cursor ein onMouseMove-Ereignis zu, so dass bei jeder Mausbewegung die Funktion mausbewegung() ausgeführt wird. Sie macht nichts weiter als die Position der Grafik jeweils auf die aktuelle Mausposition zu setzen. Damit ersetzen wir faktisch die zwar einfachere, aber nicht immer verwendbare startDrag()-Methode. Solange die Objekte mit einem veränderten Cursor relativ klein sind, reicht das erzielte Ergebnis. 11. Importieren Sie eine Grafik in die Bibliothek. Wir verwenden beispielhaft affe0.jpg aus dem Workshop Bildergalerie. 12. Fügen Sie die Grafik auf der Ebene objects unterhalb des Kreises ein. 13. Wandeln Sie sie in einen MovieClip um (Bibliotheksname mcBild, Instanzname bild, Registrierungspunkt beliebig). 14. Ändern Sie in der Deklaration von init() die Zuweisung der ersten beiden Ereignisse (Fettdruck):
bild.onRollOver = cursorEin; bild.onRollOut = cursorAus;
Bei einem Rollover über die importierte Grafik ändert sich der Cursor. Sie werden dabei feststellen, dass dessen Bewegung etwas stockend ausfällt, insbesondere, wenn Sie die Maus relativ schnell bewegen. Das liegt daran, das Flash den Screen entsprechend der Bildwiederholrate neu zeichnet, das onMouseMove aber unabhängig davon Mausbewegungen wahrnimmt. Sie können den Effekt testweise vergrößern, indem Sie die Bildwiederholrate im Eigenschaftsfenster auf einen sehr niedrigen Wert setzen. Ihr bemitleidenswerter Cursor schrubbt dann geradezu über den Screen.
updateAfterEvent();
Die Cursorbewegung erfolgt nun erheblich flüssiger, da Flash angewiesen wird, unmittelbar nach einer Positionsänderung der Maus und damit der von uns verwendeten Cursorgrafik den kompletten Screen neu zu zeichnen. Falls mehr als ein Objekt einen veränderten Cursor erhalten soll, können Sie diese Elemente in einem Array erfassen und in der init()-Funktion das gewünschte Verhalten zuweisen. Beachten Sie, dass ein Einfügecursor bei Textfeldern durch die Methode hide() nicht ausgeschaltet wird. Unser Cursor kommt zugegebenermaßen recht bescheiden daher. Da wir jedoch mit MovieClips arbeiten, besteht die Möglichkeit beliebiger Cursoränderungen bis hin zu animierten Mauszeigern. Beachten Sie, dass sich die erzielte Änderung ausschließlich auf Ihren Flash-Film erstreckt. Ist er z.B. in eine HTMLSeite eingebettet, so zeigt sich auf den nicht von Flash überdeckten Elementen lediglich der Standardcursor.
17.1.2 Mausereignisse Im Zusammenhang mit der MovieClip-Klasse haben Sie einige Mausereignisse kennen gelernt. Sie wurden verwendet, indem wir sie einfach einem MovieClip zugeordnet haben. Sie lassen sich alternativ mit Hilfe von Listener einrichten, was u.a. dann Sinn macht, wenn wir derartige Ereignisse abfragen müssen, ohne dass sie mit MovieClips verknüpft sind. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: //------------ vars ––-------------- var oMouseListener:Object = new Object();
//–------------ vars ---------------– function init():Void {
oMouseListener.onMouseDown = geklickt;
Mouse.addListener(oMouseListener); }
function geklickt():Void{
216
trace(„Autsch, nicht so fest!“); }
//–––––------- vars ---------------- init();
Wenn Sie an beliebiger Stelle klicken, meldet sich ein empörtes Nachrichtenfenster und beschwert sich über zu festes Klicken. Wir richten einen Listener ein, bei dem es sich schlicht um ein Objekt handelt, dessen Aufgabe darin bestehen soll, auf bestimmte Mausereignisse zu lauschen. Um welche es sich konkret handelt, teilen wir in der init()-Funktion mit, wo der Listener ein onMouseDown-Ereignis erhält. Tritt es auf, soll die Funktion geklickt() ausgeführt werden. Die Verknüpfung mit der Maus erfolgt über die addListener()-Methode. Dadurch wird er aktiviert. Fehlt die Verknüpfung, kann die Mouse-Klasse das onMouseDown-Ereignis nicht mit einer Funktion verbinden und es erfolgt keine Reaktion auf Klicks. Zu bedenken ist, dass mit onMouseDown jeder Mausklick gemeint ist, unabhängig davon, wo er innerhalb der Flash-Anwendung erfolgt. 3. Erstellen Sie auf objects einen Kreis (50 × 50). 4. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKugel, Instanzname kugel1). 5. Erweitern Sie die Deklaration von init() unmittelbar vor der schließenden Klammer um: kugel1.onPress = kugel1.startDrag;
Beim Testen wird auch der Klick auf die Kugel zu einer entsprechenden Reaktion des Nachrichtenfensters führen. Um über das onMouseDown keine weiteren Aktionen mehr auszuführen, kann man die Verknüpfung der Maus-Klasse mit dem Listener aufheben. 6. Ändern Sie in der Deklaration von init() das onPress-Ereignis (Fettdruck): kugel1.onPress = function(){ Mouse.removeListener (oMouseListener); }
Nach dem Klick auf die Kugel taucht keine weitere Ausgabe im Nachrichtenfenster mehr auf. Das Löschen einer Verknüpfung zwischen einem Listener und einem Objekt, dem wir ihn zugewiesen haben, erfolgt einfach über die Methode removeList ener(), wobei natürlich der Name des Listeners als
Kapitel 17 Maus und Tastatur
Argument zu übergeben ist. In unserem Fall reagiert die Maus danach nicht mehr auf ein onMouseDown. Damit wird jedoch nicht der Listener selbst gelöscht, denn das zuvor eingerichtete Objekt oMouseListener bleibt erhalten. Auf diese Weise lässt sich auch zu einem späteren Zeitpunkt der Listener einfach wieder zuweisen, falls benötigt. Bedenken Sie, dass eine der startDrag()-Methode vergleichbare Kurzform hier nicht möglich ist, da wir der removeListener()Methode ein Argument übergeben müssen. Falsch wäre also: kugel1.onPress = Mouse. removeListener(oMouseListener);
Aufgrund des Klammer-Operators am Ende von removeListener würde Flash die Anweisung korrekterweise als direkten Aufruf interpretieren und die Verknüpfung zum Listener sofort löschen. Die Verknüpfung lässt sich problemlos wieder herstellen. 7. Fügen Sie rechts neben kugel1 eine weitere Kugel ein. 8. Weisen Sie dem Duplikat den Instanznamen kugel2 zu. 9. Erweitern Sie die Deklaration von init() unmittelbar vor der schließenden Klammer: kugel2.onPress = function(){
Mouse.addListener(oMouseListener); }
Wird die Verknüpfung zwischen Maus-Klasse und Listener durch Klick auf die erste Kugel gelöscht, stellt der Klick auf die zweite Kugel sie wieder her. 10. Erweitern Sie in der Deklaration von init() das onPress-Ereignis von kugel1 unmittelbar vor dessen schließender Klammer:
delete oMouseListener;
Der Klick auf kugel löscht nun unwiderruflich das Listener-Objekt selbst, so dass die erneute Zuweisung nach Klick auf kugel2 folgenlos bleibt. Testweise können Sie sich im onPress-Ereignis von kugel2 oMouseListener ausgeben lassen. Dabei erhalten Sie ein trauriges undefined im Nachrichtenfenster. Was wir uns am Beispiel von onMouseDown angeschaut haben, funktioniert natürlich auch mit den übrigen Mausereignissen wie onMouseUp, onMouseMove und onMouseWheel.
17.1 Die Mouse-Klasse
Das Verwenden eines eigenen Cursors kann ebenfalls über einen Listener hergestellt werden. Legen wir das obige Beispiel zugrunde, sieht der Code so aus (Änderungen fett): //------------- vars -----------------var mCursor:MovieClip;
var nCursorTiefe:Number = 1000;
var oMouseListener:Object = new Object();
//––--------- functions --------------function init():Void {
oMouseListener.onMouseMove = mausbewegung; mCursor = this.createEmptyMovieClip („behCursor“, nCursorTiefe); bild.onRollOver = cursorEin; bild.onRollOut = cursorAus;
kugel1.onPress = kugel1.startDrag; kugel1.onRelease = kugel1. onReleaseOutside=kugel1.stopDrag; }
function cursorEin():Void {
mCursor.attachMovie(„mcCursor“, „cursor“, 1); mCursor._x = _xmouse; mCursor._y = _ymouse;
Mouse.addListener(oMouseListener); Mouse.hide(); }
function cursorAus():Void {
Mouse.removeListener(oMouseListener); mCursor.unloadMovie(); Mouse.show(); }
function mausbewegung():Void { mCursor._x = _xmouse; mCursor._y = _ymouse; updateAfterEvent(); }
//------------ start -----------------init();
Prinzipiell funktioniert es genauso einfach wie zuvor. Allerdings hat der Listener leider den Nachteil, kein
217
updateAfterEvent() wahrnehmen zu können, so
dass unser Cursor etwas müde vor sich hin ruckelt, falls nicht die Bildwiederholrate des Films erhöht wird. Notgedrungen nur für Windows-Systeme interessant ist die Möglichkeit, das Mausrad anzusprechen. Wir wollen uns dazu ein vereinfachtes Beispiel anschauen, bei dem eine Grafik skaliert wird. 1. Erstellen Sie eine Standarddatei. 2. Importieren Sie eine Grafik in die Bibliothek. Wir verwenden beispielhaft affe0.jpg aus dem Workshop Bildergalerie. 3. Fügen Sie die Grafik auf der Ebene objects etwa in Screenmitte ein. 4. Wandeln Sie sie in einen MovieClip um (Bibliotheksname mcBild, Instanzname bild, Registrierungspunkt beliebig). 5. Weisen Sie actions folgendes Bildskript zu: //----------- vars ----------------- var oMouseListener:Object = new Object(); var bSkalieren:Boolean = false;
//---------- functions ------------- function init():Void {
oMouseListener.onMouseWheel = function(nDelta:Number){ skalieren(nDelta); };
bild.onPress = skalEinAus; }
function skalEinAus():Void { bSkalieren = !bSkalieren; if (bSkalieren) {
Mouse.addListener (oMouseListener); } else {
Mouse.removeListener (oMouseListener); } }
function skalieren(nDelta):Void { bild._yscale = bild._xscale += nDelta; }
//––--------- start ---------------- init();
218
Nach einem Klick auf die Grafik können Sie per Mausrad skalieren, ein weiterer Klick deaktiviert das Mausrad wieder. Wir legen ein neues Objekt zwecks Referenzierung des Listeners an und speichern in einer Variablen den Wert false. Damit definieren wir die Anfangsbedingung für den Skaliervorgang. In init() definieren wir die Mausrad-Funktionalität: Bei Bewegen des Rades soll eine Funktion skalieren() mit dem Argument nDelta aufgerufen werden. Bei nDelta handelt es sich um die Anzahl an Zeilen, die pro Drehung weiter gescrollt werden soll. Bewegen wir das Rad nach rückwärts, erhalten wir negative, bei vorwärts positive Werte. Standardmäßig entspricht nDelta dem Wert 3, den Sie nach Belieben anpassen können. Die entsprechende Einstellung wird im jeweiligen Betriebssystem vorgenommen. Das Bild erhält einen Mausklick, der die Boolsche Variable bSkalieren jeweils auf den Gegenwert setzt. Beträgt sie anfangs false, setzt der erste Klick sie auf true, der nächste wieder auf false etc. Die if-Bedingung kontrolliert ihren Wert und weist dann, wenn sie true enthält, der Maus den zuvor eingerichteten Listener zu, andernfalls entfernen wir den Listener wieder. Die Funktion skalieren() erhöht die horizontale und vertikale Skalierung der Grafik um den in nDelta übergebenen Wert. Ist er positiv, vergrößern wir das Bild, andernfalls wird es verkleinert. In unserem konkreten Beispiel funktioniert nach dem Aktivieren das Skalieren auch dann, wenn sich die Maus nicht (mehr) über der Grafik befindet.
17.2 Tastatur Während sich die Maus auf grafische Interaktionen konzentriert, erfolgen Texteingaben i.d.R. über die Tastatur. Mit Hilfe der Key-Klasse können wir dabei gezielt die gedrückte Taste auslesen und dementsprechend reagieren. Jeder Taste wird intern ein Code zugeordnet, auf den wir zugreifen können. Das ist ein gängiges Verfahren, und Ihnen sind sicher diverse Codetabellen bekannt (beispielsweise ASCII oder Unicode). Sie können in Flash sowohl auf den ASCIIwie den internen Code für Tasten zugreifen. 1. Erstellen Sie eine Standarddatei.
Kapitel 17 Maus und Tastatur
2. Fügen Sie auf objects etwa in Bühnenmitte ein dynamisches Textfeld ein (12, Verdana, Bold, In stanzname tiKeyCode). 3. Weisen Sie actions folgendes Bildskript zu: //------------- vars --------------- var oKeyListener:Object = new Object();
//----------- functions ------------ function init():Void {
tiKeyCode.multiline = true;
tiKeyCode.autoSize = „left“; oKeyListener.onKeyDown = function():Void {
tiKeyCode.text = „Intern: „+Key. getCode()+“\nASCII: „+Key.get Ascii(); };
Key.addListener(oKeyListener); }
//------------ start --------------- init();
Wenn Sie testen, erhalten Sie beim Drücken einer beliebigen Taste den zugehörigen Code angezeigt. Wie im Fall der Maus richten wir zunächst ein Objekt ein, das als Listener fungieren soll. In der init()-Funktion aktivieren wir den Zeilenumbruch des Textfelds und dessen linksbündige Ausrichtung, wobei sich seine Größe an den Inhalt anpassen soll. Anschließend weisen wir dem Listener das onKeyDown-Ereignis zu und verknüpfen die Key-Klasse mit diesem Listener, so dass auf das Ereignis reagiert werden kann. Wird eine Taste gedrückt, schreibt Flash sowohl ihren internen wie ihren ASCII-Code in das zuvor angelegte Textfeld. Abhängig vom konkreten Ergebnis kann man eine bestimmte Aktion ausführen. 4. Erweitern Sie in der Deklaration von init() das onKeyDown-Ereignis unmittelbar vor der schließenden Klammer: if(Key.getCode()==65){
Key.removeListener(oKeyListener); }
Wird die Taste gedrückt, erhalten Sie noch die zugehörige Code-Ausgabe, anschließend erfolgt jedoch keine weitere Rückmeldung über die betätigte Taste.
17.2 Tastatur
219
Wir löschen den Bezug auf den Listener einfach dann, wenn der zurück gegebene Code dem Wert 65 entspricht, was die Taste bezeichnet. Beachten Sie, dass der interne Code nicht zwischen Groß- und Kleinschreibung unterscheidet. Ist diese Unterscheidung notwendig, verwenden Sie die Methode get Ascii() anstelle von getCode(). Für häufig verwendete Tasten hält Flash eine Reihe von Konstanten bereit, so dass man sich bei bedingten Anweisungen nicht den Zahlencode merken muss. Folgende Tabelle enthält eine Auflistung der Konstanten. Code
Taste
BACKSPACE
CAPSLOCK
CONTROL
<Strg>
DELETEKEY
<Entf>
DOWN
END
<Ende>
ENTER
<Eingabetaste>
ESCAPE
<Esc>
HOME
INSERT
<Einfg>
LEFT
PGDN
PGUP
RIGHT
SHIFT
SPACE
TAB
UP
Konstanten der Key-Klasse
Beliebt ist die Tastatur auch bei Spielen, um Figuren zu steuern. Wir schauen uns ein simples Beispiel an, wobei einige der Konstanten anstelle von Zahlencodes verwendet werden sollen. 1. Erstellen Sie eine Standarddatei. 2. Erstellen Sie auf objects einen Kreis (50 × 50). 3. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKugel, Instanzname kugel1). 4. Weisen Sie actions folgendes Bildskript zu:
//------------- vars --------------- var oKeyListener:Object = new Object(); var nTempoX:Number = 5; var nTempoY:Number = 5;
//----------- functions ------------ function init():Void {
oKeyListener.onKeyDown = function():Void {
var nKey:Number = Key.getCode(); switch (nKey) { case Key.LEFT :
bewegen(-nTempoX, 0); break;
case Key.RIGHT :
bewegen(nTempoX, 0); break;
case Key.UP :
bewegen(0, -nTempoX); break;
case Key.DOWN :
bewegen(0, nTempoX); } };
Key.addListener(oKeyListener); }
function bewegen(pFaktorX:Number, pFaktorY:Number):Void { kugel1._x += pFaktorX; kugel1._y += pFaktorY; }
//------------ start --------------- init();
Wenn Sie eine der Pfeiltasten drücken, bewegt sich Ihre Figur in die entsprechende Richtung. Neben dem unvermeidlichen Listener richten wir zwei Variablen ein, um das Tempo der horizontalen und der vertikalen Bewegung zu definieren. In der init()-Funktion wird zuerst der aktuelle Tastencode in einer Variablen gespeichert. Für uns interessant sind nur vier mögliche Werte, die die vier Pfeiltasten repräsentieren. Sie werden in der switch()-Anweisung abgefragt. Je nach Ergebnis rufen wir die Funk-
220
tion bewegen() auf, die als Argument Werte für die horizontale und vertikale Bewegung erhält. In bewegen() addieren wir einfach die übergebenen Werte zur aktuellen horizontalen und vertikalen Position hinzu. Wollen wir beispielsweise eine Bewegung nach links erreichen, muss -nTempoX übergeben werden, so dass innerhalb von bewegen() eine Subtraktion erfolgt. Für die vertikale Richtung ist dann ein Wert von 0 notwendig, da ansonsten zusätzlich auch eine vertikale Bewegung erfolgen würde. Etwas anders gestaltet sich die Verwendung von Key.isDown(), einer Methode, die im Gegensatz zu vorher abfragt, ob eine ganz bestimmte Taste gedrückt wurde. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: //------------- vars ----------------
Kapitel 17 Maus und Tastatur
Wenn Sie die rechte Pfeiltaste drücken, erhalten Sie im Nachrichtenfenster ein entsprechendes Feedback, bei anderen Tasten dagegen nicht. Der prinzipielle Unterschied zu vorher besteht in der dem onKeyDown-Ereignis zugewiesenen Methode Key.isDown(). Nach wie vor werden zwar alle Tastatureingaben verfolgt, aber die Reaktion hängt davon ab, ob die rechte Pfeiltaste gedrückt wurde. Nur wenn das zutrifft, gibt das Nachrichtenfenster eine eminent wichtige Meldung aus. Normalerweise kann man mit Key.getCode() bzw. Key.getAscii() nur eine einzige, nämlich die zuletzt gedrückte Taste abfragen. An dieser Stelle hilft Key.isDown(), um mehrere Tasten gleichzeitig zu kontrollieren. 3. Ändern Sie in der Deklaration von init() die ifBedingung (Fettdruck):
var oKeyListener:Object = new Object();
if(Key.isDown(Key.RIGHT)&&Key. isDown(Key.UP)){
function init():Void {
}
//------------ functions ------------
trace(„Zwei Tasten gedrückt“);
oKeyListener.onKeyDown = function() {
Nun erfolgt nur noch eine Ausgabe, wenn sowohl die rechte wie die obere Pfeiltaste gleichzeitig gedrückt werden. Mit Hilfe eines logischen Und können wir beliebige Ausdrücke miteinander kombinieren, was hier konkret mit zwei Pfeiltasten geschieht. Möglich ist natürlich auch ein logisches Oder.
if(Key.isDown(Key.RIGHT)){
trace(„Rechte Pfeiltaste gedrückt“); } }
Key.addListener(oKeyListener); }
//------------- start -------------- init();
BitmapData- und Filter-Klasse
Die Einführung der BitmapData- und der FilterKlasse in Flash 8 glich einem kleinen Quantensprung: War man bisher auf die Bearbeitung von Vektoren beschränkt, ließen sich nun mit sehr ausgefeilten Methoden Bitmaps nahezu beliebig verwenden. Ein solcher Schritt war längst überfällig, bot doch schon Director seit Jahren eine vergleichbare Funktionalität in Form des sogenannten Imaging Lingo. Trotz der Bedeutung, die diesem Schritt zukommt, muss man selbst heute noch mit einigem Erstaunen feststellen, dass die sich bietenden Möglichkeiten von den wenigsten Entwicklern wirklich voll ausgeschöpft werden. Noch allzu häufig entspricht der Workflow dem klassischen Vorgehen Photoshop (bzw. Bildbearbeitungsprogramm) – Flash, wobei alle benötigten Grafiken extern vorbereitet werden. Zahlreiche Schritte lassen sich jedoch direkt in Flash per Skripting erledigen. Wenn der Entwicklungsprozess dennoch anders verläuft, so liegt das einerseits in den durch die in Flash angebotenen Werkzeuge vertauschten Rollen: Während in Photoshop naturgemäß der Grafiker die entsprechenden Grafiken erstellt, müsste es nun in Flash der Programmierer sein, wollte man die besonderen Möglichkeiten dieses Programms ausschöpfen. Aus eigener Erfahrung weiß der Autor jedoch, dass in den meisten Fällen den grafischen Fähigkeiten eines Programmierers eher enge Grenzen gesetzt sind. So manch einer kommt da nicht weit über rote Kreise und blaue Rechtecke hinaus. Andererseits erweisen sich die beiden neuen Klassen im praktischen Einsatz als mitunter recht sperrig, wobei erschwerend hinzu kommt, dass die offizielle Dokumentation von Macro media/Adobe an dieser Stelle für Neulinge leider mitunter unbrauchbar – sprich: unverständlich und unvollständig – ist.
18
Die Einsatzmöglichkeiten alleine der BitmapDataKlasse sind ausgesprochen vielfältig und beschränken sich keineswegs nur auf grafische Effekte. Darüber hinaus können wir nun Informationen mit Hilfe dieser Klasse bereit stellen, die den Workflow vereinfachen, und wir sind in der Lage, die Darstellung von Vektorgrafiken zu optimieren, um dem immer noch nicht übermäßig leistungsfähigen Flash-Player auf die Sprünge zu helfen. Am Ende dieses Kapitels finden Sie dazu gewissermaßen als Appetithäppchen einige Hinweise. Insofern möchte der Autor dem Leser dieses Kapitel, das in einem allgemeinen Skripting-Lehrbuch leider nur in die Thematik einführen kann, ganz besonders ans Herz legen. Die vorgestellten Methoden bieten Gelegenheit zu zahlreichen Experimenten, die man sich nicht entgehen lassen sollte.
18.1 Bitmap versus Vektor Grafische Darstellungen beruhen prinzipiell auf zwei verschiedenen Verfahrensweisen:
• Vektor.
Bei einer Vektorgrafik resultiert das anzuzeigende Bild aus einer Berechnung, also einer mathematischen Beschreibung von Punkten und Kurven. So werden für eine Linie Informationen zu dessen Stärke, Farbe sowie Anfangs- und Endpunkt benötigt, während bei einem Kreis neben Linienstärke und –farbe die Position des Mittelpunkts sowie der Radius bekannt sein müssen. Beispielsweise könnte die Anweisung zur Erzeugung einer rechteckigen Vektorgrafik in einer postscriptfähigen Sprache etwa so lauten:
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
221
222
0 1 0 setrgbcolor 0 0 200 200 rectfill 0 0 1 setrgbcolor 0 0 100 100 rectfill In der ersten Zeile wird Grün als Füllfarbe festgelegt. Danach entsteht von der horizontalen und vertikalen Position 0 ausgehend ein 200 Pixel hohes und ebenso breites Rechteck, dem die erwähnte Farbe zugewiesen wird. Die dritte Zeile wechselt die Farbe zu Blau, mit dem das in der letzten Zeile ebenfalls auf der 0-Position beginnende, aber nur 100 Pixel hohe und breite Rechteck gefüllt wird. Dadurch erhalten wir ein kleines blaues Rechteck, das links oben in einem größeren grünen Rechteck eingefügt wird. Da der Code zur Erzeugung des Bildes aus wenigen Zeilen besteht, belegt diese Vektorgrafik nur minimalen Speicherplatz. Ein weiterer Vorteil liegt in der beliebigen Skalierbarkeit, ohne dass damit ein Qualitätsverlust oder eine deutliche Vergrößerung der Datei einhergehen würden. Allerdings eignen sich Vektorgrafiken nicht für jede Art von Bildern. Haben wir es beispielsweise mit einem sehr detailreichen Motiv zu tun, so könnte es notwendig werden, sehr viele Vektoren zu erzeugen, um die betreffenden Details korrekt wieder zu geben. Dadurch würden die Anweisungen zum Berechnen der Grafik enorm aufgebläht und dementsprechend eine erheblich größere Datei entstehen. Das Anzeigen der Grafik würde zudem mehr Zeit in Anspruch nehmen, da sie ja berechnet werden muss. So enthalten Photos i. d. R. viele Details und eignen sich daher nicht für eine Vektordarstellung. • Bitmap. Völlig anders dagegen funktioniert eine Bitmapgrafik. Sie betrachtet das darzustellende Motiv immer als eine rechteckige, mosaikartige Ansammlung aus zahlreichen einzelnen Bildpunkten, den sogenannten picture elements, aus denen sich der Name Pixel bzw. Pixelgrafik herleitet. Jeder Bildpunkt definiert sich durch seine Position, einen Farbwert und gegebenenfalls eine Transparenz. Um das vorhergehende Beispiel der blauen und grünen Rechtecke umzusetzen, müsste man in einer Art Tabelle jeden einzelnen Punkt, ausgehend von der linken oberen Ecke, erfassen und ihren
Kapitel 18 BitmapData- und Filter-Klasse
Farbwert sowie die Transparenz speichern. Faktisch benötigt man 200 × 200 bzw. 40.000 Werte. Handelt es sich um eine 32-Bit-Grafik mit je 8 Bit (bzw. 256 Werte) für jeden der drei Farbkanäle des RGB-Farbraums sowie zusätzlich 8 Bit für die Transparenz, wächst die entsprechende Datei auf eine Größe von 8 × 8 × 8 × 8 Bit bzw. 160 KB an. Damit ist die resultierende Datei erheblich größer als das besprochene Vektor-Pendant. Ein Skalieren führt zu einem mehr oder minder deutlich wahrnehmbaren Qualitätsverlust und Kanten, die nicht waagerecht oder senkrecht verlaufen, erzeugen unschöne Ränder, die rechnerisch per Anti-Aliasing geglättet werden müssen. In einem zentralen Punkt sind sie jedoch Vektorgrafiken eindeutig überlegen: Sie eignen sich insbesondere für detailreiche Motive und sind damit erste Wahl, wenn es um Photos geht. Zudem ändert sich die benötigte Rechnerleistung prinzipiell nicht mit der Detailfülle, da nur einfach Werte aus einer Tabelle auszulesen sind. Flash arbeitet standardmäßig mit Vektorgrafiken, eine Vorgabe, die auf den für die Entstehung des Programms ursprünglich maßgebenden Gedanken, die Dateigröße möglichst gering zu halten, zurück geht. Wer beispielsweise mit den in der Werkzeugpalette enthaltenen Bordmitteln zeichnet, erzeugt eine Vektorgrafik. Das gleiche geschieht bei der Verwendung der Zeichnungsmethoden der MovieClip-Klasse. Vor Flash 8 konnte man Bitmaps laden und entweder rudimentär in der Autorenumgebung bearbeiten oder zur Laufzeit bezüglich Position, Größe, Deckkraft und Farbe manipulieren, vorausgesetzt, sie waren in MovieClips als übergeordnete Behälter integriert. Ein Zugriff auf einzelne Pixel war dabei jedoch nicht möglich. Das ändert sich grundlegend durch die BitmapData-Klasse, die endlich zahlreiche Werkzeuge für ein gezieltes, umfassendes Bearbeiten von PixelGrafiken bereit stellt. Aller Begeisterung zum Trotz bleibt momentan noch ein dicker Wermutstropfen: Das Rendern der Grafiken erfolgt im Flash-Player bis zur Version 9 über den Hauptprozessor, die zusätzliche Leistung, die eine moderne Grafikkarte durch ihren eigenen Prozessor anbietet, bleibt ungenutzt. Sie steht erst ab Flash CS4 bzw. Player 10 zur Verfügung (und führt derzeit dazu, dass die Animation auf verschiedenen
18.2 Instanziierung der BitmapData-Klasse
Systemen leider verschieden berechnet und dargestellt wird) Selbst der Tempogewinn durch die AVM2, die nur bei AS 3.0 zum Einsatz kommt, reicht noch nicht immer aus, um diejenigen Feuerwerke abbrennen zu lassen, die rein programmiertechnisch mit den vorhandenen Sprachelementen möglich wären. Aber das dürfte lediglich eine Frage der Zeit sein, bis Flash auch an dieser Stelle an moderne Programmiersprachen anschließt.
18.2 Instanziierung der BitmapData-Klasse Alle Bitmap-bezogenen Daten werden in einem eigenen Objekt, einer Instanz der BitmapData-Klasse, im Arbeitsspeicher erfasst. Mit angenehm wenigen Parametern ist es möglich, eine derartige Instanz zu erzeugen, wobei ähnlich der geom-Klasse zunächst das benötigte Package bzw. direkt die betreffende Klasse geladen werden sollte. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: import flash.display.*;
var bmBitmap:BitmapData = new BitmapData(300,200); trace(bmBitmap);
trace(bmBitmap.width);
trace(bmBitmap.height);
Ausgabe im Nachrichtenfenster: [object Object] 300 200 Wenn Ihr Bildschirm nichts weiter anzeigt als die angegebene Meldung, so ist das völlig korrekt. Denn das eben erzeugte Objekt existiert nur im Arbeitsspeicher und besitzt weder eine visuelle Repräsentation noch irgendwelchen Inhalt. Es handelt sich um ein unsichtbares Rechteck mit einer Breite von 300 und einer Höhe von 200 Pixeln, wie uns die Ausgabe bestätigt. Die maximale Größe liegt bei 2.880 Pixeln horizontal und vertikal. Wird ein höherer Wert gewählt, weigert sich Flash, das betreffende Objekt darzustellen. Auf diese Weise soll verhindert werden, dass ein Flash-Film den
223
kompletten Arbeitsspeicher auf einem Zielrechner belegt (angesichts der Tatsache, dass Vista-geschädigte Nutzer bereits 1 GB alleine für ihr Betriebssystem verschwenden müssen, handelt es sich bei dieser Einschränkung um eine kluge Entscheidung). 3. Erweitern Sie die Initialisierung von bmBitmap (Fettdruck): var bmBitmap:BitmapData = new BitmapData(300, 200,false,0xffaa00);
4. Fügen Sie nach der Initialisierung von bmBitmap ein: v ar mBitmap:MovieClip = this.create EmptyMovieClip(„behBitmap“,1); mBitmap.attachBitmap(bmBitmap,1);
Als Ergebnis erscheint nun in der linken oberen Ecke ein orangefarbenes Rechteck. Zwingend vorgeschrieben sind für das BitmapData-Objekt als Parameter die Angabe von Breite und Höhe in Pixeln. Wenn wir ohne die Verwendung weiterer Methoden der BitmapData-Klasse auch etwas sehen wollen, müssen wir darüber hinaus auf die optionalen Parameter für Transparenz und Farbe zugreifen. In unserem konkreten Beispiel setzen wir die Transparenz auf false. Würden wir Statt dessen true übergeben, wäre der Effekt der gleiche wie zuvor: Wir könnten nichts sehen, da unabhängig von der verwendeten Farbe jeder Pixel durchsichtig dargestellt würde. An letzter Stelle definieren wir die Füllfarbe, die in unserem Beispiel einem kräftigen Orange entspricht. Eine davon unabhängige Rahmenfarbe wie bei den Zeichnungsmethoden der MovieClip-Klasse existiert hier nicht. Als Farbe haben wir einen 24-Bit-Hexadezimalwert übergeben, bestehend aus jeweils zwei Ziffern pro Farbkanal, genauso, wie wir es bereits beim ColorTransform-Objekt kennen gelernt haben. Flash führt die Anweisung auch klaglos aus. Offiziell empfohlen wird jedoch ein 32-Bit-Hexadezimalwert, der zwei weitere Ziffern für den Alphakanal verwendet. Sie stehen am Anfang der Zahl. In Dezimalschreibweise spricht man von ARGB (Alpha, Rot, Grün, Blau)-Werten, wobei jeder Kanal einen 8-Bit-Wert umfasst bzw. Werte von 0 bis 255. Der Alphakanal definiert den Grad der Transparenz. Liegt ein Wert von ff (oder: FF) vor, entspricht
224
dies der höchsten Intensität und damit voller Deckkraft. Es existiert dementsprechend kein transparentes Pixel. Vollständige Transparenz wird dagegen hexadezimal mit 00 und dezimal mit 0 festgelegt. 5. Korrigieren Sie die Initialisierung von bmBitmap wie folgt (Fettdruck): var bmBitmap:BitmapData = new BitmapData(300, 200,false,0xffffaa00);
Zwar ergibt sich beim Testen kein Unterschied zu vorher, aber formal folgt die Formulierung der offiziellen Vorgabe. 6. Ändern Sie die Initialisierung von bmBitmap wie folgt (Fettdruck): var bmBitmap:BitmapData = new BitmapData(300, 200,false,0x00ffaa00);
Wenn wir die Gegenprobe machen und im Farbwert eine vollständige Transparenz übergeben, wird diese beim Testen entgegen unserer Erwartungen offensichtlich ignoriert. Der Grund liegt im dritten übergebenen Parameter (der eigentlich etwas überflüssig ist). 7. Ändern Sie die Initialisierung von bmBitmap wie folgt (Fettdruck): var bmBitmap:BitmapData = new BitmapData(300, 200,true,0x00ffaa00);
Nun verschwindet unsere Grafik wie erwartet. Durch den dritten Parameter teilen wir Flash mit, den im Farbwert enthaltenen Transparenzwert darzustellen. Da er auf 0 (bzw. Hexadezimal: 00) gesetzt wurde, können wir kein Rechteck mehr sehen.
Kapitel 18 BitmapData- und Filter-Klasse
ansonsten doch recht langweilige orangefarbene Rechtecke zu erstellen. Diese Funktionalität bietet ja schon die MovieClip-Klasse. BitmapData werden erst durch die Bearbeitungsmöglichkeiten, die über die zahlreichen Methoden zur Verfügung gestellt werden, wirklich interessant. Und da macht es auch Sinn, dass wir, wie im ersten Beispiel am Anfang dieses Abschnitts gezeigt, sogar ein Objekt erzeugen können, das zunächst keinen sichtbaren Inhalt enthält.
18.3 Eigenschaften der BitmapData-Klasse In der BitmapData-Klasse werden nur vier jeweils schreibgeschützte Eigenschaften festgelegt, die eigentlich weitgehend selbsterklärend sind:
• width, die Breite des Bitmap-Rechtecks in Pixel; • height, die Höhe des Bitmap-Rechtecks in Pixel; • transparent, die Transparenz, die an dieser
Stelle leider keinen transparenten Verlauf zulässt, sondern nur die Werte true oder false kennt; • rectangle, gemessen als ein Rechteck, das in der linken oberen Ecke der BitmapData-Instanz beginnt, und eine Ausdehnung entsprechend der als Parameter übergebenen Werte besitzt. Die ersten drei Werte werden dem Konstruktor bei der Instanziierung der BitmapData-Klasse übergeben, während der vierte Wert automatisch aus den beiden ersten Werten folgt.
8. Ändern Sie die Initialisierung von bmBitmap wie folgt (Fettdruck): var bmBitmap:BitmapData = new BitmapData(300, 200,true,0xffffaa00);
18.4 Methoden der BitmapData-Klasse
Machen wir auch dazu die Gegenprobe, indem wir für die Deckkraft den höchstmöglichen Wert übergeben, taucht unser Rechteck in seiner vollen Schönheit wieder auf. Sie können an dieser Stelle gerne mit Werten zwischen den genannten Extremen testen. Wählen Sie einen schwarzen Hintergrund für die Bühne und übergeben Sie beispielsweise 0xaaffaa00, wird das Rechteck abgedimmt. Der Hauptzweck dieser Klasse besteht zugegebenermaßen nicht einfach darin, zwar anmutige, aber
Das eigentliche Potential dieser Klasse offenbart sich in ihren zahlreichen Methoden, die wir hier nicht alle im Einzelnen behandeln können. Die Tabelle gibt einen ersten Überblick, dem sich eine Diskussion ausgesuchter Beispiele anschließt. Sie sollte als Anregung verstanden werden, um selbst Experimente mit dieser Klasse zu wagen.
18.4 Methoden der BitmapData-Klasse
225
Methode
Bedeutung
Methode
Bedeutung
applyFilter()
Weist einen Filter der Filter-Klasse zu
noise()
clone()
Erstellt eine exakte Kopie der BitmapData-Instanz
Weist pixelweise zufällige RGB-Werte zu
paletteMap()
Ordnet Farbkanalwerte neu zu
colorTransform() Ändert die Farbwerte in einem
perlinNoise()
Erstellt eine Störung auf der Basis der Gleichung von Perlin
compare()
Vergleicht (theoretisch) zwei gleich große BitmapData-Instanzen und gibt die Unterschiede als neues Objekt zurück (ab Flash 9)
pixelDissolve()
Pixelweises Auflösen einer BitmapData-Instanz oder pixelweiser Übergang von einer BitmapData-Instanz zu einer anderen BitmapData-Instanz
copyChannel()
Überträgt Informationen aus einem Farbkanal in einen anderen sowohl innerhalb einer BitmapData-Instanz wie auch zwischen solchen Instanzen
scroll()
Scrolled eine BitmapData-Instanz um den angegebenen Wert horizontal und/oder vertikal
copyPixels()
Kopiert einen angegebenen rechteckigen Bereich aus einer BitmapData-Instanz in eine andere BitmapData-Instanz. Skalierung, Rotation oder Farbeffekte werden dabei nicht übernommen
setPixel()
Weist einem angegebenen Punkt einen RGB-Wert zu
setPixel32()
Weist einem angegebenen Punkt einen ARGB-Wert zu
threshold()
Ändert die Farbwerte aller Pixel, die eine angegebene Bedingung (Vergleich von Farbwerten) erfüllen
angegebenen rechteckigen Bereich
dispose()
Löscht eine BitmapData-Instanz aus dem Arbeitsspeicher
draw()
Kopiert eine BitmapData-Instanz oder einen MovieClip in eine andere BitmapData-Instanz. Dabei können zusätzliche Effekte wie Skalierung und Farbtransformationen mit Hilfe entsprechender Objekte (Matrix, ColorTransform etc.) definiert werden
fillRect()
Füllt einen rechteckigen Bereich mit einer ARGB-Farbe
floodFill()
Füllt einen Bereich gleicher Farbe ab einem angegebenen Startpunkt
generateFilterRect()
Definiert ein Zielrechteck für die Methode applyFilter()
getColorBoundsRect()
Gibt einen rechteckigen Bereich zurück, innerhalb dessen alle Pixel einem angegebenen ARGB-Wert entsprechen
getPixel()
Gibt eine Ganzzahl zurück, die dem RGB-Wert eines angegebenen Punktes entspricht
getPixel32()
Gibt eine Ganzzahl zurück, die dem ARGB-Wert eines angegebenen Punktes entspricht
Methoden der BitmapData-Klasse
18.4.1 Dispose() Bevor wir uns mit irgendeiner anderen Methode beschäftigen, ist es wichtig zu erfahren, wie man das gerade instanziierte Objekt wieder los wird. Denn eine Bitmapgrafik belegt im Gegensatz zur Vektorgrafik viel Speicherplatz. Daher müssen derartige Objekte, sobald sie nicht mehr benötigt werden, aus dem Arbeitsspeicher entfernt werden. Dazu stellt Flash die Methode dispose() zur Verfügung. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: //------------– import –------------ import flash.display.*;
//-------------- vars –------------- var mBitmap:MovieClip;
Kollisionstest zwischen einer BitmapData-Instanz sowie einem Punkt, einem MovieClip oder einer anderen BitmapData-Instanz
var bmBitmap:BitmapData;
loadBitmap()
Lädt aus der Bibliothek eine BitmapGrafik in eine BitmapData-Instanz
merge()
bmBitmap = new BitmapData(300, 200,true,0xffffaa00);
Mischung der Farbkanäle zweier BitmapData-Instanzen
hitTest()
//------------ functions –---------- function init() {
mBitmap = this.createEmptyMovieClip („behBitmap“, 1);
226
Kapitel 18 BitmapData- und Filter-Klasse
mBitmap.attachBitmap(bmBitmap, 1);
18.4.2 FillRect(), floodFill()
bmBitmap.dispose();
Wie bereits angesprochen strebt so mancher Künstler nach höheren Weihen und begnügt sich nicht mit orangefarbenen Rechtecken. Zum Glück können nachträglich problemlos einzelne Bereiche bis hin zur gesamten Bitmap mit einer neuen Farbe gefüllt werden. Nehmen wir an, wir wollen die linke obere Ecke unserer Beispielgrafik mit einem kräftigen Rot füllen.
mBitmap.onPress = function() { }; }
//------------- start –------------- init();
Bei Mausklick auf das Rechteck verschwindet es. Obwohl es so aussieht, wird nicht der MovieClip, sondern nur dessen Inhalt, also die Bitmap, gelöscht. Das können wir kontrollieren, indem wir auf eine bekannte Eigenschaft des angesprochenen Objekts zugreifen. 3. Erweitern Sie das onPress-Ereignis nach dem Aufruf von dispose(): trace(bmBitmap.width);
Ausgabe im Nachrichtenfenster: –1 Wir erhalten nicht mehr 300 Pixel wie unmittelbar nach der Instanziierung, sondern nur noch –1, was bedeutet, dass das betreffende Objekt keine Ausdehnung mehr besitzt, weil es nicht gefunden werden konnte bzw. nicht mehr existiert. Erneut ist eine Gegenprobe möglich. 4. Kommentieren Sie im onPress-Ereignis den Aufruf von dispose() aus (Fettdruck): //bmBitmap.dispose();
5. Fügen Sie danach ein: this.removeMovieClip();
Ausgabe im Nachrichtenfenster: 300 Optisch ergibt sich beim Testen per Mausklick auf mBitmap kein Unterschied, aber wie die Ausgabe zeigt, existiert das BitmapData-Objekt weiterhin, denn seine Breite wird wieder mit 300 angegeben. Der Behälter dagegen, in den wir es zwecks Darstellung eingefügt haben, wurde gelöscht. In diesem Fall würde der Arbeitsspeicher weiterhin mit einer Bitmapgrafik belegt, obwohl wir sie für eine Anzeige nicht mehr weiter benötigen. Um also eine Instanz der BitmapData-Klasse vollständig zu löschen, müssen wir auf die Methode dispose() zugreifen.
6. Fügen Sie nach dem Import des display-Packages ein: import flash.geom.*;
7. Ersetzen Sie innerhalb des onPress-Ereignisses alle Anweisungen durch (Fettdruck): mBitmap.onPress = function() {
bmBitmap.fillRect(new Rectangle (0, 0, 150, 100), 0xffff0000); };
Bei Mausklick erscheint auf der bisherigen Bitmap in der linken oberen Ecke ein rotes Rechteck. Um den betreffenden Bereich definieren zu können, benötigen wir zunächst die Rectangle-Klasse, die Teil des geom-Packages ist. Dieses wird am Anfang importiert; alternativ hätten wir auch direkt die betreffende Klasse aufrufen können. Dem onPressEreignis weisen wir die fillRect()-Methode zu, der wir zwei Parameter übergeben:
• new
Rectangle(0,0,150,100), was einem
Rechteck entspricht, das in der linken oberen Ecke der Bitmapgrafik beginnt und eine Breite von 150 sowie eine Höhe von 100 Pixeln besitzt; • 0xffff0000, einen hexadezimalen Farbwert, der keine Transparenz enthält und den Rotkanal auf den höchstmöglichen Wert setzt. Der sichtbare Bereich des neu erstellten Rechtecks liegt immer innerhalb des sichtbaren Bereichs der BitmapData-Instanz. 8. Weisen Sie mBitmap vor Deklaration des onPress-Ereignisses testweise eine andere Position zu: mBitmap._x = mBitmap._y = 100;
9. Ändern Sie den ersten Parameter der fillRect()Methode (Fettdruck):
18.4 Methoden der BitmapData-Klasse
b mBitmap.fillRect(new Rectangle(-50, 0, 150, 100), 0xffff0000);
Der Behälter für das orangene Rechteck wird nach rechts und unten um jeweils 100 Pixel verschoben, so dass wir eventuell links neben der aktuellen BitmapData-Instanz gezeichnete Objekte erkennen können. Anschließend verschieben wir innerhalb von fillRect() das zu färbende Rechteck gegenüber dem Nullpunkt der Bitmapgrafik um 50 Pixel nach links. Dennoch erfolgt der Füllvorgang ausschließlich auf dem sichtbaren Teil der Grafik, was faktisch zu einer Verkürzung der Breite des roten Rechtecks um die erwähnten 50 Pixel führt. 10. Setzen Sie den geänderten Parameter der fillRect()-Methode wieder auf seinen ursprünglichen Wert zurück(Fettdruck):
mBitmap.fillRect(new Rectangle(0, 0, b 150, 100), 0xffff0000);
Neben dem gezielten Einfärben eines Rechtecks ist es damit auch leicht möglich, den ursprünglichen Zustand der BitmapData-Instanz wieder herzustellen, etwa, wenn wir einzelne Bereiche mit einer der weiter unten erörterten Methoden geändert haben. Dann reicht es aus, fillRect() mit einem der Größe der betreffenden Instanz entsprechenden Rechteck sowie der Originalfarbe aufzurufen. Für das Rechteck greift man der Einfachheit halber auf die rectangleEigenschaft des gewünschten Objekts zu. In unserem Beispiel würde die Anweisung lauten: bmBitmap.fillRect(bmBitmap.rectangle, 0xffffaa00);
Es ist also nicht unbedingt notwendig, explizit mit einem Konstruktor ein neues Rechteck zu definieren Eine Fläche lässt sich alternativ mit Hilfe von floodFill() füllen. In dem Fall haben wir es mit einer Methode zu tun, die dem bekannten Fülleimer aus Bildbearbeitungsprogrammen wie Photoshop entspricht. 11. Erweitern Sie das onPress-Ereignis nach Aufruf der fillRect()-Methode um:
mBitmap.floodFill(this._xmouse, b this._ymouse, 0xaa0000FF);
Wenn Sie beim Testen mit der Maus in die linke obere Ecke klicken, wird wie bisher ein rotes Rechteck gezeichnet. Alle übrigen Pixel dagegen färbt Flash dann
227
blau. Denn aufgrund von floodFill() werden, ausgehend von der als Parameter übergebenen Position, alle Pixel in der neuen Farbe dargestellt, die zuvor über denselben Farbwert verfügten. Das gesamte orangene Rechteck wird damit in zwei Farbbereiche aufgeteilt. Ein anderes Ergebnis erhalten Sie bei einem Klick auf einen Bereich, der bisher immer rot eingefärbt wurde. Dort erfolgt dann zwar ebenfalls die Rotfärbung, aber danach wird derselbe Bereich mit Blau gefüllt. Die umgebenden orangenen Pixel dagegen bleiben unberührt. Wenn Sie anschließend in den übrig gebliebenen orangenen Bereich klicken, taucht erneut das rote Rechteck auf, während der Rest blau eingefärbt wird. Mit einem abschließenden Klick auf das rote Rechteck wird auch dieses mit dem definierten Blauton gefüllt.
18.4.3 GetPixel(), getPixel32(), setPixel(), setPixel32() Natürlich besteht auch die Möglichkeit, ganz gezielt einzelne Pixel zu manipulieren, wobei wir mit 24- und 32-Bit-Hexwerten arbeiten können. 12. Kommentieren Sie innerhalb des onPress-Ereignisses den Aufruf der floodFill()-Methode aus: //bmBitmap.floodFill(this._xmouse, this._ymouse, 0xaa0000FF);
13. Fügen Sie innerhalb des onPress-Ereignisses nach den auskommentierten Zeilen ein:
race((bmBitmap.getPixel(100, 0)). t toString(16));
bmBitmap.setPixel(100, 0, 0 x 0000ff); race((bmBitmap.getPixel(100, 0)). t toString(16));
Nach einem Mausklick wird in einem einzigen kaum erkennbaren Punkt am oberen Rand des Rechtecks der Farbwert von Rot auf Blau geändert. Das Nachrichtenfenster gibt den betreffenden Farbwert vor und nach der Änderung aus. In der praktischen Anwendung macht es nun nicht wirklich Sinn, lediglich einen einzigen Pixel zu ändern, da der resultierende Effekt kaum auszumachen ist.
228
Kapitel 18 BitmapData- und Filter-Klasse
Abbildung 44: Erzeugung eines Punktrasters mit setPixel()
Abbildung 45: Erzeugung eines zufälligen Musters mit setPixel32()
14. Löschen Sie die eben eingefügten trace()-Anweisungen 15. Ersetzen Sie den Aufruf der setPixel()-Methode durch:
16. Ersetzen Sie die im vorhergehenden Schritt eingefügten Anweisungen vollständig durch:
var nDif:Number = 5;
for (var j:Number = 0; j in eine swf-Datei um. Sie wird automatisch in demselben Ordner gespeichert, in dem sich die zugehörige flaDatei befindet.
Kapitel 20 Externe Assets
6. Kehren Sie zu index zurück. 7. Fügen Sie in actions folgendes Bildskript ein: this.loadMovie(„main.swf“);
Beim Testen blickt uns ein harmlos wirkendes Kätzchen entgegen. Wir erstellen zwei verschiedene Dateien:
• index.swf enthält den Aufruf der externen Datei.
Wir wählen den Namen index in Anlehnung an die Startdatei einer in HTML erstellten Website, deren erste Datei normalerweise eben diese Bezeichnung trägt. • main.swf beinhaltet den konkreten Inhalt, den wir anzeigen wollen. Er kann dabei beliebiger Natur sein, egal, ob Text, Grafik, Sound, etc. Beim Start der ersten Datei wird diese in den FlashPlayer geladen und das im ersten Frame befindliche Skript ausgeführt. Es sorgt dafür, dass in die Zeitleiste des angegebenen Objekts die externe swf-Datei eingefügt wird. In unserem Beispiel handelt es sich bei der Zeitleiste um die Hauptzeitleiste des aufrufenden Films, also _root in index. In der Titelleiste des Flash-Players erkennen Sie den Namen dieser Datei, während die geladene Datei dort nicht aufgeführt wird. Das liegt daran, dass Flash die angegebene Zeitleiste vollständig durch den geladenen Film ersetzt und diesen so behandelt, als sei er ein in in dex vorliegender MovieClip. Er gilt somit nicht mehr als eigenständige swf-Datei. Abbildung 56 zeigt das bisherige Ergebnis. Die Position des geladenen Films ergibt sich aus dem Registrierungspunkt der jeweils betroffenen Dateien: Die Datei main wird mit ihrem Registrierungspunkt im Koordinatenursprung des Zielorts eingefügt. Das bedeutet konkret, dass die linke obere Ecke von main über die linke obere Ecke von index gelegt wird. Da sowohl Registrierungspunkt wie Ausdehnung beider Dateien miteinander identisch sind, verdeckt main exakt und vollständig index. Allerdings hat der Ladevorgang Konsequenzen, die sich auf unsere Anwendung in ungewünschter Weise auswirken können. 8. Zeichnen Sie in index auf objects in der linken oberen Ecke ein Rechteck (25 × 75, beliebige Farbe). 9. Wandeln Sie es in einen MovieClip um (Bibliotheksname btnLoad, Instanzname bLoad).
20.1 Laden externer swf-Dateien
253 Abbildung 56: Externer Ladevorgang in der Hauptzeitleiste
10. Ersetzen Sie den bestehenden Code durch:
bLoad.onPress = function() {
bLoad.onPress = function() {
mLaden.loadMovie(„main.swf“);
};
}
init();
_root.loadMovie(„main.swf“);
Wenn Sie beim Testen auf unseren bescheidenen Button klicken, verschwindet er und main wird angezeigt. Wir weisen bLoad ein onPress-Ereignis zu, das bei Auftreten den externen Ladevorgang auslöst. Da die loadMovie()-Methode vollständig den Inhalt der betroffenen Zeitleiste durch den Inhalt des geladenen Films ersetzt, wird unsere Schaltfläche allerdings gelöscht. Denn sie befindet sich ja wie der zu ladende main.swf in _root. Daher müssen alle Objekte, die beibehalten werden sollen, getrennt von den extern zu ladenden Dateien abgelegt werden. 11. Ersetzen Sie den bisherigen Code durch:
//------------ vars –--------------–
//---------- functions –------------
};
//––---------- start –--------------
Beim Klicken bleibt unser Button erhalten. Im Variablen-Block deklarieren wir eine Variable, die in der init()-Funktion mit einer Referenz auf einen neu erstellten, leeren MovieClip gefüllt wird. In diesen Clip laden wir bei Mausklick auf bLoad die gewünschte externe Datei. Dadurch erreichen wir, dass die Hauptzeitleiste in index vom Ladevorgang nicht betroffen ist und somit alle dort vorhandenen Elemente erhalten bleiben. Da wir auch an späterer Stelle mit derartigen Ladevorgängen zu tun haben werden, sollten wir unseren Ladeaufruf etwas flexibler halten.
var mLaden:MovieClip;
12. Fügen Sie im Funktions-Block folgende Deklaration ein:
function init() {
function laden(pWas:String, pZiel:MovieClip):Void {
mLaden = this.createEmptyMovieClip („behExternSWF“, this.getNextHigh estDepth());
pZiel.loadMovie(pWas);
}
254
Kapitel 20 Externe Assets
13. Ersetzen Sie in der Deklaration von init() im onPress-Ereignis den Ladeaufruf durch:
laden(„main.swf“, mLaden);
Das Ergebnis bleibt das Gleiche wie zuvor. Doch nun können wir auch von anderer Stelle aus einen Ladeaufruf starten und zudem ein neues Ziel angeben, was notwendig ist, sobald wir eine komplexere Anwendung wie eine Website erstellen wollen. Wir legen die Funktion laden() an, die über zwei Parameter verfügt:
• pWas, die als String übergebene Datei, die wir la-
den wollen. Im konkreten Fall verzichten wir auf eine Pfadangabe, was bedeutet, dass sie sich im selben Ordner befindet wie die aufrufende Datei; • pZiel, diejenige Zeitleiste bzw. der MovieClip, in den die angegebene Datei geladen werden soll. Bei Klick auf unseren Button rufen wir die genannte Methode auf und übergeben als Ziel den leeren MovieClip sowie als zu ladende Datei main.swf. Wenn der geladene Film als in den aufrufenden Film integrierten MovieClip behandelt wird, dann hat das auch Folgen in Bezug auf die Adressierung der Zeitleisten: In einem Flash-Film existiert immer nur ein Objekt mit der Bezeichnung _root. Was geschieht jedoch, wenn sowohl der aufrufende wie auch der geladene Film _root ansprechen? 14. Wandeln Sie die in main.fla importierte Grafik in einen MovieClip um (Bibliotheksname mcBild, Instanzname bild, Registrierungspunkt links oben). 15. Weisen Sie in main.fla der Ebene actions folgendes Bildskript zu:
//------------- import –------------
import flash.filters.*;
var fSchatten:DropShadowFilter;
//------------- vars –-------------//------------ functions –---------function init() {
fSchatten = new DropShadowFil ter(5,45,0 × 000000,0.9,5,5,1,3);
bild.filters = [fSchatten];
//------------- start –-------------
}
init();
Wenn Sie nun unmittelbar die index-Datei testen, werden Sie keinen Unterschied feststellen. Das liegt einfach daran, dass wir zwar die Änderungen in main gespeichert, aber daraus keine neue swf generiert haben. Daher lädt Flash die vorhergehende main.swf. 16. Erstellen Sie aus der main.fla eine entsprechende main.swf. Beim erneuten Testen der index-Datei zeigt die eingefügte Grafik in main einen leichten Schlagschatten. Wir importieren die Filter-Klasse und deklarieren eine Variable zur Aufnahme einer Filter-Instanz, die in der Funktion init() mit den gewünschten Eigenschaften eingerichtet wird. In unserem Fall handelt es sich um einen simplen Schatten, der in einem Abstand von 5 Pixeln und einem Winkel von 45 Grad mit einem schwarzen, auf 90 % Deckkraft gesetzten, weich gezeichneten Rechteck dargestellt wird. Wir weisen ihn dem mit dem Instanznamen bild versehenen MovieClip, der nun unsere Grafik enthält, zu. Mitunter ist es notwendig, einen unmittelbaren Bezug auf eine Hauptzeitleiste herzustellen. In dem Fall müssten wir bild anders ansprechen. 17. Ändern Sie in der Deklaration von init() die Zuweisung der filters-Eigenschaft (Fettdruck):
_root.bild.filters = [fSchatten];
18. Erstellen Sie daraus erneut eine swf. Der Schlagschatten ist wie zuvor zu erkennen. 19. Testen Sie index. Der Schlagschatten wird nicht mehr angezeigt. Abbildung 57 stellt beide Situationen gegenüber. Solange wir main als unabhängige swf testen, gibt es keine Probleme mit der Darstellung des Schattens. Sowohl die absolute Pfadangabe mit _root wie diejenige ohne _root ermöglicht es, bild den gewünschten Filter zuzuweisen. In dem Augenblick jedoch, in dem wir main in eine andere Datei laden, verliert sie ihre Unabhängigkeit und wird zu einem gewöhnlichen MovieClip innerhalb einer anderen, übergeordneten Zeitleiste. Dadurch ändert sich auch der Bezug von _root, der nun auf eben diese neue Zeitleiste verweist. 20. Fügen Sie in index in der Deklaration von init() unmittelbar nach der Zuweisung von mLaden ein:
mLaden._lockroot = true;
20.1 Laden externer swf-Dateien
255 Abbildung 57: Auswirkungen von _root auf externe Ladeprozesse
Beim Testen bleibt der Schatten wie gewünscht erhalten. Wir weisen Flash an, direkt nach dem Einrichten des leeren MovieClips dessen Zeitleiste zu sperren, so dass in der zu ladenden Datei enthaltene Bezüge auf _root immer den MovieClip selbst ansprechen. Alternativ kann das _lockroot in die zu ladende Datei aufgenommen werden. Dabei müssen Sie auf die Schreibweise sowie die Stelle des Aufrufs achten. Falsch wäre in main ganz am Anfang des Codes: _root._lockroot = true;
Denn in dem Augenblick, wo diese Datei geladen wird, ist _root mit der Hauptzeitleiste der aufrufenden Datei identisch. Der Befehl würde sich also bereits auf die Zeitleiste von index beziehen, was eben nicht gewünscht ist. Statt dessen müssten Sie schreiben: this._lockroot = true;
Mit this wird automatisch die korrekte Zeitleiste ermittelt. Wichtig ist in diesem Fall die korrekte Position, d. h. möglichst am Anfang des Code-Blocks und selbstverständlich vor der Zuweisung des Filters an _root.bild. Die einfachste Variante speichert den Bezug so, wie wir es ohnehin mit anderen MovieClips bereits gewohnt sind, nämlich in eine Variable. 21. Erweiteren Sie in main den Variablen-Block um:
Während der Test keinen Unterschied zu vorher ergibt, legen wir eine Variable an, in die wir beim Aufruf von init() die Hauptzeitleiste des aktuellen Films speichern. Anschließend benutzen wir diese Variable, um den Filter einem Objekt zuzuweisen, das sich auf der Hauptzeitleiste des zu ladenden Films befindet. Um keine Missverständnisse aufkommen zu lassen: Hier ging es ausschließlich um die Frage, welche Auswirkungen die Verwendung von _root in verschiedenen Zeitleisten besitzt. Wir haben ein einfaches Beispiel gewählt, das selbstverständlich auch ohne Bezug auf _root funktioniert. Er wurde nur zu Demonstrationszwecken hergestellt. Wir haben bisher mit einer flachen Ordnerstruktur gearbeitet, die man so zumindest bei etwas größeren Projekten nicht findet. Es fördert nämlich nicht unbedingt die Übersichtlichkeit, wenn man mit Hunderten oder gar Tausenden von Dateien arbeiten muss, die alle im selben Verzeichnis liegen. Existiert eine komplexere Ordnerstruktur vor und es werden aus mehreren, in verschiedenen Verzeichnissen abgelegten Dateien externe Ladevorgänge gestartet, stellt die Pfadangabe eine potentielle Fehlerquelle dar. 1. Legen Sie innerhalb des Ordners modular folgende Unterordner an, wie sie in Abbildung 58 dargestellt sind.
var mRoot:MovieClip;
22. Fügen Sie in main in die Deklaration von init() unmittelbar nach der öffnenden Klammer ein:
mRoot = this;
23. Ändern Sie in main die Zuweisung der filtersEigenschaft (Fettdruck):
mRoot.bild.filters = [fSchatten];
Abbildung 58: Verschachtelte Ordnerstruktur für externe Ladevorgänge
256
2. Kopieren Sie die bisherige main.fla in den Ordner site/kapitel/main. 3. Kopieren Sie in den Ordner site/bilder/main eine beliebige Grafik. Wir verwenden beispielhaft gepard7.jpg. 4. Kopieren Sie die bisherige index.fla in den Ordner site. 5. Fügen Sie in index im Variablen-Block folgende Initialisierung ein: var sPfad:String = „kapitel/main/“
6. Ändern Sie in index in der Deklaration von init() den Aufruf von laden() (Fettdruck): laden(sPfad+„main.swf“, mLaden);
Die Anwendung funktioniert bei einem Test wie gewohnt. Die Dateien befinden sich nicht mehr in demselben Ordner. Daher müssen wir Flash mitteilen, wo sie zu finden sind. Verzichten wir darauf, erhalten wir bei Mausklick auf den Button die in Abbildung 59 gezeigte Fehlermeldung: Glücklicherweise gibt uns Flash direkt den Pfad an, auf den zugegriffen wurde, so dass wir leicht kontrollieren können, ob er mit dem übereinstimmt, was wir geskriptet haben. In unserem konkreten Beispiel speichern wir im Variablen-Block den benötigten Pfad und übergeben ihn im onPress-Ereignis an der Stelle, an der die betreffende Datei geladen wird. 7. Löschen Sie in main das Bild der possierlichen Katze sowohl auf der Bühne als auch aus der Bibliothek.
Kapitel 20 Externe Assets
8. Ersetzen Sie in main das Skript vollständig durch: //------------ vars –----------------
var mRoot:MovieClip, mBild:MovieClip; //---------- functions –------------ function init() { mRoot = this;
mBild = this. createEmptyMovieClip(„behBild“, mRoot.getNextHighestDepth()); mBild._x = 100;
mBild.loadMovie(„../../bilder/main/ gepard1.jpg“); }
//------------ start –-------------- init();
Wenn Sie main testen, können Sie wieder die Katze sehen. Die einzigen Änderungen gegenüber vorher bestehen in der Positionierung (horizontal explizit auf 100, vertikal automatisch auf 0) und einem modifizierten Pfad. Um zum richtigen Ordner zu gelangen, müssen wir ausgehend vom aktuellen Speicherort zunächst zwei Ebenen nach oben gehen. Denn Ordner sind hierarchisch aufgebaut, d. h. sie bilden eine Unterordnung ab. In einer derartigen Struktur kann man sich immer nur vertikal, nie jedoch horizontal bewegen. Um Ordner ansprechen zu können, die sich auf derselben Hierarchieebene befinden, muss man zunächst eine Ebene höher gehen, bevor man von dort aus nach unten auf den gewünschten Ordner zugreifen kann. Nehmen wir der Einfachheit halber an, wir befinden uns im Ordner
Abbildung 59: Fehlermeldung bei fehlgeschlagenem Ladeaufruf
20.2 Eigenschaften der geladenen Dateien
kapitel und wollten auf den Ordner bilder zugrei-
fen, der sich innerhalb der Hierarchie auf derselben Ebene befindet. Der korrekte Pfad würde dann lauten: ../bilder/Datei. Aufgrund der hierarchischen Struktur können wir nicht direkt auf bilder zugreifen bzw. den Pfad bilder/Datei verwenden. Das würde nämlich so interpretiert, als existiere am aktuellen Standort ein Unterordner namens bilder. Im konkreten Fall müssen wir gleich zwei Ordner nach oben und zwei Ordner nach unten gehen, bevor wir zur gewünschten Grafik gelangen. Der Test über index und Mausklick hält allerdings eine Überraschung bereit: Anstelle der Grafik sehen wir eine Fehlermeldung, derzufolge die aufgerufene Datei nicht gefunden werden konnte. Wenn Sie den angegebenen Pfad beachten, erkennen Sie, dass Flash nun vom Speicherort von index, nicht jedoch wie vorher von main ausgegangen ist. In dem Fall stimmt der Pfad natürlich nicht mehr, da Flash zunächst um zwei Ebenen nach oben geht und damit längst das Verzeichnis unserer Site verlassen hat. Dieses Vorgehen ist allerdings völlig korrekt, denn main gilt, wie bereits mehrfach gesehen, als abhängiger MovieClip und nicht mehr als unabhängige swf. Daher wird der in main verwendete Pfad so ausgelesen, als befänden wir uns bereits in index. 9. Korrigieren Sie den Pfad in main (Fettdruck): mBild.loadMovie(„bilder/main/gepard1.jpg“);
10. Erzeugen Sie eine swf-Datei und ignorieren Sie nervenstark die auftauchende Fehlermeldung (an dieser Stelle dürfen Sie das ausnahmsweise tun). Flash beschwert sich zu Recht, denn der Pfad stimmt nicht mehr, wenn wir von main ausgehen. 11. Testen Sie index. Nun erhalten Sie wieder eine korrekte Darstellung bei Mausklick, da der Pfad an index angepasst wurde. Beachten Sie also bei derart verschachtelten Aufrufen externer Daten unbedingt die Pfadangaben. Da sich Flash (korrekterweise) so verhält, ist das Testen der Ladevorgänge nur aus der ersten aufrufenden Datei heraus sinnvoll möglich. Um die extern geladenen Dateien wieder loszuwerden, gibt es gleich mehrere Möglichkeiten:
• Wie im Fall von attachMovie() kann auch hier in ein und dasselbe Ziel immer nur ein einziges Ob-
257
jekt geladen werden. Wenn Sie also in mLaden eine andere Datei laden, wird eine dort bereits vorhandene einfach überschrieben und damit gelöscht. • Mit der Methode unloadMovie() lässt sich explizit Inhalt entfernen, ohne dass an dessen Stelle neuer Inhalt tritt. Im konkreten Beispiel würde der Befehl lauten: mLaden.unloadMovie(). • Wie bei attachMovie() können wir eine radikale Lösung wählen, sprich: Den Behälter einfach löschen, was notwendigerweise seinen Inhalt mit in den digitalen Abgrund reißen würde. In unserem Beispiel müssten Sie schreiben: mLaden.remo veMovieClip(). Achten Sie dann darauf, dass vor einem erneuten Laden der gelöschte MovieClip wieder als leerer Clip eingerichtet werden muss, ansonsten fehlt ja der Behälter für einen weiteren Ladevorgang.
20.2 Eigenschaften der geladenen Dateien Wenn die geladenen Dateien als MovieClip gelten, beeinflusst das notgedrungen diejenigen Eigenschaften, die wir für den Film insgesamt bzw. die Hauptzeitleiste festgelegt haben, nämlich die Bildwiederholrate, die horizontale und vertikale Ausdehnung sowie die Hintergrundfarbe. Die aufrufende Datei legt für alle geladenen Dateien diese Eigenschaften fest, unabhängig davon, welche Einstellungen anderweitig getroffen wurden. Wenn also beispielsweise in dex über einen grauen Hintergrund verfügt, wird der weiße Hintergrund in main ignoriert. 1. Erstellen Sie in den aktuellen Ordnern jeweils eine Kopie von index und main. 2. Nennen Sie die Kopien um in index1 bzw. main1. 3. Ändern Sie in index1 die Hintergrundfarbe zu cccccc (helles Grau). 4. Ändern Sie in index1 in der Deklaration von init() den Ladeaufruf (Fettdruck): laden(sPfad+„main1.swf“, mLaden);
Beide Dateien werden nach Mausklick mit einem hellgrauen Hintergrund dargestellt. Maßgeblich ist die Festlegung in index1, da wir von dort aus main1 laden. Dessen spezifische Einstellungen für die Hauptzeitleiste werden beim Aufruf
258
einfach überschrieben, so dass der weiße Hintergrund verschwindet. Möchten Sie dennoch für die zu ladende Datei einen eigenen Hintergrund definieren, so geht das am einfachsten, indem Sie eine entsprechend eingefärbte Grafik auf der untersten Ebene von dessen Hauptzeitleiste platzieren. 5. Reduzieren Sie in index1 die Breite der Bühne auf 200 Pixel. Das zugegebenermaßen gewöhnungsbedürftige Maß führt dazu, dass unsere Katze beim Laden nur noch partiell zu sehen ist, wie in Abbildung 60 zu erkennen. Die verschiedenen Größen führen dazu, dass wir die geladene Datei quasi durch ein von index1 vorgegebenes Fenster betrachten, gerade so, als sei ein Teil von ihr maskiert. Zu sehen ist nur der Bereich, der sich mit index1 überschneidet. Beachten Sie, dass dies nur gilt, wenn Sie nicht direkt in _root, sondern in einen eigenen Behälter laden. Andernfalls nämlich
Kapitel 20 Externe Assets
passt sich die Größe der aufrufenden Datei derjenigen der geladenen Datei an. 6. Um ein vernünftiges Arbeiten zu ermöglichen, stellen Sie die Größe wieder auf das ursprüngliche Maß zurück. Da mLaden in index1 als Behälter dient, können wir Eigenschaftsänderungen an der zu ladenden Datei auch indirekt über diesen Behälter realisieren. 7. Löschen Sie in main1 in der Deklaration von init() diejenige Zeile, die mBild eine horizontale Position zuweist (mBild._x = 100). 8. Generieren Sie eine swf. 9. Ergänzen Sie in index1 die Deklaration von init() unmittelbar nach der Initialisierung von mLaden: mLaden._x = 100;
Die Positionierung nehmen wir nun nicht mehr in main1 dadurch vor, dass wir dort dem Behälter für das Bild eine horizontale Position zuweisen. Statt dessen belassen wir die Werte auf den Standardwerten, also 0. In index1 verschieben wir nun den für die externe Datei zuständigen Behälter um exakt den Betrag, den wir zuvor für die Position von mBild in main1 verwendeten. Das visuelle Ergebnis bleibt gleich. Welchen Weg Sie für eine Positionierung wählen, bleibt in diesen Fällen letztlich Ihrem Geschmack überlassen, da beide faktisch gleichwertig sind. Oft möchte man über bestimmte Eigenschaften hinaus auch Interaktionen definieren, so dass der Anwender beispielsweise per Klick auf die geladene Datei diese entfernen, ihren Inhalt oder eine ihrer Eigenschaften ändern kann. 10. Ergänzen Sie in main1 die Deklaration von init() vor der schließenden Klammer:
mRoot.onPress = function(){
}
this._x += 50;
11. Generieren Sie eine swf.
Abbildung 60: Eigenschaftsänderungen bei externen Ladevorgängen
Wenn Sie nun ganz verzweifelt auf der erzeugten swf-Datei herum klicken, ohne dass sich eine sichtbare Veränderung einstellt, so ist das völlig korrekt, schließlich setzt das onPress-Ereignis im zugeordneten Objekt visuellen Inhalt voraus, der einen Klick wahrnehmen kann. Anders stellt sich die Situation dar, sobald über index1 der externe Ladevorgang ausge-
20.2 Eigenschaften der geladenen Dateien
259
löst wird. Da mRoot aufgrund von loadMovie() mit mLaden identisch ist und dort eine Grafik angezeigt wird, können wir per Klick auf sie eine horizontale Bewegung erzeugen. Tatsächlich wird dabei jedoch nicht das Bild, sondern der übergeordnete MovieClip bzw. Behälter mLaden bewegt. Das können Sie testen, indem Sie nach mehrmaligem Verschieben erneut auf den Button in index1 klicken. Dann wird main1 neu geladen, befindet sich aber durch unser wiederholtes Klicken rechts von der ursprünglichen Position. Etwas anders gestaltet sich der Ablauf, wenn wir beispielsweise in main1 externe Daten laden und von dort aus mit ihnen interagieren wollten. Das wäre etwa bei einer Galerie der Fall, die externe Bilder lädt. Durch Mausklick sollen sie wieder entladen, alternativ das nächste Bild geladen werden. Das Problem dabei besteht darin, dass wir in die Grafik selbst kein Skript für einen Mausklick integrieren können. Das geht nur, indem wir dem MovieClip, in den wir laden, ein entsprechendes Ereignis zuweisen.
Es wird unsere geliebte Katze in der linken obere Ecke angezeigt. Wir speichern zunächst den benötigten Pfad in einer Variablen. Da wir mehrere Bilder laden wollen, legen wir in einem Array deren Namen fest. Noch flexibler wird man, wenn diese Namen aus einer externen Datei eingelesen werden, wie im Workshop zur Bildergalerie gezeigt. Um das erste Bild anzeigen zu können, initialisieren wir nBildIndex mit 0. In der init()-Funktion referenzieren wir in mBild einen leeren MovieClip, in den der Ladevorgang ausgeführt wird. Dabei bedienen wir uns derselben Ladefunktion, die wir zuvor in index definiert haben. An laden() übergeben wir als Parameter dasjenige Element des Bilder-Arrays, das sich auf der durch nBildIndex festgelegten Position befindet. Am Anfang handelt es sich dabei notwendigerweise um das erste Element. Nun möchten wir den Behälter abhängig von der Bildbreite in der Bühnenmitte positionieren und bei Klick auf die Grafik das nächste Bild laden.
1. Erstellen Sie eine Standarddatei und speichern sie im Ordner site als extBilder.fla ab. 2. Kopieren Sie in den Ordner site/bilder/main zwei weitere beliebige Grafiken. Wir verwenden beispielhaft gepard1.jpg und gepard5.jpg. 3. Fügen Sie in actions folgendes Bildskript ein:
4. Ergänzen Sie die init()-Funktion nach dem Ladeaufruf um:
//-----------––– vars –------------- var mBild:MovieClip;
var sBildPfad:String = „bilder/ main/“; var nBildIndex:Number = 0;
v ar aBilder:Array = [„gepard1.jpg“, „gepard5.jpg“, „gepard7.jpg“];
//------------- functions –--------- function init() {
mBild = this.createEmptyMovie Clip(„behBild“, this.getNextHigh estDepth()); laden(sBildPfad+aBilder[nBild Index], mBild); }
function laden(pWas:String, pZiel:MovieClip):Void { pZiel.loadMovie(pWas); }
//------------ start –-------------- init();
mBild._x = Stage.width/2 – mBild._ width/2;
Das Bild wird, wenn nicht alles täuscht, keinesfalls exakt in der Bühnenmitte, sondern nach rechts verschoben angezeigt, wie Abbildung 61 verdeutlicht. Zwei Gründe sind dafür verantwortlich:
• Ein Ladevorgang wird unabhängig von seiner Posi-
tion im Skript immer zum Schluss ausgeführt. Die Breite des MovieClips kann daher bei der Zuweisung der horizontalen Position nur 0 betragen. • Erschwerend kommt hinzu, dass ein externer Ladevorgang immer Zeit benötigt. Dies sieht zwar momentan anders aus, schließlich taucht die Grafik sofort auf. Aber das liegt an der Testumgebung, die lokal abläuft. Würde die Grafik über ein Netzwerk, etwa über das Internet, geladen, träte eine Verzögerung auf. Unsere Positionsberechnung ergibt daher als Wert halbe Bühnenbreite minus die Hälfte von 0, was faktisch eben der halben Bühnenbreite entspricht. An dieser Stelle liegt der MovieClip und die Grafik wird dort mit der linken oberen Ecke eingefügt. Daher benötigen wir eine Möglichkeit, den Abschluss des Ladevorgangs zu kontrollieren, um danach die korrekte Position herauszufinden.
260
Kapitel 20 Externe Assets
Abbildung 61: Fehlerhafte Positionierung einer extern geladenen Grafik
Es bieten sich gleich mehrere Möglichkeiten an, von denen wir uns zwei im nächsten Abschnitt über Preloader näher anschauen werden. Hier soll nur eine recht einfache zur Sprache kommen, die so eigentlich gar nicht von dem Hersteller von Flash gedacht gewesen ist. 5. Erweitern Sie den Variablen-Block (Fettdruck): var mBild:MovieClip, mCode:MovieClip;
6. Erweitern Sie die Deklaration von init() nach der Initialisierung von mBild: mCode = this. createEmptyMovieClip(„behCode“, this. getNextHighestDepth());
7. Ändern Sie die Deklaration von init() unmittelbar nach dem Aufruf des Ladevorgangs (Fettdruck): mCode.onEnterFrame = function() { if (mBild._width>10) {
trace(„habefertisch!“);
mBild._x = Stage.width/2-mBild._ width/2; delete this.onEnterFrame; } };
Bei einem Test wird das Bild in der Bühnenmitte angezeigt. Wir nutzen die Tatsache aus, dass die zu ladende Datei visuellen Content enthält. Zunächst richten wir wieder unsere Variable zur Aufnahme einer Referenz auf einen leeren MovieClip ein, der zusätzlich in init() erzeugt wird. Wir benötigen ihn, um ihm ein onEnterFrame-Ereignis zur Kontrolle des Ladevorgangs zuzuweisen. Dort lesen wir permanent die Breite des Ladebehälters aus. Erst wenn diese einen bestimmten Wert überschritten hat (hier können Sie prinzipiell alles wählen, was größer 0 und kleiner der tatsächlichen Bildbreite ist), wissen wir, dass der Ladevorgang erfolgreich beendet wurde. In diesem Fall streamt Flash nicht, so dass es nur zwei mögliche Werte für die abgefragte Breite gibt: 0 und Bildbreite. Ist letztgenannter Wert erreicht, gibt die if-Bedingung true zurück und der abhängige Code wird ausgeführt, mit dem wir ein trace() ausgeben, die Positionierung korrekt vornehmen und das nicht mehr benötigte Ereignis löschen. Einen genaueren Einblick in den Ablauf gewinnen wir, wenn Flash den Download simuliert. 8. Generieren Sie wie gewohnt eine swf-Datei. 9. Wählen Sie im am oberen Rand eingeblendeten Menü . Das Ergebnis zeigt Abbildung 62.
20.2 Eigenschaften der geladenen Dateien
261
Abbildung 62: Bandbreiten-Profiler zur Kontrolle des Ladevorgangs
10. Wählen Sie im Menü , wie in Abbildung 63 gezeigt. 11. Wählen Sie im Menü .
function ladeKontrolle(pZiel:Movie Clip):Void {
Nun zeigt Flash im Bandbreiten-Profiler den Fortschritt eines Ladevorgangs, der über ein eher älteres Modem mit Handkurbel stattfindet. Abbildung 64 zeigt einen Ausschnitt aus dem Ladevorgang. Wie Sie sehen können, verstreicht eine deutlich wahrnehmbare Zeit, während derer die benötigten Datenpakete geladen werden.
trace(„habefertisch!“);
12. Erstellen Sie im Funktions-Block folgende Deklaration: function ladeKontrolle(pZiel:Movie Clip):Void {
}
13. Verschieben Sie aus init() alle Zeilen von mCode.onEnterFrame = function(){ bis zur zugehörigen schließenden Klammer in diese Deklaration (Fettdruck):
mCode.onEnterFrame = function() { if (mBild._width>10) {
mBild._x = Stage.width/2mBild._width/2; delete this.onEnterFrame; } };
}
14. Ersetzen Sie in der Deklaration von ladeKont rolle() mBild durch pZiel (Fettdruck): function ladeKontrolle(pZiel:Movie Clip):Void {
mCode.onEnterFrame = function() { if (pZiel._width>10) {
trace(„habefertisch!“);
pZiel._x = Stage.width/2pZiel._width/2;
262
Abbildung 63: Konfiguration der Download-Simulation
Abbildung 64: Simulation eines externen Ladevorgangs
Kapitel 20 Externe Assets
20.3 Anzeigen der Ladekontrolle (Preloader mit getBytesLoaded())
delete this.onEnterFrame; }
263
20.3 Anzeigen der Ladekontrolle (Preloader mit getBytesLoaded())
};
}
15. Rufen Sie die neue Funktion innerhalb von la den() vor der schließenden Klammer auf:
ladeKontrolle(mBild);
Indem wir die gesamte Kontrolle des Ladevorgangs ausgelagert haben, können wir diese nun zu einem beliebigen Zeitpunkt ausführen. Der Einfachheit halber lassen wir sie jedes Mal ausführen, sobald die Ladefunktion aufgerufen wird. Den direkten Bezug auf mBild ersetzen wir in der Funktion ladeKon trolle() durch den Parameter pZiel, so dass wir auch andere MovieClips als Ziel übergeben können. 16. Ergänzen Sie die Deklaration von ladeKont rolle() unmittelbar vor delete um:
pZiel.onPress = function() {
nBildIndex++;
if (nBildIndex>=aBilder.length) { nBildIndex = 0; }
laden(sBildPfad+aBilder[nBild Index], this); };
Sie können jetzt nach Herzenslust laden und jeweils den Ladevorgang kontrollieren. Durch einen Mausklick wollen wir das jeweils nächste Bild laden. Das geht jedoch nur, wenn ein bestehender Ladevorgang vollständig abgeschlossen wurde. Weisen wir vorher mBild ein Ereignis zu, wird es durch loadMovie() überschrieben. Daher müssen wir das betreffende Ereignis in die Funktion zur Ladekontrolle verschieben und können es erst innerhalb des von der if-Bedingung abhängigen Codes definieren. Wir legen dort fest, dass ein Klick auf den Bild-Behälter die Index-Variable inkrementiert. Ist sie größer oder zumindest gleich der Länge des BilderArrays, haben wir bereits das letzte Bild angezeigt und setzen sie wieder zurück auf den Ausgangswert. Abschließend erfolgt derselbe Ladeaufruf wie bereits in der init()-Funktion.
Wie wir gesehen haben, zwingt uns die zeitliche Verzögerung, die beim Laden auftritt, in manchen Fällen dazu, den gesamten Prozess zu kontrollieren. Sollte dieser Prozess längere Zeit in Anspruch nehmen, so empfiehlt sich aus Gründen der Usability ein entsprechendes Feedback an den User. Andernfalls könnte der Eindruck entstehen, die Anwendung sei abgestürzt, da sich ihr aktueller Zustand in visueller Hinsicht nicht ändert. In solchen Fällen kommt der berühmte Preloader zum Einsatz, der grafisch oder zumindest textuell über den Fortschritt des Ladevorgangs informiert. ActionScript bietet mehrere Möglichkeiten, den Verlauf eines externen Ladevorgang zu protokollieren. Die älteste Methode vergleicht die Anzahl der aktuell geladenen Frames mit der Gesamtanzahl der Frames, jeweils bezogen auf die swf-Datei, die geladen wird. Ein solches Vorgehen ist heute allenfalls noch von historischem Interesse und sollte nicht mehr verwendet werden. Denn sie setzt voraus, dass ein Flashfilm mehr als einen Frame umfasst und dass der Content gleichmäßig über alle Frames verteilt ist. Trifft Letzteres nicht zu, kommt es beim Anzeigen des Ladefortschritts zu unschönem Ruckeln. Wenn beispielsweise der erste Frame 50 % des Contents umfasst, zeigt der Ladebalken lange Zeit nichts an, um dann plötzlich auf die 50 %- Marke zu springen. Eine Alternative bieten die getBytesLoaded()und getBytesTotal()-Methoden, die unabhängig von der konkreten Verteilung des Contents lediglich die Bytes kontrollieren, sowie die MovieClipLoaderKlasse. Obwohl Letztere erheblich größere Funktionalität besitzt, wird die ältere Methode mit get BytesLoaded() immer noch sehr viel verwendet, da sie ausgesprochen einfach funktioniert. Wir werden nachfolgend beide Varianten vorstellen. Die ältere Methode lässt sich mit Hilfe des Codes aus der vorherigen Übung sehr einfach umsetzen. 1. Speichern Sie die Datei aus der vorhergehenden Übung im Ordner site unter dem Namen pre loader1 ab. Alternativ erstellen Sie ein Standarddatei mit folgendem Bildskript: //-------------- vars –--------------
var mBild:MovieClip, mCode:MovieClip;
var sBildPfad:String = „bilder/main/“;
264
Kapitel 20 Externe Assets
var nBildIndex:Number = 0;
dessen dritter Zeile. Genau an diese Stelle können wir unter Beibehaltung des ganzen übrigen Codes die veränderte Kontrolle eintragen.
//----------- functions –------------
2. Ändern Sie in der Deklaration von ladeKont rolle() die if-Bedingung unmittelbar nach der öffnenden Klammer des onEnterFrame-Ereignisses (Fettdruck):
v ar aBilder:Array = [„gepard1.jpg“, „gepard5.jpg“, „gepard7.jpg“]; function init() {
mBild = this. createEmptyMovieClip(„behBild“, this.getNextHighestDepth()); mCode = this. createEmptyMovieClip(„behCode“, this.getNextHighestDepth());
laden(sBildPfad+aBilder[nBildIndex], mBild); }
function laden(pWas:String, pZiel:MovieClip):Void { pZiel.loadMovie(pWas); ladeKontrolle(pZiel); }
function ladeKontrolle(pZiel):Void { mCode.onEnterFrame = function() { if (pZiel._width>10) {
trace(„habefertisch!“);
pZiel._x = Stage.width/2pZiel._width/2;
pZiel.onPress = function() { nBildIndex++;
if (nBildIndex>=aBilder. length) { nBildIndex = 0; }
laden(sBildPfad+aBilder[nBild Index], this); };
delete this.onEnterFrame; } };
function ladeKontrolle() {
mCode.onEnterFrame = function() { if (mBild.getBytesLoaded ()>=mBild.getBytesTotal()) {
Ein Test mit Hilfe der Download-Simulation ergibt prinzipiell das gleiche Ergebnis wie in der vorhergehenden Version. Anstelle der Breite kontrollieren wir unmittelbar den Ladefortschritt anhand der aktuell geladenen Bytes im Vergleich zu den insgesamt zu ladenden Bytes. Den letztgenannten Wert ermittelt Flash automatisch beim Aufruf des Ladevorgangs. Erst dann, wenn beide Werte mindestens übereinstimmen, wurden alle Daten geladen und können wir auf den Content zugreifen. Soweit waren wir vorher zwar auch schon, doch bietet uns diese Vorgehensweise die Möglichkeit, herauszufinden, wie viele Daten zu einem beliebigen Zeitpunkt während des Ladevorgangs bereits geladen wurden. Die benötigte Information wird in der getBytesLoaded()-Methode zur Verfügung gestellt. Damit lässt sich ein unmittelbares Feedback an den wartenden User geben. Eine kleine Ungenauigkeit muss noch korrigiert werden: Da der erste Frame Flash-intern anders etwas ausgeführt wird als alle anderen Frames, kommt es hier zu einer ungewünschten Behandlung des Ladevorgangs. Denn weder die Breite des Behälters wird nach Abschluss des Ladens korrekt erkannt noch reagiert er auf Mausklicks. Flash fügt das Bild wie bei unseren ersten Ladeversuchen rechts von der Mitte ein.
}
3. Verschieben Sie das komplette Skript in Frame 2 der Hauptzeitleiste. 4. Fügen Sie ganz am Ende des Skripts ein:
init();
stop();
Wie wir gesehen haben erfolgt hier die Kontrolle des Ladevorgangs einfach über die Breite des Behälters für den externen Content. Die entsprechende Stelle findet sich in der Funktion ladeKontrolle() in
Jetzt funktioniert die Ladekontrolle wie gewünscht. Der stop()-Befehl ist notwendig, da unsere Zeitleiste nun mehr als einen Frame umfasst. In solchen Fällen loopt Flash automatisch, was dazu führen würde, dass
//––----------- start –--------------
20.3 Anzeigen der Ladekontrolle (Preloader mit getBytesLoaded())
265
die Ladefunktion ständig aufgerufen würde, ohne jemals abgeschlossen zu werden. Durch den genannten Befehl erzwingen wir ein Anhalten im zweiten Frame. Sollte dennoch gelegentlich – ja, auch Flash hat seine schwachen Momente – die Breite falsch ausgelesen werden, so können Sie sicherheitshalber nach der bisherigen if-Bedingung die vorherige hinsichtlich der Breite zusätzlich einfügen (Fettdruck):
konkreten Inhalt anzeigen wollen, positionieren wir mCode an einer Stelle, die ungefähr derjenigen des geladenen Bildes entspricht.
if (pZiel.getBytesLoaded()>=pZiel.get BytesTotal()) {
Ein Test mit eingeschalteter Simulation zeigt einen zugegebenermaßen noch recht bescheiden dreinschauenden, dafür aber recht schnell hochzählenden Text an, in dem wir die Anzahl der geladenen Bytes sehen können. Dies erreichen wir, indem in das Feld mit Hilfe der Methode getBytesLoaded() der aktuelle Wert hinein geschrieben wird. Das ist allerdings noch nicht wirklich aussagekräftig: Woher soll der Anwender wissen, was da angezeigt wird? Und welche Aussagekraft hat der Text in Bezug auf die Gesamtgröße der Daten?
if (pZiel._width>10) {
Die schließende Klammer muss dann nach der Anweisung delete this.onEnterFrame; gesetzt werden. Das ist nun ein bisschen Overkill, aber es bietet die Sicherheit, im Fall der Fälle eine korrekte Positionierung durchzuführen. Hinsichtlich der Zuweisung des onPress-Ereignisses existiert dieses Problem nicht. 5. Erweitern Sie den Variablen-Block:
7. Fügen Sie in der Deklaration von ladeKont rolle() unmittelbar nach der öffnenden Klammer des onEnterFrame-Ereignisses ein: tProzent.text = String(pZiel.get BytesLoaded());
var tProzent:TextField;
8. Fügen Sie in der Deklaration von ladeKont rolle() unmittelbar nach der öffnenden Klammer ein:
6. Erweitern Sie die Deklaration von init() nach der Initialisierung von mCode:
var nAktuellBytes:Number, nTotal:Number;
mCode._x = Stage.width/2; mCode._y = 50;
tProzent = mCode. createTextField(„txt“, mCode.getNext HighestDepth(), 0, 0, 50, 25); tProzent.autoSize = „left“;
Wir erstellen eine Variable zur Referenzierung eines Textfelds, das in der init()-Funktion mit einigen Standardwerten erzeugt wird:
• txt, der Instanzname, • mCode.getNextHighestDepth(), die Tiefe, • 0,0, Position, • 50, 25, Breite und Höhe. Die beiden letztgenannten Werte sind eigentlich irrelevant, da wir die konkrete Größe an den Textinhalt anpassen. Erzeugt wird das Textfeld der Einfachheit halber in mCode; alternativ hätten wir auch einen eigenen MovieClip erstellen können. Nicht möglich dagegen wäre das Einfügen des Feldes in mBild, da dort der gesamte Inhalt sofort nach Start des Ladevorgangs bekanntermaßen gelöscht wird. Weil wir im Textfeld
9. Fügen Sie in der Deklaration von ladeKont rolle() im onEnterFrame-Ereignis unmittelbar nach dessen öffnenden Klammer ein: nTotal = pZiel.getBytesTotal()/1000; nAktuellBytes = pZiel.getBytesLoa ded()/1000;
10. Ändern Sie die Zuweisung von tProzent.text (Fettdruck): tProzent.text = String(nAktuell Bytes)+“ Kilobytes von \n“+nTotal+“ Kilobytes geladen“;
Wir erhalten bei der Simulation des Downloads eine Angabe über die geladenen Bytes und die insgesamt zu ladenden Bytes. Wir legen in der Funktion ladeKontrolle() zwei lokale Variablen an, die die gewünschten Werte aufnehmen sollen. Im onEnterFrame-Ereignis lesen wir diese Werte aus und dividieren sie durch 1000 bzw. verschieben die Kommastelle um drei Zeichen nach links, so dass unser Ergebnis lesbarer wird. An-
266
Kapitel 20 Externe Assets
schließend weisen wir dem Textfeld die betreffenden Werte in einer Konkatenation zu, d. h. verknüpft mit weiterem Informationstext. Um die Zeile nicht zu lange werden zu lassen, fügen wir zwischendurch einen Zeilenumbruch mit \n ein. Auf den ersten Blick mag es überflüssig scheinen, permanent auch die Gesamtmenge der zu ladenden Daten auszulesen, schließlich kann sie sich nicht ändern. Da die loadMovie()-Methode aber erst Auswirkungen zeitigt, nachdem die übrigen Zeilen abgearbeitet wurden, liegen uns vor Beginn des onE nterFrame-Ereignisses noch keine Informationen bezüglich der Bytes vor. Das können Sie testen, indem Sie vorübergehend unmittelbar nach der Deklaration der beiden lokalen Variablen und noch vor Deklaration des onEnterFrame-Ereignisses eintragen: trace(„Gesamt: „+pZiel.getBytesTo tal()+“, geladen: „+pZiel.getBytesLoa ded());
Als Ergebnis gibt Flash zwei überaus bescheidene Nullen aus, obwohl im Skript die Funktion la deKontrolle() mit dieser Ausgabe nach der loadMovie()-Methode aufgerufen wird. Der Ladevorgang hat also noch nicht gestartet. 11. Fügen Sie in der Deklaration von ladeKont rolle() innerhalb der (inneren) if-Bedingung ein:
tProzent.text = „“;
Da der Text bisher auch nach Abschluss des Ladens noch angezeigt wurde, löschen wir ihn einfach, indem wir dem verwendeten Textfeld einen leeren String zuweisen. Bevor wir uns weitere Möglichkeiten anschauen, bleibt noch eine kleine Ergänzung, nämlich der Schlagschatten. 12. Fügen Sie am Beginn des Codes einen ImportBlock ein:
//------------ import –-------------
import flash.filters.*;
13. Erweitern Sie den Variablen-Block am Ende um:
var fSchatten:DropShadowFilter;
14. Fügen Sie in der init()-Funktion unmittelbar nach der öffnenden Klammer ein: fSchatten = new DropShadowFilter(5, 45, 0 × 000000, 0.9, 5, 5, 1, 3);
15. Fügen Sie in der Deklaration von ladeKont rolle() vor der Zuweisung des onPress-Ereignisses ein:
pZiel.filters = [fSchatten];
Wie in einer der vorhergehenden Übungen verfügt das angezeigte Bild über einen Schlagschatten. Da die Vorgehensweise sich gegenüber dieser Übung nicht geändert, erübrigt sich hier eine nähere Erläuterung. Wichtig ist lediglich die Stelle, an der wir den Filter zuweisen. Dies muss dann erfolgen, wenn das Bild vollständig geladen wurde, d. h. der Code steht innerhalb unserer (inneren) if-Bedingung. Da die filters-Eigenschaft durch den Ladevorgang gelöscht wird, haben wir nicht die Möglichkeit, zu Beginn bei Einrichten von mBild den gewünschten Filter zuzuweisen. Preloader geben oft statt konkreter Werte einen prozentualen Anteil wieder. Er lässt sich mit einem Dreisatz ermitteln, in dem die bekannten Werte zu dem gesuchten Wert in Beziehung gesetzt werden. Dabei gilt: Geladene Bytes Gesamtanzahl Bytes
=
Gesuchte Prozent 100 Prozent
Wenn wir nach dem gesuchten Wert auflösen, erhalten wir: Geladene Bytes * 100 Prozent Gesuchte = Prozent Gesamtanzahl Bytes Daraus ergibt sich die Formel: nAktuellBytes * 100 nTotal
= nProzent
16. Erweitern Sie in der Deklaration von ladeKont rolle() die Variableninitialisierung unmittelbar nach der öffnenden Klammer (Fettdruck): var nAktuellBytes:Number, nTotal:Number, nProzent:Number;
17. Ändern Sie in der Deklaration von ladeKont rolle() im onEnterFrame-Ereignis die Berechnung von nAktuellBytes und nTotal (Fettdruck):
nTotal = pZiel.getBytesTotal();
nAktuellBytes = pZiel.getBytesLoaded();
20.3 Anzeigen der Ladekontrolle (Preloader mit getBytesLoaded())
18. Fügen Sie unmittelbar nach dieser Berechnung ein: nProzent = Math. round((nAktuellBytes*100)/nTotal);
19. Ändern Sie in der darauffolgenden Zeile die Zuweisung von tProzent.text (Fettdruck): tProzent.text = String(nProzent)+“ Prozent geladen“;
Anstelle der vorherigen Bytes-Angaben zeigt Flash beim Ladevorgang den Prozentwert der geladenen Daten an. Zur Berechnung benötigen wir eine weitere lokale Variable, die wir am Anfang der Funktion einrichten. In die bisher verwendeten beiden Variablen speichern wir wiederum die geladene und die Gesamtdatenmenge, verzichten diesmal aber auf eine Verschiebung der Kommastellen, da wir später ohnehin das Ergebnis runden. Anschließend errechnen wir den benötigten Wert entsprechend der oben vorgestellten Formel und runden das Ergebnis, um es in einer vernünftigen Form anzeigen zu können. Ohne Math. round() würden wir eine aufgrund zahlreicher unnötiger Nachkommastellen sehr unlesbare Zahl erhalten. Das Ergebnis weisen wir wie zuvor dem Textfeld zu, wobei sich hier ein Hinweis auf die Gesamtmenge erübrigt, da sie notwendigerweise 100 Prozent entspricht. Unser Ergebnis lässt sich natürlich auch grafisch umsetzen, wobei wir lediglich einen Zusammenhang herstellen müssen zwischen dem ermittelten Prozentwert und der Ausdehnung eines visuell wahrnehmbaren Objekts. In der simpelsten Form handelt es sich dabei um einen sogenannten Ladebalken, den wir anstelle des Textfeldes verwenden wollen.
267
var nBildIndex:Number = 0;
v ar aBilder:Array = [„gepard1.jpg“, „gepard5.jpg“, „gepard7.jpg“]; var fSchatten:DropShadowFilter;
//----------- functions –----------- function init() {
fSchatten = new DropShadowFilter(5, 45, 0 × 000000, 0.9, 5, 5, 1, 3); mBild = this. createEmptyMovieClip(„behBild“, this.getNextHighestDepth()); mCode = this. createEmptyMovieClip(„behCode“, this.getNextHighestDepth()); mCode._x = Stage.width/2; mCode._y = 50;
tProzent = mCode. createTextField(„txt“, mCode.getNextHighestDepth(), 0, 0, 50, 25); tProzent.autoSize = „left“;
laden(sBildPfad+aBilder[nBild Index], mBild); }
function laden(pWas:String, pZiel:MovieClip):Void { pZiel.loadMovie(pWas); ladeKontrolle(pZiel); }
function ladeKontrolle(pZiel:Movie Clip):Void { var nAktuellBytes:Number, nTotal:Number, nProzent:Number;
mCode.onEnterFrame = function() {
1. Der Einfachheit halber speichern Sie die bestehende Datei im selben Ordner unter einem anderen Namen. 2. Löschen Sie alle Bezüge auf das eingefügte Textfeld (Fettdruck):
nTotal = pZiel.getBytesTotal();
//------------ import –--------------
tProzent.text = String(nProzent)+“ Prozent geladen“;
import flash.filters.*;
//------------ vars –----------------
var mBild:MovieClip, mCode:MovieClip; var tProzent:TextField;
var sBildPfad:String = „bilder/ main/“;
nAktuellBytes = pZiel.getBytes Loaded(); nProzent = Math. round((nAktuellBytes*100)/ nTotal);
if (pZiel. getBytesLoaded()>=pZiel.getBy tesTotal()) { if (pZiel._width>10) {
trace(„habefertisch!“);
268
Kapitel 20 Externe Assets
tProzent.text = „“;
mBalken.moveTo(0, 0);
pZiel.filters = [fSchatten];
mBalken.lineTo(pBreite, nBalken Hoehe);
nBildIndex++;
mBalken.lineTo(0, 0);
pZiel._x = Stage.width/2pZiel._width/2;
pZiel.onPress = function() { if (nBildIndex>=aBilder. length) { nBildIndex = 0;
mBalken.lineTo(pBreite, 0);
mBalken.lineTo(0, nBalkenHoehe); mBalken.endFill(); }
}
6. Fügen Sie in der Deklaration von ladeKont rolle() unmittelbar vor dem onEnterFrameEreignis ein:
};
balken(nBalkenBreite, 100, 0);
laden(sBildPfad+aBilder[nBi ldIndex], this); delete this.onEnterFrame; } } }; }
//------------ start –-------------- init(); stop();
Wenn wir einen Ladebalken verwenden wollen, können wir zwischen mehreren Varianten wählen:
• Händisches Zeichnen eines MovieClips; • draw()-Methode der BitmapData-Klasse; • Zeichnungsmethoden der MovieClip-Klasse. Wir wählen beispielhaft den letztgenannten Weg. 3. Erweitern Sie den Variablen-Block: var nBalkenBreite:Number = 100; var nBalkenHoehe:Number = 10;
4. Erweitern Sie die Deklaration von init() nach der Initialisierung von mBalken: mBalken._x –= nBalkenBreite/2;
5. Erweitern Sie den Funktions-Block: function balken(pBreite:Number, pAlphaRahmen:Number, pAlphaFuell:Number):Void {
mBalken.lineStyle(2, 0 × 000000, pAlphaRahmen);
mBalken.beginFill(0xff0000, pAlpha Fuell);
7. Fügen Sie in der Deklaration von ladeKont rolle() unmittelbar nach der Berechnung von nProzent ein: mBalken.clear();
balken(nBalkenBreite, 100, 0); balken(nProzent, 0, 100);
8. Fügen Sie in der Deklaration von ladeKont rolle() in der (inneren) if-Bedingung vor der Positionierung von pZiel ein: mBalken.clear();
Im Grunde genommen benötigen wir zwei Zeichenvorgänge: Erstellen des äußeren Rahmens, der unverändert beibehalten wird, solange der Ladevorgang läuft, und Zeichnen der Füllung entsprechend des errechneten Prozentwerts. Eine Füllung setzt allerdings einen Rahmen voraus, innerhalb dessen sie sich befindet. Wir müssen daher für die Füllung einen weiteren Rahmen zeichnen, auch wenn er faktisch nur als Hilfsmittel benötigt wird. Wir könnten nun zwei verschiedene Funktionen erstellen, von denen eine nur einen Rahmen, die zweite Füllung und Rahmen zeichnet. Alternativ wäre es auch möglich, alles in eine einzige Funktion hinein zu packen, wobei wir dann allerdings Acht geben müssen, dass sich Rahmen und Füllung beliebig ausschalten lassen. Das funktioniert mit einer if-Bedingung oder noch einfacher, indem wir diejenigen Elemente, die unsichtbar sein sollen, zwar zeichnen, ihre Deckkraft aber auf 0 reduzieren. Solange wir mit wenigen und in der Ausdehnung beschränkten Objekten arbeiten, ergeben sich daraus keine Performance-Einbußen.
20.3 Anzeigen der Ladekontrolle (Preloader mit getBytesLoaded())
Wir legen zunächst zwei Variablen fest, in denen die Werte für die Ausdehnung des Ladebalkens enthalten sind, wenn er zu 100 % gefüllt ist. Um uns weitere Berechnungen zu ersparen, werden einfach 100 für die Breite und ein willkürlicher Wert für die Höhe verwendet. Wenn wir unser externes Bild horizontal mittig positionieren, dann sollten wir dasselbe mit dem Ladebalken tun. Das geschieht, indem innerhalb des horizontal mittig angeordneten Clips mCode der neu erstellten Clip mBalken um die halbe Balkenbreite nach links verschoben wird. In dem Fall wird er 50 Pixel links von der Mitte beginnen sich zu füllen, und ebenso viele Pixel rechts von der Mitte den Zeichenprozess beenden. Wenn die Funktion balken() zunächst etwas komplex ausschaut, so nur deshalb, weil sie variabel genug sein muss, um mehrere Möglichkeiten abzudecken. Sie enthält drei Parameter:
• pBreite,
diejenige horizontale Position, bis zu der ein Rahmen gezeichnet werden soll; • pAlphaRahmen, die Deckkraft des Rahmens • pAlphaFuell, die Deckkraft der Füllung. Innerhalb der Funktion legen wir einen Linienstil mit folgenden Eigenschaften fest:
269
• 0xff0000, rote Farbe; • pAlphaFuell, Deckkraft. Falls keine Füllung erwünscht ist, übergeben wir 0.
Das eigentliche Zeichnen beginnt mit der Positionierung des Zeichenstiftes auf dem Koordinatenursprung des MovieClips, in dem gezeichnet wird. Von dort aus ziehen wir eine horizontale Linie nach rechts entsprechend des als Parameter übergebenen Wertes. Benötigen wir den äußeren Rahmen, entspricht dieser Wert 100 bzw. der anfangs festgelegten Variable nBal kenBreite. Da sich die Höhe nie ändert, zeichnen wir von dort nach unten ebenfalls entsprechend einer zuvor deklarierten Variable. Die beiden nächsten Linien verlaufen horizontal zurück zum Ausgangspunkt und anschließend vertikal zurück zum Koordinatenursprung. Damit entsteht ein geschlossenes Rechteck, dessen horizontale Ausdehnung einmal von der eingangs initialisierten Variable und einmal von dem errechneten Prozentwert bestimmt wird, je nachdem, an welcher Stelle wir die Funktion aufrufen. Abbildung 65 visualisiert diesen Vorgang, wobei sich die angegebenen Zahlen auf die Zeichnungsreihenfolge (Endpunkt einer Linie) beziehen. Benötigen wir keine Füllung, wird der äußere Rahmen erstellt und die Parameter lauten
• 2, Stärke der Linie. Wir wählen einen etwas hö- • Rahmenbreite nBalkenBreite, heren Wert, um später beim Füllen die RahmenliDeckkraft Rahmen 100 nie noch erkennen zu können. Bei einem kleineren • Deckkraft Füllung 0. Wert müssten wir die Füllung etwas kleiner zeich- • nen oder einen eigenen MovieClip anlegen, der sich hinter demjenigen für den Rahmen befinden würde; • 0 × 000000, schwarze Farbe; • pAlphaRahmen, Parameter für die Deckkraft der Linie. Benötigen wir keinen Rahmen, übergeben wir beim Aufruf für diese Stelle einfach 0. Wir legen eine Füllung mit folgenden Eigenschaften fest:
Um die Füllung zu erhalten, übergeben wir dagegen:
• Rahmenbreite nProzent, • Deckkraft Rahmen 0 • Deckkraft Füllung 100. Aufgerufen wird die Funktion einmal unmittelbar nach Start eines Ladevorgangs, um den äußeren Rahmen zu erstellen. Danach führen wir sie jeweils zweimal permanent mit verschiedenen Argumenten solange aus, bis alle Daten geladen wurden. Die doppelte Ausfüh-
Abbildung 65: Zeichnen der Elemente des Ladebalkens
270
Kapitel 20 Externe Assets
rung ist deshalb notwendig, weil wir, um den neuen Füllwert darstellen zu können, jedes Mal den alten mit der clear()-Methode löschen. Da die Zeichnung innerhalb eines einzigen MovieClips erfolgt, geht dadurch jedoch zugleich unser äußerer Rahmen verloren, so dass er zusammen mit der Füllung wieder neu erstellt werden muss. Optisch macht es übrigens keinen Unterschied, ob wir hier löschen; wir könnten darauf verzichten und würden uns den Aufruf von balken() für den äußeren Rahmen ersparen, aber so ist es formal korrekter. Ganz zum Schluss, wenn wir feststellen, dass die Grafik vollständig vorhanden ist, entfernen wir die Zeichnung, so dass kein Balken mehr sichtbar ist. Bessere Usability erhalten wir, wenn zusätzlich zur grafischen eine textuelle Darstellung erfolgt. Wir können dabei auf der vorhergehenden Übung aufbauen. 9. Ergänzen Sie den Variablen-Block:
var tProzent:TextField;
10. Erweitern Sie die Deklaration von init() unmittelbar vor dem Aufruf von laden(): tProzent = mCode. createTextField(„txt“, mCode.get NextHighestDepth(), 0, 10, 50, 25);
tProzent.autoSize = „left“;
11. Fügen Sie in der Deklaration von ladeKont rolle() im onEnterFrame-Ereignis nach dem zweiten Aufruf von balken() ein: tProzent.text = String(nProzent)+“ Prozent geladen“; tProzent._x = nProzent-nBalken Breite/2;
12. Fügen Sie in der Deklaration von ladeKont rolle() unmittelbar vor dem onPress-Ereignis ein:
tProzent.text = „“;
Bei einer Ladesimulation taucht der Text neben dem Ladebalken auf. Das Einrichten des Textfeldes unterscheidet sich nicht von vorher. Neu ist lediglich die Positionierung, die sich nach dem aktuellen Prozentwert richtet. Da das Textfeld wie mBalken in mCode eingefügt, der Zeichenbehälter aber um halbe Balkenbreite nach links verschoben wurde, müssen wir diesen Versatz bei der Anzeige berücksichtigen. Konkret bedeutet
dies, dass vom aktuellen Prozentwert besagte halbe Balkenbreite zu subtrahieren ist. Sie können gerne die Gegenprobe machen, indem Sie auf die Subtraktion verzichten. In dem Fall wird der Text viel zu weit rechts angezeigt. Alternativ können Sie ihn auch mittig zum Prozentwert ausrichten, indem Sie nBalken Breite subtrahieren. Hier sind natürlich zahlreiche Varianten und Ergänzungen denkbar. So könnte man nach dem gleichen Prinzip einen maskierten Text als Preloader verwenden und ihn sukzessive freigeben. Denkbar wäre ein kreisförmiger Preloader, bei dem eine vollständige Drehung von 360 Grad 100 Prozent und eine Drehung um 3,6 Grad 1 Prozent entsprechen würde. Auf der Basis dieser Information über den aktuellen Ladezustand könnte man zusätzlich errechnen, wie lange der Ladevorgang noch ungefähr dauern wird. Dabei muss man nur die verstrichene Zeit und bereits geladene Datenmenge in Bezug zur gesamten Datenmenge setzen. Eine derartige Angabe kennt jeder von seinem Betriebssystem, das beim Kopieren von Daten die voraussichtliche Gesamtdauer angibt. Dabei gilt es jedoch zu beachten, dass technisch weniger versierte User – und davon gibt es immer noch eine recht große Menge – eher mit Unverständnis reagieren werden, wenn aufgrund der im Internet zwangsläufig vorhandenen schwankenden Datenübertragungsrate die verbleibende Zeit trotz Fortschritte des Ladevorgangs plötzlich ansteigt statt zu sinken (was zumindest prinzipiell möglich ist). Ladevorgänge sind immer lästig, was natürlich um so deutlicher zutrifft, je längere Zeit sie in Anspruch nehmen. Das kann man allerdings auch nutzen, indem der Anwender in dieser Zeit auf irgendeine Weise abgelenkt wird. Denkbar wäre eine kleine Interaktion etwa in Form eines simplen Spiels, das den Ladevorgang überbrückt. Geht man geschickt genug vor, wird das Laden sogar zu einer Werbung für die zu ladende Site.
20.4 Überblenden bei Ladevorgängen Beim Laden externer Dateien (swf oder Grafiken) per loadMovie()-Methode entstehen i. d. R. abrupte Übergänge: Während die zuvor geladene Datei sofort gelöscht wird, muss die neu aufgerufene Datei erst noch vollständig geladen werden, bevor Flash sie anzeigen kann. Das dadurch entstehende
20.4 Überblenden bei Ladevorgängen
Flackern zwischen den Ladevorgängen lässt sich auf mehrere Arten vermeiden. So wäre eine Verwendung von zwei Containern möglich, die jeweils mit swap Depths() ihre Tiefe vertauschen. Eine andere Möglichkeit vertauscht jeweils die Container, in die die neue Datei hineingeladen wird, ohne deren Tiefen zu verändern. Auch hier werden zwei Container verwendet: Einer enthält die vorher geladene Datei, der andere nimmt die neue Datei auf. Die Deckkraft des Containers für die neue Datei wird auf 0 reduziert, so dass er nicht zu sehen ist. Danach laden wir die neue Datei und kontrollieren den Ladevorgang. Liegt sie vollständig vor, blenden wir den Container mit der vorhergehenden Datei aus und gleichzeitig den Container mit der neuen Datei ein. Der resultierende Effekt ist der gleiche wie im Falle der swapDepths()Methode. 1. Erstellen Sie im Ordner site eine Standarddatei. 2. Erstellen Sie einen leeren MovieClip (Bibliotheksund Verknüpfungsname mcBtn). 3. Benennen Sie dort die bestehende Ebene um in flaeche. 4. Fügen Sie eine neue Ebene namens info ein. 5. Zeichnen Sie auf flaeche beginnend an der Position –28, –12 ein 56 Pixel breites und 24 Pixel hohes Rechteck beliebiger Farbe. 6. Fügen Sie im Koordinatenursprung auf info ein dynamisches Textfeld ein (12, Verdana, beliebige Farbe, nicht auswählbar, zentriert, fett, Instanzname info). 7. Kehren Sie zur Hauptzeitleiste zurück. 8. Weisen Sie actions folgendes Bildskript zu: //---------------- vars –----------- var mNav:MovieClip, mBtn:MovieClip, mLaden:MovieClip, mBild1:MovieClip, mBild2:MovieClip; var mLadeNeu:MovieClip, mLadeAlt:MovieClip;
var nAbstand:Number = 5; var nLadeX:Number = 200; var nLadeY:Number = 100; var nAlpha:Number = 5;
var sBildPfad:String = „bilder/ main/“;
v ar aBilder:Array = [„gepard1.jpg“, „gepard5.jpg“, „gepard7.jpg“]; var aBtns:Array = [];
271
Aufgrund der besonderen Vorgehensweise benötigen wir eine etwas größere Anzahl an MovieClips:
• mNav, einen Behälter für die Aufnahme der gesam-
ten Navigation bzw. der Buttons, die externe Elemente laden. • mLaden, einen Behälter, in dem alle MovieClips mit externen Ladevorgängen enthalten sind. Dieser und der vorhergehende Clip sind nicht unbedingt notwendig, entsprechen aber unserem bisherigen Vorgehen, bei dem wir logisch zusammengehörige Elemente in übergeordneten Behältern gruppieren, um unsere Flexibilität zu erhöhen. • mBtn, um jeweils die einzelnen Schaltflächen zu referenzieren. • mBild1 und mBild2, um die für externe Ladevorgänge zuständigen MovieClips zu referenzieren. • mLadeNeu und mLadeAlt, um jeweils die Reihenfolge der externen Ladevorgänge vertauschen zu können. Auf diese beiden hätten wir ebenfalls verzichten können, müssten uns aber dann mit längeren Pfadangaben herumschlagen, die eine potentielle Fehlerquelle darstellen. Die Variablen nAbstand, nLadeX und nLadeY dienen dazu, den vertikalen Abstand zwischen den Schaltflächen sowie die Position des übergeordneten Behälters zu definieren. Mit nAlpha legen wir denjenigen Wert fest, um den die externen Elemente ein- bzw. ausgeblendet werden. Wie zuvor auch initialisieren wir danach einen Pfad zu den gesuchten Elementen sowie ein Array, in dem die benötigten Dateinamen erfasst werden. Das abschließende Array wird benötigt, um später auf alle vorhandenen Buttons zuzugreifen. 9. Ergänzen Sie folgenden Funktions-Block: //------------- functions –-------- function init() {
mNav = this.createEmptyMovieClip („behNavigation“, this.getNextHigh estDepth()); for (var i:Number = 0; i=10) {
this.onEnterFrame = function() { if (mLadeAlt._alpha>0) {
mLadeAlt._alpha –= nAlpha;
mLadeNeu._alpha += nAlpha; } else {
20.4 Überblenden bei Ladevorgängen
273 Abbildung 66: Aufbau der Anwendung
mLadeNeu._alpha = 100; mLadeAlt._alpha = 0;
if (mLadeNeu == mBild2) { mLadeAlt = mBild2; mLadeNeu = mBild1; } else {
mLadeAlt = mBild1; mLadeNeu = mBild2; }
delete this.onEnterFrame; } }; }
}
Nun blenden die extern geladenen Elemente langsam ein. Die Ladekontrolle umfasst zwei aufeinander folgende Vorgänge:
• Laden der externen Daten. Wie wir gesehen haben, können wir dabei getBytesLoaded() und/oder eine bekannte Eigenschaft des Ladebehälters verwenden. In letztgenanntem Fall ist auch die Initialisierung des Contens abgeschlossen, weswegen wir uns hier für diese Variante entscheiden (zumal die tatsächlich geladenen Bytes im aktuellen Zusammenhang ohne Bedeutung sind). • Aus- bzw. Einblenden der Ladebehälter.
Da der zweite von dem ersten Prozess abhängt, müssen wir in der Funktion zunächst permanent kontrollieren, ob die benötigten Daten vorliegen. Trifft das zu, überschreiben wir das bestehende onEnterFrame-Ereignis durch ein neues, innerhalb dessen:
• mLadeAlt, der Behälter mit dem bereits irgend-
wann früher geladenen Content ausgeblendet wird. • Synchron damit mLadeNeu, der Behälter mit dem neu geladenen Content, eingeblendet wird. Ist das Ausblenden beendet, setzen wir die Deckkraft von mLadeAlt auf 0 und diejenige von mLadeNeu auf 100. Dies ist notwendig, da die Subtraktion bzw. Addition zu etwas gewöhnungsbedürftigen Werten führt, die sich deutlich von den genannten Grenzwerten unterscheiden (auch wenn das der Anwender visuell nicht wahrnehmen kann) und wir somit weder die 0 noch 100 exakt treffen würden. Um den nächsten Ladevorgang vorzubereiten, wird die Referenzierung der Ladebehälter umgekehrt: mLadeAlt enthält nun den eben geladenen Content und zeigt ihn zu 100 % an, während mLadeNeu auf den bereits früher aufgerufenen Inhalt verweist, ihn aber aufgrund der Deckkraft von 0 % nicht anzeigt. Bei einem erneuten Aufruf der Ladefunktion wird so automatisch der ältere, nicht mehr benötigte Content gelöscht. Da der Ladevorgang abgeschlossen ist, löschen wir das onEnterFrameEreignis. Wer sich die zwar lesbare und eindeutige if-elseBedingung sparen möchte, kann an dessen Stelle mit
274
einem Array arbeiten, das mBild1 und Bild2 enthält. Nach jedem Einblenden erfolgt der Aufruf der Ar ray.reverse()-Methode, mit der die Reihenfolge des Inhalts umgekehrt wird. Dadurch kann man den konkreten Inhalt von mLadeAlt und mLadeNeu austauschen, obwohl immer der gleiche Index verwendet wird. Vielleicht überrascht es, dass wir mLadeAlt einen anderen Ladebehälter zuweisen können und dieser in der gewünschten Weise angezeigt werden. Schließlich wurde mLadeAlt auf 0 % Deckkraft reduziert. Wenn dort der Behälter mit dem neu geladenen Content, der zu 100 % sichtbar ist, zugewiesen wird, müsste doch alles verschwinden. Tatsächlich funktioniert unser Vorgehen, weil die einzelnen Variablen keine Kopie, sondern eine Referenz auf die jeweiligen MovieClips enthalten. Wenn wir also die Deckkraft von mLade Neu erhöhen, betrifft das automatisch das dort refe-
Kapitel 20 Externe Assets
renzierte Objekt. Haben wir den Wert 100 erreicht, wird in mLadeAlt, dessen Deckkraft 0 beträgt, der gerade vollständig eingeblendete Clip referenziert. Dann übernimmt mLadeAlt dessen Deckkraft von 100 ebenfalls. Sie können das wie immer mit einem trace() kontrollieren, indem Sie sich die Deckkraft von mBild1 und mBild2 während des Einblendens ausgeben lassen. Den Ladevorgang zeigen die Abbildungen 67 bis 69 im Überblick. Könnte man dann nicht auf die feste Zuweisung von Werten im ersten else-Fall innerhalb des on EnterFrame-Ereignisses der Funktion ladeKon trolle() verzichten? Wenn wir das tun, entsteht das Problem, dass keines der Objekte mehr mit dem korrekten Anfangswert initialisiert wird und im Laufe der Zeit blendet sich jedes Objekt mit verminderter Deckkraft ein.
Abbildung 67: Phase 1 des Ladevorgangs
Abbildung 68: Phase 2 des Ladevorgangs
Abbildung 69: Phase 3 des Ladevorgangs
20.5 Alternative Ladekontrolle (Preloader mit der MovieClipLoader-Klasse)
Eventuell etwas störend wirkt sich die Möglichkeit aus, dass man während eines noch aktiven Ladevorgangs den nächsten starten kann. 13. Erweitern Sie in der Deklaration von init() das onPress-Ereignis der einzelnen Schaltflächen unmittelbar nach der öffnenden Klammer:
einAus(false);
14. Erweitern Sie die Deklaration von ladeKont rolle() im onEnterFrame-Ereignis unmittelbar vor Aufruf der delete-Anweisung:
einAus(true);
15. Fügen Sie im Funktions-Block ein: function einAus(pBoolean:Boolean):Void { for (e in aBtns) {
aBtns[e].enabled = pBoolean;
}
}
Wenn Sie testen, kann immer nur ein Bild geladen werden. Erst wenn dieser Prozess beendet ist, lässt sich der nächste Aufruf starten. Da wir vorsorglich alle Buttons beim Einfügen in einem Array erfasst haben, können wir an dieser Stelle sehr einfach darauf zugreifen. Dazu verwenden wir die Funktion einAus(), die der Eigenschaft enabled von jedem Element, dass sich in diesem Array befindet, das beim Aufruf übergebene Argument zuweist. Rufen wir die Funktion mit dem Argument false bei Mausklick auf eine beliebige Schaltfläche auf, schalten wir alle Buttons aus, und beim Argument true, das nach Ende des Einblendens übergeben wird, aktivieren wir sie wieder. Bedenken Sie, dass unsere Lösung zwar reibungslos funktioniert, aber aus prinzipiellen Gründen einen Haken hat: Wenn ein Ladevorgang aus welchem Grund auch immer nicht beendet werden kann, lässt sich keine Schaltfläche mehr aktivieren. Insofern wäre es also notwendig, sich zu vergewissern, ob die zu ladende Datei denn auch tatsächlich existiert. Im nachfolgenden Abschnitt werden Sie eine alternative Ladekontrolle mit Hilfe des MovieClipLoader kennen lernen, der den Vorteil besitzt, bei misslungenem Laden ein entsprechendes Ereignis (onLoadError) zu initiieren. Tritt es auf, muss der Aufruf einAus(true); erfolgen und die Anwendung funktioniert auch bei fehlgeschlagenem Ladeaufruf problemlos.
275
20.5 Alternative Ladekontrolle (Preloader mit der MovieClipLoader-Klasse) Wie oben angesprochen, stellt die MovieClipLoaderKlasse die modernere Variante für einen Preloader dar. Sie verfügt nämlich über einige Ereignisse, die den Ladevorgang recht genau abbilden bzw. erfassen und dabei über das einfache Protokollieren der geladenen Daten hinaus reichen. Da oben bereits gezeigt wurde, wie die über einen Preloader ermittelten Informationen grafisch und textuell angezeigt werden können, wollen wir uns an dieser Stelle auf trace()-Anweisungen begnügen, um den Ladeprozess zu verfolgen. 1. Erstellen Sie eine Standarddatei im Ordner site der vorhergehenden Übungen. 2. Weisen Sie actions folgendes Bildskript zu: //----------––- vars –-------------- var mBild:MovieClip;
var mLoader:MovieClipLoader; var oLoadListener:Object;
var sBildPfad:String = „bilder/ main/“; var nBildIndex:Number = 0;
v ar aBilder:Array = [„gepard1.jpg“, „gepard5.jpg“, „gepard7.jpg“];
//----------- functions –----------- function init() {
mBild = this. createEmptyMovieClip(„behBild“, this.getNextHighestDepth());
mLoader = new MovieClipLoader(); oLoadListener = new Object();
laden(sBildPfad+aBilder[nBild Index], mBild); }
function laden(pWas:String, pZiel:MovieClip):Void {
oLoadListener.onLoadProgress = function(pZiel:MovieClip, pGeladen:Number, pGesamt:Number):Void {
trace(pGeladen+“ von „+pGesamt+“ geladen“); };
276
oLoadListener.onLoadInit = function(pZiel:MovieClip):Void {
pZiel._x = Stage.width/2-pZiel._ width/2; pZiel.onPress = function() { nBildIndex++;
if (nBildIndex>=aBilder.length) { nBildIndex = 0; }
laden(sBildPfad+aBilder[nBildIn dex], this); }; };
mLoader.addListener(oLoadListener); mLoader.loadClip(pWas, mBild); }
//-------------- start –––––––----- init();
Wenn Sie den Ladevorgang simulieren, erhalten Sie im Nachrichtenfenster eine Anzeige über die geladenen Bytes. Liegen alle Daten vor, stellt Flash die Grafik in Bühnenmitte dar. Per Mausklick lässt sich die nächste Grafik laden. Diese Funktionalität haben wir bereits im Zusammenhang mit der getBytesLoaded()-Methode kennen gelernt. Hier vereinfacht sich der Prozess jedoch insofern, als wir keinen zweiten Frame benötigen, um einen zuverlässigen Zugriff auf den geladenen Inhalt zu gewährleisten. Gegenüber vorher sind folgende Schritte neu: Wir benötigen nur noch einen einzigen MovieClip, nämlich den Behälter für die zu ladende Datei, wobei es sich wie oben um eine Grafik oder swf handeln kann. Statt dessen verwenden wir zusätzlich einen MovieClipLoader zur Initialisierung des Ladevorgangs und einen Listener zur Kontrolle, die beide später in hier angelegte Variablen referenziert werden. Die init()Funktion richtet konkret die benötigten Objekte ein und ruft wie gewohnt eine Lade-Funktion auf. In dieser Funktion weisen wir dem Listener, bei dem es sich um ein simples Objekt handelt, ein onLoadPro gress-Ereignis zu, das über drei Parameter verfügt:
• pZiel, der MovieClip, in den geladen wird; • pGeladen, die Anzahl der aktuell geladenen Bytes;
Kapitel 20 Externe Assets
• pGesamt, die Gesamtzahl der zu ladenden Bytes. Das Ereignis tritt automatisch bei jedem Speichern von Dateipaketen nach Start und vor Ende des Ladevorgangs auf. Die erwähnten Parameter bzw. Argumente übergeben wir nicht explizit in Form konkreter Werte, sondern ergeben sich einerseits aus dem tatsächlichen Ladefortschritt und andererseits aus dem Aufruf der loadClip()-Methode. Im Ereignis onLoadInit legen wir fest, was geschehen soll, wenn der Ladevorgang vollständig abgeschlossen und ein Zugriff auf alle Eigenschaften des geladenen Objekts möglich ist. Bei einer swf-Datei mit einem Skript im ersten Frame würde das bedeuten, dass dieses Skript vollständig in den Arbeitsspeicher geladen wurde und ausgeführt werden kann. Das entspricht demjenigen Zeitpunkt, den wir oben mit dem Auslesen der Breite des übergeordneten MovieClips erfasst haben. Damit der MovieClipLoader etwas mit diesen Ereignissen anfangen kann, muss ihm der Listener zugeordnet werden. Anschließend erfolgt der konkrete Ladeaufruf, wobei der Loader nur den Ladeprozess durchführt, nicht jedoch selbst als Ziel für das zu ladende Objekt dient, da er keine visuelle Repräsentanz besitzt. Anzeigen können wir die externen Inhalte nach wie vor nur mit Hilfe eines MovieClips, hier also von mBild. Ist es wichtig, den exakten Beginn des Ladevorgangs festzustellen, muss man dem Listener noch ein onLoadStart-Ereignis mit dem Ziel-MovieClip als Parameter zuweisen. Um den Inhalt des Bildbehälters zu löschen, können Sie die Methode unloadClip() verwenden, beispielsweise innerhalb des onPress-Ereignisses anstelle des bisherigen Codes: mLoader.unloadClip(this);
Zwar ist diese Vorgehensweise zunächst gewöhnungsbedürftig, sie arbeitet aber zuverlässiger als die vorstehende getBytesLoaded()-Variante und ermöglicht eine bessere Kontrolle des Ladevorgangs.
20.6 Beispiel modulare Website Aus den zahlreichen Einsatzmöglichkeiten wollen wir uns kurz das Beispiel einer modularen Website herausgreifen, deren einzelne Unterseiten extern geladen
20.6 Beispiel modulare Website
werden. Eine derartige Vorgehensweise hat mehrere Vorteile:
• Mit Ausnahme der Hauptseite werden nur diejeni-
gen Unterseiten geladen, die der User explizit aufruft. Würden wir Statt dessen alle Elemente unserer Site in eine einzige Flash-Datei packen, würde dagegen alles komplett auf den Client übertragen. • Die Site lässt sich einfacher pflegen. Ist es beispielsweise notwendig, eine Unterseite zu aktualisieren oder modifizieren, muss nur die entsprechende Datei geöffnet werden. Die Hauptseite und alle anderen Seiten bleiben unberührt, was eine potentielle Fehlerquelle ausschaltet, indem man beispielsweise unbeabsichtigt Änderungen an Elementen durchführt, die nicht zur korrigierenden Unterseite gehören. • Entwickeln wir eine sinnvolle modulare Struktur, lässt sie sich leicht auf verschiedene Projekte übertragen, was die Entwicklung neuer Sites erleichtert. Wir wollen das Beispiel bewusst einfach halten, da es nur um das zugrunde liegende Prinzip geht. Daher werden die benötigten Informationen für den Aufbau der Navigation direkt in den betreffenden Flashfilm integriert anstatt sie in eine XML-Datei auszulagern, die Navigation ist nicht weiter untergliedert und alle Dateien liegen im selben Ordner. 1. Erstellen Sie eine Standarddatei und speichern diese als index.fla in einem beliebigen Ordner. 2. Ersetzen Sie den Farbverlauf im Hintergrund durch einen von links oben nach rechts unten gehenden Farbverlauf (c8c8c8 zu 515151). 3. Erstellen Sie einen leeren MovieClip (Bibliotheks- und Verknüpfungsname mcBtn). 4. In diesem Clip erstellen Sie vier Ebenen (von oben nach unten: rahmen, highlight, txt, flaeche). 5. Auf flaeche zeichnen Sie an der Position 0,0 ein Rechteck (grüner vertikaler Farbverlauf, Höhe 22, Breite z. B. 50). Prinzipiell ist die Breite beliebig, da wir sie nachher per Skript der Beschriftung dieses als Button verwendeten MCs anpassen werden. 6. Wandeln Sie die Fläche in einen MovieClip um (Bibliotheksname mcFlaeche, Registrierungspunkt links oben, Instanzname flaeche).
277
7. Zeichnen Sie auf rahmen an der Position 0,0 einen Rahmen in der Größe der Fläche (Haarlinie, beliebige Farbe). 8. Wandeln Sie den Rahmen in einen MovieClip um (Bibliotheksname mcRahmen, Registrierungspunkt links oben, Instanzname rahmen). 9. Zeichnen Sie auf highlight an der Position 0,0 eine helle Fläche mit einem vertikalen Farbverlauf (weiß 40 % zu weiß 5 %). 10. Wandeln Sie diese Grafik in einen MovieClip um (Bibliotheksname mcHighlight, Registrierungspunkt links oben, Instanzname highlight). 11. Fügen Sie auf txt an der Position 2,3 ein dynamisches Textfeld ein (Arial, 12, fett, 1f1f1f, 12 pt, linksbündig, einzeilig, nicht auswählbar, Instanzname txt). Dies ist nur ein beispielhafter Aufbau für einen Button, den Sie natürlich nach Belieben gestalten können. Wichtig ist eigentlich nur das Textfeld für die Aufnahme der Beschriftung, denn wir wollen diesen einen MC verwenden, um daraus die komplette Navigation zu generieren. Das macht die Applikation erheblich flexibler und die Datei schlanker als wenn wir für jeden einzelnen Menüpunkt einen eigenen Button gestalten würden. 12. Kehren Sie zur Hauptzeitleiste zurück. 13. Fügen Sie in actions folgendes Bildskript ein:
//------------ vars –---------------
var mLoader:MovieClipLoader;
var nBtnRandX:Number = 4;
var mContent:MovieClip, mNav:MovieClip, mBtn:MovieClip;
var nOffsetX:Number = 2;
var oLoadListener:Object;
ar aNav:Array = [„Home“,„Produkte“, v „Service“,„Shop“,„Impressum“];
Wir benötigen eine Referenz zu einem leeren MovieClip zur Aufnahme des Contents, also der eigentlichen Inhaltsseiten, des gesamten Menüs, der einzelnen Buttons sowie zu einer Instanz der MovieClipLoader-Klasse und der Object-Klasse, die für einen Listener Verwendung finden soll. In zwei Variablen definieren wir den horizontalen Abstand der Buttons untereinander sowie den Abstand zwischen Buttonbeschriftung und dem rechten Buttonrand. In
278
Kapitel 20 Externe Assets
einem Array legen wir die Beschriftungen unserer Buttons und damit gleichzeitig die Namen der zu ladenden Dateien fest, so dass wir nachher sehr einfach bei einem Mausklick herausfinden können, welche Datei aufgerufen wurde. 14. Erweitern Sie das Skript um einen FunktionsBlock:
//---------- functions –------------
function init(pNavX:Number, pNavY:Number) {
mContent = this.createEmptyMovie Clip(„behInhalte“, this.getNext HighestDepth()); mNav = this.createEmptyMovieClip( „behMenue“, this.getNextHighest Depth()); var nBreite:Number = mNav._width; for(var i:Number = 0; i < aNav. length; i++){
mBtn = mNav. attachMovie(„mcBtn“,„btn“+i,i); mBtn.txt.autoSize = „left“;
mBtn.txt.text = aNav[i];
mBtn.sDatei = aNav[i].sub str(0,1).toLowerCase() + aNav[i].substr(1)+“.swf“;
mBtn.rahmen._width = mBtn. flaeche._width = mBtn.highlight._ width = mBtn.txt._width + nBtn RandX;
mBtn.onPress = geklickt; mBtn._x = nBreite;
nBreite = mNav._width + nOff setX; }
mNav._x = pNavX; mNav._y = pNavY;
mLoader = new MovieClipLoader(); oLoadListener = new Object(); laden(„home.swf“, mContent);
}
15. Fügen Sie einen Aufruf ein:
//------------- start –-----------init(20,10);
Bei einem Test werden in der linken oberen Ecke 5 Buttons angezeigt, die bei onRollOver einen veränderten Cursor zeigen, aber noch nicht auf einen Mausklick reagieren können, da die dann aufgerufene Funktion noch nicht deklariert wurde. Die Funktion init() erzeugt einen leeren MovieClip zur Aufnahme der Inhaltsseiten sowie einen leeren MovieClip zur Aufnahme der Navigation. Da beiden zunächst keine Position zugewiesen wurde, befinden sie sich im Koordinatenursprung der Hauptzeitleiste. Unsere Buttons sollen horizontal angeordnet werden. Da ihre Breite von der jeweiligen Beschriftung abhängt, müssen wir die korrekte Position in Abhängigkeit von der aktuellen Breite des übergeordneten Behälters berechnen. Am Anfang beträgt dieser Wert 0. Um später darauf zugreifen zu können, speichern wir ihn in nBreite. Die for-Schleife fügt so oft den MovieClip mcBtn aus der Bibliothek ein, wie Elemente im Array aBtns existieren, im konkreten Beispiel also fünfmal. Als Text weisen wir das Arrayelement entsprechend dem aktuellen Wert der Zählvariablen i zu und passen das Textfeld im MovieClip an die Größe des Textes an. Danach skalieren wir alle anderen Elemente des Buttons, also die Instanzen highlight, rahmen sowie flaeche, auf die Größe des Textfeldes zuzüglich eines kleinen Wertes, damit der rechte Rand des Buttons nicht unmittelbar am Text klebt. Um später zu wissen, welche Datei bei Klick auf den Button geladen werden soll, legen wir jeweils eine Variable an, in der einfach das schon für die Beschriftung verwendete Element aus aBtns gespeichert wird. Dabei ändern wir lediglich das erste Zeichen dieses Textes zu einem Kleinbuchstaben, da wir in den Dateinamen keine Großbuchstaben verwenden wollen. Dann erhält jeder Button ein onPress, in dem eine noch zu definierende Funktion geklickt() aufgerufen wird. Die aktuelle Position entspricht dem Wert von nBreite und damit der Breite des übergeordneten Behälters vor dem Einfügen eines neuen Buttons zuzüglich eines kleinen Abstandswertes. Abschließend speichern wir die veränderte Breite des Behälters in nBreite. Wenn alle Buttons eingefügt sind, erfolgt die Positionierung des übergeordneten Behälters auf den als Argument beim Aufruf von init() übergegebenen Werten. Wie im vorherigen Kapitel werden MovieClipLoader und Listener eingerichtet. Am Ende der
20.6 Beispiel modulare Website
Funktion rufen wir die ebenfalls noch zu definierende Funktion laden() auf, um ersten Content anzeigen zu können – ansonsten würde die Site mit einer recht einsamen Navigation allzu nackt aussehen. 16. Erweitern Sie den Funktions-Block um folgende Deklaration: function laden(pWas:String, pZiel:MovieClip):Void {
oLoadListener.onLoadProgress = function(pZiel:MovieClip, pGeladen:Number, pGesamt:Number):Void {
trace(pGeladen+“ von „+pGesamt+“ geladen“); };
mLoader. addListener(oLoadListener);
mLoader.loadClip(pWas, pZiel);
}
Dieser Teil entspricht dem vorherigen Abschnitt und bedarf keiner näheren Erläuterung; er bereitet den Loader vor und lädt die als Argument übergebene Datei. 17. Erweitern Sie den Funktions-Block um folgende Deklaration:
function geklickt(){
}
laden(this.sDatei, mContent);
Wenn Sie testen, lädt Flash – nichts. Sie erhalten lediglich bei Klick auf einen beliebigen Button eine Fehlermeldung, über die wir uns aber im konkreten Fall tatsächlich freuen. Schließlich besagt sie, dass eine bestimmte Datei nicht gefunden werden konnte. Daraus erkennen wir, dass der Ladevorgang zwar korrekt ausgelöst wurde, aber noch nicht alle benötigten Dateien vorliegen. Die Funktion geklickt() macht weiter nichts, als auf die Variable sDatei des angeklickten Buttons zuzugreifen und den dort gespeicherten Dateinamen an die Funktion laden() zu übergeben. Um die ganze Funktionalität testen zu können, erstellen Sie einfach weitere Dateien unter den Namen home.swf, produkte.swf, service.swf, shop. swf und impressum.swf. Wenn diese Dateien im selben Ordner wie index.swf liegen, können Sie
279
nun nach Belieben zwischen den einzelnen Elementen hin- und herschalten. Die enorme Flexibilität dieses Systems zeigt sich, sobald wir das Menü ändern wollen, z. B., weil ein zusätzlicher Punkt eingebaut werden soll. Dann muss nur der Inhalt des Arrays aBtns angepasst werden, weitere Änderungen sind nicht notwendig (vorausgesetzt natürlich, dass die dann neu aufgerufene Datei auch tatsächlich existiert). Wer möchte, kann die aufgerufenen Dateien noch einblenden lassen – entweder in der oben vorgestellten Form oder in einer etwas vereinfachten Version. 18. Erweitern Sie den Variablen-Block:
var nAlpha:Number = 10;
19. Fügen Sie in der Deklaration von laden() unmittelbar nach der öffnenden Klammer ein:
mContent._alpha = 0;
20. Fügen Sie in derselben Funktion vor dem Aufruf der addListener()-Methode ein: oLoadListener.onLoadInit = function(pZiel:MovieClip):Void {
pZiel.onEnterFrame = einblenden;
};
21. Erweitern Sie den Funktions-Block um folgende Deklaration:
function einblenden(){
this._alpha += nAlpha;
if(this._alpha >= 100){ this._alpha = 100;
delete this.onEnterFrame; }
}
Zunächst legen wir in einer Variablen die Schrittweite der Funktion einblenden() fest, also den Wert, um den wir später die Deckkraft erhöhen werden. Damit überhaupt etwas eingeblendet werden kann, setzen wir den Behälter für die externen Dateien auf einen Alphawert von 0. Das erfolgt bei jedem Aufruf der Funktion laden(). Um die Einblendfunktion verwenden zu können, müssen die externen Inhalte vollständig geladen sein. Tritt das onLoadInit-Ereignis auf, liegen alle benötigten Daten vor und der Behälter für die Inhalte
280
ruft in einem onEnterFrame-Ereignis die Funktion einblenden() auf. Dieser erhöht solange die Deckkraft von mContent, bis sie mindestens 100 erreicht. In dem Fall löschen wir das onEnterFrame-Ereignis und setzen den Alphawert auf 100. Beachten Sie, dass wir hier auf einen Preloader verzichten; in einer konkreten Anwendung sollte er jedoch auf jeden Fall Verwendung finden. Wie das funktioniert, haben Sie bereits in den vorhergehenden Abschnitten gesehen.
Kapitel 20 Externe Assets
mBtn.sDatei = aNav[i].substr(0,1). toLowerCase() + aNav[i].sub str(1)+“.swf“; mBtn.onPress = geklickt; mBtn._x = nBreite;
nBreite = mNav._width + nOffsetX; }
mNav._x = pNavX; mNav._y = pNavY;
mLoader = new MovieClipLoader();
20.7 Code //------------- vars –-----------------
var mContent:MovieClip, mNav:MovieClip, mBtn:MovieClip; var mLoader:MovieClipLoader; var nOffsetX:Number = 2;
var nBtnRandX:Number = 4; var nAlpha:Number = 10;
var oLoadListener:Object;
var aNav:Array = [„Home“,„Produkte“, „Service“,„Shop“,„Impressum“];
//--------- functions –---------------function init(pNavX:Number, pNavY:Number) {
mContent = this.createEmptyMovieClip („behInhalte“, this.getNextHighest Depth()); mContent._alpha = 0;
oLoadListener = new Object(); laden(„home.swf“, mContent); }
function geklickt(){
mContent._alpha = 0;
laden(this.sDatei, mContent); }
function laden(pWas:String, pZiel:MovieClip):Void {
oLoadListener.onLoadProgress = function(pZiel:MovieClip, pGeladen:Number, pGesamt:Number):Void { trace(pGeladen+“ von „+pGesamt+“ geladen“); };
oLoadListener.onLoadInit = function(pZiel:MovieClip):Void {
pZiel.onEnterFrame = einblenden; };
mNav = this.createEmptyMovieClip(„beh Menue“, this.getNextHighestDepth());
mLoader.addListener(oLoadListener);
for(var i:Number = 0; i < aNav. length; i++){
function einblenden(){
var nBreite:Number = mNav._width;
mBtn = mNav. attachMovie(„mcBtn“,„btn“+i,i); mBtn.txt.autoSize = „left“; mBtn.txt.text = aNav[i];
mBtn.rahmen._width = mBtn.flaeche._ width = mBtn.highlight._width = mBtn.txt._width + nBtnRandX;
mLoader.loadClip(pWas, pZiel); }
this._alpha += nAlpha;
if(this._alpha >= 100){ this._alpha = 100;
delete this.onEnterFrame; } }
//------------ start –----------------init(20,10);
21
XML
Das heutige Internet wäre zumindest in seiner uns bekannten Form ohne sogenannte Auszeichnungssprache (Markup Language) gar nicht denkbar. Denn das nach wie vor wichtigste Format, in dem dort Daten zur Verfügung stehen, stellt HTML, also die Hypertext Markup Language, dar. Selbst Flash benötigt mindestens eine HTML -Seite, um im Internet aufgerufen werden zu können. Im Prinzip ist eine derartige Sprache nichts anderes als reiner Text, der ein Medium in stark formalisierter Form beschreibt. Neben HTML existieren zahlreiche weitere Auszeichnungssprachen, zu denen weniger bekannte wie SMIL (Synchronized Multimedia Integration Language) und SAML (Security Assertion Markup Language), die mittlerweile schon wieder fast vergessene Sprache VRML zur Beschreibung von 3DObjekten, SGML (Standard Generalized Markup Language), die Mutter aller Auszeichnungssprachen, und eben XML, die Xtensible Markup Language, gehören. Was wie ein Schreibfehler aussieht – schließlich beginnt Extensible mit einem e, nicht mit einem x –, ist wohl letzten Endes nur ein weiterer Marketing-Gag in der an solchen Gags reichen Welt der Bits und Bytes. Anders als die übrigen Beispiele gilt XML als eine Art Allrounder, mit dem sich alle Arten an Medien abbilden lassen, der aber vor allem eingesetzt wird, wenn es um den Austausch von Daten geht. Der zugrunde liegende Gedanke ist eigentlich ganz einfach. Wenn Menschen miteinander kommunizieren wollen, müssen sie sich auf einen gemeinsamen Zeichenvorrat verständigen und festlegen, in welcher Form diese Zeichen zu verwenden sind. Ist das geschehen, spielt es letzten Endes keine Rolle, mit welchem technischen Medium als Hilfsmittel der Kommunikationsprozess durchgeführt wird. Die Kommunikation wird gelingen, vorausgesetzt, man beachtet die zuvor
vereinbarten Regeln und setzt ihnen entsprechend die vereinbarten Zeichen ein. Nichts anderes tut XML: Es stellt einen Zeichensatz und bestimmte Regeln zur Verfügung, um zwischen verschiedenen technischen Systemen eine Kommunikationsbrücke zu realisieren. Es bietet sich also für einen Datenaustausch an. Des weiteren eignet es sich hervorragend, um Daten in strukturierter Form abzuspeichern und stellt damit aus Sicht von Flash prinzipiell eine interessante Alternative zu einem normalen Textformat dar, in dem die Daten per Variable und Wertezuweisung erfasst werden, ohne dass man sie darüber hinausgehend strukturieren könnte.
21.1 Aufbau von XML-Dokumenten Aufgrund der gemeinsamen Herkunft ähneln sich HTML und XML. In beiden Sprachen kommen Tags, Attribute und Werte vor. HTML ist jedoch wesentlich stärker eingeschränkt, insofern alle Tags vorgegeben sind (auch wenn ihre Umsetzung bzw. Darstellung mittlerweile durch CSS verändert werden kann), während XML die Möglichkeit bietet, eigene Tags zu definieren. Die in einem spezifischen XML-Dokument definierten Elemente sollten dabei bestimmten grundlegenden Regeln gehorchen, die der Entwickler in einer DTD (Document Type Definition) selbst fest legen kann oder die aus einer bereits vorgefertigten DTD stammen. In dem Fall spricht man von einem wohlgeformten Dokument. Ein XML-Dokument könnte in der einfachsten Form also so aussehen:
Raymond Smullyan
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
281
282
Martin Gardner
Nicholas Falletta
Der Knoten namens quellen verfügt seinerseits über mehrere Knoten namens autor. Der Inhalt von autor besteht jeweils aus einem String. Ein Browser kann mit den verwendeten Elementen wie nichts anfangen. Dort würden sie schlicht ungültige Tags darstellen. XML dagegen lässt uns die Freiheit, sie selbst zu wählen, wobei man aus Gründen der Lesbarkeit Bezeichnungen verwendet, die einen Bezug zum erfassten Inhalt besitzen. Das Dokument kann, da es sich um reinen, unverdünnten Text handelt, prinzipiell mit jedem Texteditor geschrieben werden. Selbst der simple Windows-Editor reicht dazu eigentlich aus. Wer dagegen Wert auf einen gewissen Komfort legt wie z. B. das automatische Einrücken von Zeilen, um den Text lesbarer zu gestalten, der findet entsprechende Editoren im FreewareBereich. Natürlich sind komplexere, beliebig tief verschachtelte Dokumente möglich. Unser obiges Beispiel könnte man erweitern:
Kapitel 21 XML
Raymond Smullyan
Logik-Ritter und andere Schurken <jahr>1991
Martin Gardner Gotcha <jahr>1987
Nicholas Falletta Paradoxon <jahr>1988
Beachten Sie, das unsere XML-Dokumente einen einzigen Wurzelknoten, hier beispielhaft besitzen, dem alle anderen Knoten untergeordnet sind, und das jeder geöffnete Tag irgendwann wieder geschlossen wird. Darin unterscheidet sich XML deutlich von HTML. Zudem kennt es keinen spezifischen head- oder body-Bereich.
Abbildung 70: Datei literatur1. xml im Internet Explorer
21.1 Aufbau von XML-Dokumenten
Ob Ihr Dokument den für XML geltenden Regeln entspricht, können Sie leicht testen. 1. Erstellen Sie im Windows Editor (<Start><Editor>) ein Textdokument, das den Inhalt des eben gezeigten Beispiels mit den drei Autoren enthält. 2. Speichern Sie die Datei unter literatur1.xml ab. Wählen Sie dabei als Dateityp Textdatei und als Codierung UTF-8. 3. Öffnen Sie diese Datei im Internet Explorer. Wenn Ihre Datei korrekt angelegt wurde, erscheint das in Abbildung 70 gezeigte Bild. Wie Sie sehen, geschieht nichts sonderlich Aufregendes – Ihre Daten werden lediglich in strukturierter Form in einer Art Baum angezeigt. Sollte Ihnen ein Fehler unterlaufen, erfolgt ein entsprechender Hinweis, der aber gerade für Anfänger nicht notwendigerweise sehr präzise ist. 4. Löschen Sie in der XML-Datei testweise das letzte schließende jahr-Tag inklusive des Tag-Namens. Der Internet-Explorer zeigt nichts mehr an und gibt nun die in Abbildung 71 enthaltene Fehlermeldung aus:
283
5. Machen Sie den im vorhergehenden Schritt eingefügten Fehler wieder rückgängig, indem Sie das letzte geöffnete jahr-Tag korrekt schließen. Daten lassen sich alternativ mit Hilfe von Attributen beschreiben. Obiges Beispiel würde dann folgendermaßen aussehen:
Unter einem Attribut versteht man wie in HTML eine Eigenschaft, die das zugehörige Element näher charakterisiert. Die Werte, die man einem Attribut zuweist, müssen immer als String, also mit Anführungsstrichen, übergeben werden. Das XML-Dokument wird dadurch wesentlich kompakter und enthält keine textlichen Inhalte mehr, sondern nur noch Attribute mit zugehörigen Werten. Da jedes Tag geschlossen werden muss, wird hier am
Abbildung 71: Fehlerhaftes XML-Dokument im Internet Explorer
284
Ende des öffnenden Tags ein Schrägstrich eingefügt. Selbst eine Kombination beider Varianten ist möglich. Beachten Sie, dass innerhalb ein- und desselben Elements kein Attribut zweimal vorkommen kann, da es sich dann nicht mehr eindeutig zuordnen lässt. In der ersten Zeile enthält ein XML-Dokument einen Hinweis auf die Version, z. B.:
Das ist insofern relevant, als XML ähnlich wie HTML einer ständigen Weiterentwicklung unterworfen ist und neuere Versionen gegebenenfalls erweiterte Funktionalitäten enthalten, auf die in einer spezifischen Form zugegriffen werden kann. Last not least kann ein derartiges Dokument einen Hinweis darauf beinhalten, welche Daten für die verwendeten Attribute und welche Elemente zulässig sind. Man unterscheidet zwischen einer internen DTD, die im Kopf des jeweiligen XML-Dokuments definiert wird, und einer externen DTD, mit dem das Dokument (ebenfalls im Kopfteil) verknüpft wird. Da Flash XML-Dokumente parsed (auswertet), ohne sie zu validieren (auf Übereinstimmung mit einer DTD überprüfen), wollen wir uns hier nicht weiter mit einer derartigen Definition aufhalten. Auch wenn wir die Namen der Elemente bzw. Knoten frei wählen können, so existieren doch gewisse Einschränkungen, die denjenigen bei Variablennamen weitgehend entsprechen:
• Es wird zwischen Groß- und Kleinschreibung un-
terschieden. • Sonderzeichen sind nicht zulässig. • Als erstes Zeichen darf keine Zahl verwendet werden. Möglich sind lediglich Buchstaben sowie der Unterstrich _. • Selbstverständlich sind Leerzeichen tabu. • Am Anfang darf weder in Groß- noch Kleinschreibung das Kürzel XML verwendet werden. Wie in HTML ist es möglich, Kommentare einzufügen, die jeweils mit einem Ausrufezeichen eingeleitet werden:
Kapitel 21 XML
21.2 Laden von XML-Dokumenten (new XML, load(), onLoad, onData) Die Verwendung von XML besteht aus zwei Prozessen: Laden der Datei und Auslesen der Inhalte. Dabei kann es sich sowohl um konkrete Inhalte handeln, die als Text angezeigt werden sollen, wie auch um Informationen bezüglich der Struktur Ihrer Applikation. Gerade die letztgenannte Variante wird viel verwendet, da sie eine einfache Möglichkeit bietet, dynamisch aufgebaute Applikationen zu konfigurieren. Ein typisches Beispiel finden Sie im Workshop Bildergalerie, bei dem die benötigten Informationen über die Bilder in einer XML-Datei erfasst werden. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: //--------–––– vars ––––------------ var xmlBuecher:XML = new XML();
//------- functions –-------------––– function ladeXML(pWer:XML, pDatei:String):Void{ pWer.load(pDatei); }
//------------ aufruf –-------------
ladeXML(xmlBuecher,„literatur1.xml“);
Mit Hilfe des Operators new instanziieren wir die XMLKlasse, so dass wir auf ihre Methoden, Eigenschaften und Ereignisse zugreifen können. Der Operator erstellt also eine Art Kopie mit dem Bezeichner xmlBuecher, der die komplette Funktionalität der XML-Klasse besitzt. Anschließend deklarieren wir eine Lade-Funktion, die als Parameter das erstellte XML-Objekt sowie die zu ladende Datei enthält. Für den eigentlichen Ladevorgang ist die Methode load() zuständig, die als Argument eine XML-Datei erhält. Am Ende des Skripts rufen wir die Funktion auf und übergeben das anfangs definierte XML-Objekt sowie die im vorhergehenden Abschnitt erstellte XML-Datei namens literatur1.xml. Achten Sie darauf, dass die swf_Datei, die aus dieser Übung entsteht, und die angesprochene XML-Datei im selben Ordner liegen müssen. Andernfalls übergeben Sie beim Aufruf von ladeXML() einen entsprechenden Pfad. Findet Flash die angegebene Date nicht, erfolgt eine Fehlermeldung: Error opening URL „file:///F|/work/flash/literatur1. xml“
21.2 Laden von XML-Dokumenten (new XML, load(), onLoad, onData)
Die Sicherheitsrichtlinien von Flash erlauben prinzipiell nur den Aufruf von Daten derselben Domain. Wollen Sie domainübergreifend Daten verarbeiten, benötigen Sie eine Datei, in der die Domains eingetragen werden, auf die Sie zugreifen möchten. Wer an dieser Stelle Spektakuläres erwartet, wird enttäuscht: Wir machen nichts weiter als eine Datei zu laden. Es erfolgt keinerlei Zugriff auf deren Inhalt, und wir überprüfen nicht einmal den Ladevorgang, so dass wir nicht wissen, ob die fraglichen Daten überhaupt geladen wurden. 3. Ergänzen Sie die Deklaration von ladeXML() nach dem Aufruf der load()-Methode: pWer.onLoad = function(erfolg:Boolean) { if (erfolg) {
trace(„Erfolgreich geladen“); } else {
trace(„Sorry, habe die Datei verschlampt!“); }
285
falls tritt der else-Fall ein, der normalerweise eine Fehlerbehandlungsroutine umfasst und hier beispielhaft ein einfaches trace() ausführt. Der Name des Parameters kann frei gewählt werden; eingebürgert hat sich der hier verwendete Name oder dessen englisches Pendant success. Obwohl wir den Aufruf von load() vor dem Ereignis onLoad eingetragen haben, spielt die Reihenfolge im Skript keine Rolle, denn ein externer Ladevorgang wird von Flash erst dann ausgeführt, wenn alle anderen Anweisungen abgearbeitet wurden. Bei größeren Dateien empfiehlt es sich, den Anwender über den Fortschritt des Ladevorgangs zu informieren. Ähnlich wie die MovieClip-Klasse verfügt die XML-Klasse über die getBytesLoaded()- und getBytesTotal()-Methoden, die einen Vergleich der bereits geladenen Daten mit der Gesamtgröße erlauben. Wir benötigen für unser Beispiel zwar keine derartige Kontrolle. Im Fall der Fälle könnte man sie jedoch folgendermaßen implementieren (Änderungen gegenüber bisher in Fettdruck): //------------ vars –---------–––––----
};
var xmlBuecher:XML = new XML();
Ausgabe im Nachrichtenfenster: Erfolgreich geladen
//------------ functions –-------------
Flash bietet zwei Ereignisse, die eine Kontrolle des Ladevorgangs ermöglichen:
• onData: Tritt auf, wenn die angeforderte XML-
Datei vollständig vom Server übertragen wurde. Anschließend wird sie vom Flash-internen Parser verarbeitet und das onLoad-Ereignis aufgerufen. Mit Hilfe dieses Ereignisses können wir einen selbst definierten Parser aufrufen, falls benötigt. • onLoad: Tritt nach dem vollständigen Laden und Parsen der angegebenen XML-Datei auf. Dieses Ereignis wird i. d. R. verwendet, um eigene Routinen zwecks Zugriff auf die Inhalte der XML-Datei auszuführen. In unserem Beispiel setzen wir das onLoad-Ereignis ein. Es gibt einen Bool’schen Wert zurück, je nachdem, ob der Ladevorgang erfolgreich abgeschlossen werden konnte oder nicht. Um den betreffenden Wert abfragen zu können, richten wir einen Parameter namens erfolg ein und fragen seinen Inhalt ab. Beträgt er true, trifft die if-Bedingung zu und wir lassen uns eine entsprechende Meldung ausgeben. Andern-
var nIntervLaden:Number;
function ladeXML(pWer:XML, pDatei:String):Void{ pWer.load(pDatei);
pWer.onLoad = function(erfolg:Boolean) { if (erfolg) {
trace(„Erfolgreich geladen“); } else {
trace(„Sorry, habe die Datei verschlampt!“); }
clearInterval(nIntervLaden); }; }
function ladeKontrolle(pWer:XML):Void { var nGeladen:Number = pWer.getBytesLoaded(); var nGesamt:Number = pWer.getBytes Total(); var nProzent:Number = Math. floor((nGeladen/nGesamt)*100);
286
Kapitel 21 XML
trace(nProzent+“ Prozent geladen“);
trace(this.firstChild);
//------------ aufruf –-------------
Ausgabe im Nachrichtenfenster: Raymond Smullyan Logik-Ritter und andere Schurken <jahr>1991 Martin Gardner Gotcha <jahr>1987 Nicholas Falletta Paradoxon <jahr>1988
}
ladeXML(xmlBuecher, „literatur1.xml“);
nIntervLaden = setInterval(ladeKontrolle, 50, xmlBuecher);
Zunächst legen wir eine Variable an, in der wir später eine Referenznummer für ein noch einzurichtendes Intervall speichern. Unterlassen wir diesen Schritt, sind wir nicht in der Lage, das Intervall zu löschen, nachdem das gesamte Dokument geladen wurde. Die ladeKontrolle()-Funktion speichert mit getBytesLoaded() die aktuell geladenen und mit getBytesTotal() die Gesamtzahl der zu ladenden Bytes jeweils in einer lokalen Variable. Aus beiden lässt sich ein Prozentsatz errechnen, den wir abgerundet in einer weiteren Variable speichern. Sie wird anschließend ausgegeben. Am Ende des Skripts richten wir ein Intervall ein, das diese Kontroll-Funktion alle 50 Millisekunden ausführt und als Argument das XML-Objekt übergibt, in das die externen Daten geladen werden sollen. Tritt das onLoad-Ereignis ein, wurde der Ladevorgang beendet, so dass wir das Intervall löschen. Natürlich würde die Ausgabe bezüglich des Ladefortschritts per trace() auf Ihrer Website keinen Sinn machen, da dort das Nachrichtenfenster nicht zur Verfügung steht. Statt dessen würden Sie die betreffenden Angaben einem dynamischen Textfeld zuweisen. Beim lokalen Testen kommen Sie eventuell nicht in den Genuss von irgendwelchen Ausgaben, weil der Ladevorgang sehr schnell abläuft und Ihre XML-Datei zu klein ist.
21.3 Zugriff auf den gesamten Inhalt einer XML-Datei (firstChild, ignoreWhite) Liegen die Daten vor, möchte man naturgemäß mit ihnen arbeiten. 4. Ändern
Sie
innerhalb
der
Deklaration
der
ladeXML()-Funktion das onLoad-Ereignis (Fett-
druck):
if (erfolg) {
}
Anstelle der simplen Erfolgsmeldung greifen wir nun auf den konkreten Inhalt zu, indem wir uns den Wurzelknoten ausgeben lassen. Der Wurzelknoten wird mit firstChild angesprochen. Da er innerhalb eines XML-Dokuments immer den kompletten Inhalt umschließt, haben wir so Zugriff auf alle benötigten XML-Daten. Allerdings entspricht die Ausgabe nicht ganz unseren Wünschen: Wir erhalten beispielsweise nach jedem Element eine Leerzeile, die ihrerseits als Element gezählt wird. Würden wir das bestehende Dokument in dieser Form weiter verarbeiten, lägen unnütze Informationen vor, die wir nicht verwenden können. Etwas anders sieht die Ausgabe aus, wenn wir in der ersten Zeile des XML-Dokuments den oben erwähnten Hinweis auf die Version aufnehmen. In dem Fall wird die Versionsangabe als erster Knoten interpretiert und die Anzeige bleibt leer, da ihm kein verwertbarer Inhalt zugeordnet wurde. 5. Ändern Sie die Deklaration von ladeXML() (Fettdruck): function ladeXML(pWer:XML, pDatei: String, pBoolean:Boolean):Void {
6. Fügen Sie innerhalb der Deklaration von ladeXML() vor dem Aufruf von onLoad ein: pWer.ignoreWhite = pBoolean;
21.4 Verarbeitung einzelner Daten
7. Ändern Sie am Ende des Skript den Aufruf von ladeXML() (Fettdruck): ladeXML(xmlBuecher, „literatur1.xml“, true);
Ausgabe im Nachrichtenfenster: Raymond Smullyan Logik-Ritter und andere Schurken <jahr>1991Martin GardnerGotcha<jahr>1987Nicholas Falletta Paradoxon<jahr>1988 Mit der Eigenschaft ignoreWhite legen Sie fest, wie Flash Leerräume (z. B. Zeilenumbrüche, Leerzeichen, Tab-Stops) innerhalb des XML-Dokuments behandeln soll. Standardmäßig weist Flash dieser Eigenschaft false zu, so dass sie wie im vorhergehenden Beispiel als Information mit ausgelesen werden. I. d. R. benötigen wir diese Leeräume nicht, weswegen wir der betreffenden Eigenschaft true zuweisen. Nun wird auch ein Dokument, das in der ersten Zeile einen Hinweis auf die zugrunde liegende XML-Version enthält, korrekt dargestellt.
21.4 Verarbeitung einzelner Daten
287
wir Zugriff auf „Raymond Smullyan“ zuzüglich aller weiterer Angaben, die von umschlossen werden, also die Tags und <jahr>. 9. Erweitern Sie erneut das onLoad-Ereignis (Fettdruck): if (erfolg) {
trace(this.firstChild.firstChild. firstChild); }
Ausgabe im Nachrichtenfenster: Raymond Smullyan Da XML streng hierarchisch aufgebaut ist, verfügt auch der eben angesprochene -Tag über einen ersten Knoten, bestehend aus dem Tag sowie dem konkreten Text „Raymond Smullyan“. 10. Aller guten Dinge sind drei – erweitern Sie noch einmal das onLoad-Ereignis (Fettdruck):
if (erfolg) {
trace(this.firstChild.firstChild. firstChild.firstChild); }
Ausgabe im Nachrichtenfenster: Raymond Smullyan
8. Erweitern Sie innerhalb der Deklaration der ladeXML()-Funktion das onLoad-Ereignis (Fettdruck):
Nun erhalten wir den gewünschten Inhalt, nämlich den konkreten Namen des Autors einer Reihe lesenswerter Bücher zu Logikproblemen und -spielereien. Der Code mag recht umständlich und unübersichtlich erscheinen, spiegelt aber exakt den Aufbau bzw. die Verschachtelung unseres XML-Dokuments wieder. Allerdings besitzen wir so keine Möglichkeit, den nächsten Knoten des Wurzelknotens zu erfassen. Hier hilft die Eigenschaft childNodes weiter.
if (erfolg) {
11. Ändern Sie das onLoad-Ereignis (Fettdruck):
Die für uns interessanten Informationen stecken in den einzelnen Knoten des XML-Dokuments. Daher benötigen wir eine Möglichkeit, sie auszulesen, ohne dass uns jedes Mal der gesamte Wurzelknoten angezeigt wird.
trace(this.firstChild. firstChild); }
Ausgabe im Nachrichtenfenster: Raymond Smullyan Logik-Ritter und andere Schurken<jahr>1991 Der erste Knoten, also das gesamte Dokument, besitzt seinerseits einen ersten Knoten, nämlich die konkreten Daten, die wir beim erstmaligen Auftreten des -Tags eingetragen haben. Dadurch erhalten
if (erfolg) {
trace(this.firstChild.childNodes); }
Ausgabe im Nachrichtenfenster: Raymond Smullyan Logik-Ritter und andere Schurken<jahr>1991,Martin Gardner Gotcha<jahr>1987,Nicholas Falletta Paradoxon<jahr>1988
288
Kapitel 21 XML
Zunächst sieht das Ergebnis genauso aus, wie zuvor beim Zugriff mit Hilfe von firstChild. Die Eigenschaft childNodes gibt uns jedoch die gewünschten Informationen in Form eines Arrays zurück, so dass wir gezielt beliebige Elemente aufrufen können. 12. Ergänzen Sie das onLoad-Ereignis (Fettdruck):
if (erfolg) {
t race(this.firstChild.childNodes[0]); }
Ausgabe im Nachrichtenfenster: Raymond Smullyan Logik-Ritter und andere Schurken<jahr>1991 Übrig bleibt der erste Knoten von firstChild, also das erste -Tag. Im Gegensatz zu vorher können wir nun alle weiteren Knoten auslesen. Wenn Sie beispielsweise den Index von 0 auf 1 ändern, zeigt Flash die Informationen zum zweiten Kindknoten des Wurzelknotens an. Mit einer Schleife lassen sich nun alle Knoten einzeln ansprechen. 13. Ändern Sie das onLoad-Ereignis (Fettdruck):
if (erfolg) {
var aNodes:Array = this.firstChild. childNodes; for (var i:Number = 0; i1991 Kindknoten 1: Martin GardnerGotcha<jahr>1987 Kindknoten 2: Nicholas FallettaParadoxon<jahr>1988 Wir speichern in dem Array aNodes den kompletten Inhalt des Dokuments, wobei ein Kindknoten jeweils einem Element des genannten Arrays entspricht. An-
schließend greifen wir in einer Schleife, deren Zählvariable sich an der Gesamtlänge des Arrays orientiert, auf die konkreten Inhalte elementweise zu. Alternativ lässt sich für aNodes auch der Datentyp String oder, ganz allgemein, Object angeben. Ein -Tag umfasst seinerseits Kindknoten, die wir nun einzeln ansprechen wollen. 14. Ergänzen Sie die erstellte Schleife (Fettdruck): for (var i:Number = 0; i= anstatt ==). Der Debugger hat also in diesem sehr simplen Beispiel geholfen, durch Beobachtung der kritischen Werte einem logischen Problem auf die Sprünge zu kommen. Natürlich wird Ihr Code im Fall der Fälle komplexer ausfallen, aber die Vorgehensweise ist dieselbe: Sie versuchen immer, diejenigen Werte, die verantwortlich für das unbotmäßige Verhalten Ihrer Anwendung sein könnten, zu überprüfen. Ist nicht direkt klar, um welche Werte es sich handelt, lässt man sich sukzessive verschiedene Variablen bzw. Eigenschaften ausgeben. Über die Kontrolle von Werten hinaus lässt es der Debugger auch zu, den Ablauf eines Skriptes gezielt zu verfolgen. Dazu können an beliebiger Stelle Haltepunkte (break points) gesetzt werden, um dann Stück für Stück weiter zu gehen. 23. Kehren Sie zum Scriptfenster zurück. 24. Klicken Sie dort mit der linken Maustaste links neben die Zahl 7, die eine Zeilennummer angibt. Auf diese Weise wird ein Haltepunkt gesetzt, der im Debugger den Ablauf der Applikation anhält. 25. Starten Sie wieder den Debugger und die Applikation mit Hilfe des grünen Pfeils. 26. Klicken Sie auf die Kugel. Nun bewegt sie sich nicht mehr wie zuvor. Statt dessen öffnet sich in der rechten Hälfte des Debuggers ein neues Fenster, in dem der aktuelle Code aufgelistet wird. Ein Pfeil markiert die Stelle, an der die Applikation angehalten wurde – es handelt sich um dieselbe Zeile, in der Sie den Haltepunkt eingefügt haben. Zusätzlich zeigt links unten ein kleines Fenster den Call Stack, also das Element, das gerade ausgeführt wurde,
Kapitel 23 Debugging
als der Haltepunkt erreicht wurde. Konkret handelt es sich um das onEnterFrame-Ereignis, innerhalb dessen wir die Funktion bewegen() permanent aufrufen lassen. Nun können Sie sukzessive den gesamten Ablauf quasi mit einer Zeitlupe verfolgen. 27. Wählen Sie unter den links oben aufgelisteten Objekten _level0. 28. Lassen Sie die Variable nVelX überwachen wie oben gezeigt. 29. Klicken Sie in der Symbolleiste oberhalb des angehaltenen Skriptes auf das Symbol , also den nach rechts weisenden Pfeil. Flash führt nun die angehaltene Codezeile aus und stoppt in der nächsten. Gleichzeitig können Sie sehen, wie nVelX von 0 auf 0.1 erhöht wird. 30. Klicken Sie erneut auf . Wieder geht Flash eine Zeile weiter. Bei jedem Klick wird die aktuelle Zeile ausgeführt und die nächste sich aus der Programmlogik ergebende Zeile markiert. Sind Sie am Ende von bewegen() angelangt, springt Flash wieder auf dessen erste Anweisung und der gesamte Prozess wiederholt sich. Nun steht Ihnen nicht nur der jeweils geänderte Wert von nVelX zur Verfügung, sondern Sie können darüber hinaus auch den konkreten Programmablauf verfolgen. So sehen Sie, was im Einzelnen geschieht und ob beispielsweise eine Funktion, die Sie benötigen, überhaupt nicht oder zum falschen Zeitpunkt aufgerufen wird. Auch unabhängig von einer konkreten Fehlersuche können die trace()-Anweisung und vor allem der Debugger von großem Nutzen sein, insbesondere, wenn Sie noch über eher weniger Erfahrung im Bereich der Programmierung verfügen. Denn beide ermöglichen es uns, erheblich genauer die Ausführung eines Skripts nachzuvollziehen. Wir können beide Tools einsetzen, um zu verstehen wie ein Skript aufgebaut ist und was wann geschieht. Selbst jemand mit ausreichend Erfahrung wird in dem einen oder anderen Fall noch auf sie zurück greifen, etwa, wenn es gilt, nicht oder nur schlecht dokumentierten und wüst durcheinander gewürfelten (Fremd-) Code zu verdauen. Nun sind Sie soweit gewappnet, dass wir uns an konkrete Projekte machen können, um das Gelernte umzusetzen.
Geskriptete Animationen
Bereits mehrfach haben wir im vorhergehenden Kapitel bei der Behandlung von MovieClips auf die Möglichkeit verwiesen, welche Flash für das Skripten von Animationen bietet. Ein solcher Verweis mag sonderbar klingen bei einem Programm, das seine Stärke doch eigentlich nicht zuletzt aus den einfach zu handhabenden, aber mächtigen Animationstechniken von MovieClip und Zeitleiste zieht. In relativ kurzer Zeit entstehen mittels zahlreicher Effekte (z. B. Tweens, Ebeneneffekte, Filter) hochkomplexe und (hoffentlich) ästhetische Animationen. Dennoch bietet gerade das Skripting ein Potential für Animationen, das je nach Aufgabenstellung weit über das hinausgeht, was ohne Programmierung möglich ist. Der wichtigste Vorteil besteht zweifelsohne in der Möglichkeit, dem Anwender Interaktionen zu gestatten. Er kann Einfluss auf den Ablauf der Animation nehmen, sie etwa starten und stoppen oder das Tempo verändern etc. Ein weiterer Vorteil besteht in der einfacheren Wiederverwendung: Grafikunabhängiger Code lässt sich erheblich bequemer in verschiedenen Anwendungen einsetzen als wenn man mühsam Zeitleistenanimationen per copy-and-paste oder als fertige swf-Datei in andere Flashfilme integrieren muss. Und last not least können wir per AS Animationen realisieren, die händisch nicht möglich sind. So gibt es, um nur ein Beispiel zu nennen, Filter, die ausschließlich geskriptet werden können. Um Missverständnissen vorzubeugen: Das heißt natürlich nicht, dass man ab sofort und für alle Ewigkeit Animationen nur noch programmieren sollte. Eine derartige Einstellung wäre absurd, würde man doch damit die eben optimal auf Animationen abgestimmten Werkzeuge des Autorensystems zu Unrecht ignorieren. Vielmehr kommt es darauf an, sich genau zu überlegen, was man erreichen möchte. In manchen
24
Fällen wird es einfacher sein, mit den Zeichenwerkzeugen und der Zeitleiste eine Animation zu erstellen, während andere Fälle nur eine geskriptete Lösung erlauben. Wir befassen uns hier naturgemäß mit der letztgenannten Kategorie.
24.1 Animationskategorien Prinzipiell stellt eine Animation eine Eigenschaftsänderung im zeitlichen Verlauf dar. Zwei Dinge spielen also eine Rolle:
• Objekteigenschaften.
Notwendigerweise handelt es sich um Eigenschaften, die wir optisch und/oder akustisch wahrnehmen können. Das ideale Objekt stellt in dieser Hinsicht der MovieClip dar, der über zahlreiche visuelle Eigenschaften verfügt, die sich manipulieren lassen. Daher verwundert es auch nicht, dass er der absolute Favorit bei Flash-Animationen ist. Ebenfalls häufig verwendet wird Text. • Mindestens zwei verschiedene Zeitpunkte, die Anfang und Ende der Animation markieren. Zu diesen Zeitpunkten ändert sich mindestens eine Eigenschaft eines Objekts, so dass eine Animation vorliegt. Wir wollen uns im folgenden auf visuell wahrnehmbare Animationen beschränken und behandeln einige prinzipielle Vorgehensweisen, während Sound außen vor bleibt. Zahlreiche konkrete Umsetzungen finden Sie in den weiteren Workshops. Trotz der vielen Varianten und Versionen, in denen Animationen anzutreffen sind, lassen sie sich, bezogen auf die zeitliche Dauer und die Anzahl der verschiedenen Animationszustände (bzw. der verschiede-
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
317
318
Kapitel 24 Geskriptete Animationen
nen Werte, welche die zu verändernden Eigenschaften annehmen können), in Kategorien unterteilen:
Denkbar wäre etwa, ein Objekt genau 12 Sekunden drehen zu lassen.
• Eine Animation tritt einmalig auf. Zum Beispiel
Schließlich spielt noch eine Rolle, wie autonom der Ablauf erfolgt:
wird ein zunächst nicht sichtbares Objekt übergangslos eingeblendet. Dabei unterscheidet man zwei Zustände: Nicht sichtbar – sichtbar. Der zeitliche Verlauf der Animation ist ausgesprochen kurz. Die betroffenen Eigenschaften kennen nur zwei Zustände: Startwert und Endewert. • Eine Animation findet eine genau definierte Zeit lang statt und endet dann. Beispielsweise wird ein Objekt über eine gewisse Strecke animiert und stoppt, sobald es seinen Zielpunkt erreicht hat. Der zeitliche Verlauf variiert zwar im Gegensatz zur erstgenannten Variante. Es existiert jedoch prinzipiell ein genau definiertes zeitliches Ende der Animation. Diese Form der Animation kennt eine prinzipiell begrenzte, genau definierte Anzahl an Zuständen. • Eine Animation läuft permanent ab. Denkbar wäre etwa ein Objekt, das sich im Hintergrund unserer Anwendung permanent hin- und herbewegt. Programmiertechnisch existiert keine zeitliche Begrenzung; sie wird allenfalls vorgegeben durch die Dauer, die der Anwender mit unserer Applikation verbringt. Die Anzahl der verschiedenen Animationszustände dagegen kann begrenzt sein wie im genannten Beispiel des sich bewegenden Objekts, oder sie kann unbegrenzt sein wie etwa bei vielen animierten Bildschirmschonern. Der Ablauf einer Animation gehorcht in jedem Fall bestimmten Regeln. Das gilt sogar für rein zufällige Animationen, bei denen eben der Zufall selbst die besagte Regel darstellt. Auch derartige Regeln lassen sich kategorisieren:
• Anzahl der Schritte. Wir können festlegen, wie oft
eine Eigenschaftsänderung erfolgen soll. Beispielsweise möchten wir zehnmal den Alphawert eines Objekts ändern, um von 100 % zu 0 % zu gelangen. • Schrittweite. Alternativ ist es möglich, die Größe der Eigenschaftsänderung zwischen zwei Animationsschritten vorzugeben. So könnten wir ein Objekt jedes Mal um 25 Pixel nach rechts verschieben oder die Schrittweite dynamisch nach einer bestimmten Regel ermitteln, so dass beispielsweise jeder Schritt genau der Hälfte des vorhergehenden entspricht, was optisch einen Bremseffekt ergibt. • Zeitliche Dauer. Hier definieren wir den zeitlichen Abstand zwischen Animationsbeginn und –ende.
• Nicht-interaktiv. Der Anwender kann in keiner Weise
in die Animation eingreifen. Sie läuft automatisch ab und wird ausschließlich vom System kontrolliert. • Interaktiv. Alternativ kann der Anwender mit der Animation interagieren, wobei verschiedene Grade möglich sind, die sich auf Start, Ende sowie diverse Parameter der Animation (z. B. Tempo, Dauer, Form) beziehen. Die genannten Kategorien existieren nicht vollständig unabhängig voneinander, sondern beeinflussen sich gegenseitig. Denn jede Festlegung der Schrittanzahl oder Schrittweite bestimmt beispielsweise automatisch die Gesamtdauer. Nur legen wir dabei die Dauer nicht in Sekunden fest, ja wir kennen noch nicht einmal den exakten Wert der Gesamtdauer, es sei denn, wir würden ihn explizit beispielsweise aus den Einzelschritten errechnen. Was so hübsch abstrakt und akademisch klingen mag, hat unmittelbare praktische Auswirkungen, denn ein konkretes Skript muss die Kategorie berücksichtigen, der unsere erwünschte Animation zugehört. Wenn wir z. B. nur zwei exakt beschriebene Zustände haben, lösen wir die Animation durch ein anderes Ereignis aus, als wenn wir eine permanente Animation definieren wollen. Wenn die Animation in zehn Schritten ablaufen soll, benötigen wir eine entsprechende Zählvariable. Soll sie dagegen genau eine Minute dauern, müssen wir die Uhrzeit kontrollieren.
24.2 Einmalige Animation In der einfachsten Form liegt eine Eigenschaftsänderung zwischen zwei Zeitpunkten vor, die Animation tritt also einmalig auf. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects in der Bühnenmitte ein Rechteck beliebiger Farbe (60 × 60). 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcRecht, Instanzname recht, Registrierungspunkt mittig). 4. Weisen Sie actions folgendes Bildskript zu: recht.onPress = function(){
24.3 Animation mit onEnterFrame und fester Schrittweite
this._visible = false; }
Wenn Sie beim Testen auf das Objekt klicken, wird es erwartungsgemäß unsichtbar. Gratulation, damit haben Sie Ihre erste Animation geskriptet, ein Ereignis, dem Sie einige Zeilen in Ihrem Tagebuch widmen dürfen. Zugegeben, das ist nicht ganz zutreffend, denn Sie haben diese einfache Form der Animation bereits im Kapitel über Movie Clips kennen gelernt. Sie greifen per Mausklick auf die Eigenschaft _visible zu und weisen ihr den Wert false zu, so dass das betreffende Objekt nicht mehr angezeigt wird. Selbstverständlich kann sich eine einmalige Animation auf beliebige Eigenschaften beziehen, die weniger drastische Veränderungen hervorrufen als die Sichtbarkeit. So können Sie beispielsweise die Position, Drehung oder Größe einmalig ändern. Das Prinzip ist das Gleiche wie in unserem Beispiel mit der Sichtbarkeit.
24.3 Animation mit onEnterFrame und fester Schrittweite In der Regel verknüpfen wir mit dem Begriff der Animation länger andauernde Phasen, also wiederholte oder permanente Animationen. Um sie realisieren zu können benötigen wir ein anderes Ereignis, das dieses zeitliche Moment besser abbildet. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects in der Bühnenmitte ein Rechteck beliebiger Farbe (60 × 60). 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcRecht, Instanzname recht, Registrierungspunkt mittig). 4. Weisen Sie actions folgendes Bildskript zu:
319
Flash führt das onEnterFrame-Ereignis dauernd aus und erhöht jedes Mal die Rotation von recht um 10 Grad, dem Wert der Variablen nRotat, mit der wir die Schrittweite definiert haben. Bei Start beträgt die Rotation 0 Grad. Im ersten onEnterFrame erhöhen wir diesen Wert um 10, was in der Summe 10 ergibt. Im nächsten onEnterFrame wird 10 hinzu addiert, woraus sich eine Drehung um 20 Grad ergibt usw. Da wir das onEnterFrame-Ereignis an keiner Stelle unterbrechen oder abbrechen, dreht sich das Objekt ohne Unterlass im Uhrzeigersinn. Falls Sie eine Drehung gegen den Uhrzeigersinn erreichen wollen, müssen Sie lediglich das Vorzeichen von nRotat ändern: var nRotat:Number = -10;
Alternativ führen Sie im onEnterFrame eine Subtraktion durch (in dem Fall enthält nRotat einen positiven Wert): this._rotation –= nRotat;
Den Ablauf der Animation visualisiert Abbildung 87. Selbstverständlich lassen sich andere Eigenschaften ebenso einfach animieren. Am häufigsten verwendet man die Position. 6. Erweitern Sie den Variablen-Block: var nTempoX:Number = 12;
7. Erweitern Sie das onEnterFrame-Ereignis nach der öffnenden Klammer: this._x += nTempoX;
Nun rollt sich Ihr Rechteck nach rechts aus der Bühne hinaus.
//-------------- vars –------------- var nRotat:Number = 10;
5. Fügen Sie einen Funktions-Block ein: //------------- functions –--------- recht.onEnterFrame = function(){ this._rotation += nRotat; }
Wenn Sie den Film starten, wird sich das Rechteck permanent um seine eigene Achse drehen.
Abbildung 87: Animation durch permanente Änderung der Drehung
320
Kapitel 24 Geskriptete Animationen
if(this._y >= Stage.height){ this._y = 0; }
Abbildung 88: Horizontale und vertikale Bewegung
Zusätzlich zur Drehung ändern wir permanent die horizontale Position, indem wir jeweils den Inhalt von nTempoX hinzu addieren. Damit verschiebt sich das Rechteck bei jedem onEnterFrame um 12 Pixel nach rechts. Eine Bewegung nach links können Sie auf die gleiche Weise wie oben per Vorzeichenumkehr bzw. Subtraktion erreichen. 8. Erweitern Sie die Variablendeklaration: var nTempoY:Number = 4;
9. Erweitern Sie das onEnterFrame-Ereignis: this._y += nTempoY;
Zusätzlich zur horizontalen erhält das Rechteck eine kleinere vertikale Bewegung, so dass es sich in der Summe nach rechts unten bewegt. Nach kurzer Zeit verschwindet es am rechten Rand, da die Animation, auch wenn wir es nicht mehr wahrnehmen können, endlos andauert. Das ist kein Grund zur Traurigkeit, besteht doch die Möglichkeit, das Rechteck höflich, aber bestimmt aufzufordern, wieder zurückzukehren. 10. Erweitern Sie das onEnterFrame-Ereignis unmittelbar vor der schließenden Klammer: if(this._x >= Stage.width){ this._x = 0; }
Bei Erreichen des rechten Randes wird das Rechteck am linken Rand wieder eingefügt. Mit der if-Bedingung kontrollieren wir permanent die horizontale Position. Wenn sie einen bestimmten Wert – hier: rechter Bühnenrand – übersteigt, weisen wir recht eine neue x-Position zu. Da jedoch auch eine vertikale Bewegung stattfindet, reicht das nicht aus, um unser Objekt auf der Bühne zu behalten. Denn nach zwei Durchläufen hat sich unser Rechteck endgültig aus dem digitalen Staub gemacht. 11. Fügen Sie nach Ende der if-Bedingung und vor Ende des onEnterFrame-Ereignisses eine weitere if-Bedingung ein:
Zusätzlich zur x-Position kontrollieren wir permanent die y-Position. Sobald horizontal oder vertikal der angegebene Grenzwert überschritten wird, setzen wir das Rechteck in der betreffenden Eigenschaft auf den Koordinatenursprung, also 0. Jetzt läuft unsere Animation permanent ab, ohne dass das Rechteck aus dem Blickfeld verschwinden kann. In diesem Beispiel ändert sich die Animation ständig, da das Erreichen der Ränder nicht synchron erfolgt. Mal trifft die Bedingung zu, die sich auf die x-Position bezieht, ein anderes mal diejenige für die y-Position. Soll immer exakt dieselbe Animation erfolgen, müssen wir den von der Bedingung abhängigen Code ändern und die Startposition speichern. 12. Erweitern Sie den Variablen-Block um: var nPosX:Number = recht._x; var nPosY:Number = recht._y;
Das Rechteck startet an einer ganz bestimmten Position, die wir beim Erstellen der Anwendung festgelegt haben. Um später wieder darauf zurückgreifen zu können, speichern wir diese Position in zwei Variablen. 13. Ändern Sie die if-Bedingung für die horizontale Position wie folgt (Fettdruck): if(this._x >= Stage.width){ this._x = nPosX; this._y = nPosY; }
Wenn Sie testen, wird das Rechteck immer dieselbe Animation abspielen: Bewegung nach rechts unten bis zum Erreichen des rechten Bühnenrandes, dann Neustart auf der ursprünglichen Position. Da wir anfangs die Startposition gespeichert haben, müssen wir diese zuweisen, sobald die if-Bedingung zutrifft. In unserem Fall funktioniert der Code problemlos, da die vertikale Geschwindigkeit erheblich niedriger ist als die horizontale. Daher erreicht das Rechteck den rechten vor dem unteren Rand. 14. Ändern Sie die Initialisierung von nTempoY (Fettdruck):
var nTempoY:Number = 14;
Nun bewegt sich das Rechteck schneller nach unten
24.3 Animation mit onEnterFrame und fester Schrittweite
als nach rechts. Da zuerst die Bedingung hinsichtlich der y-Position zutrifft, wird das Rechteck auf fehlerhafte Werte zurück gesetzt. 15. Korrigieren Sie die Abfrage der y-Position (Fettdruck): if(this._y >= Stage.height){ this._x = nPosX; this._y = nPosY; }
Jetzt spielt es keine Rolle mehr, ob das animierte Objekt zuerst den rechten oder den unteren Rand erreicht. In beiden Fällen erfolgt das Zurücksetzen auf die ursprüngliche Position, so dass die Animation wieder von vorne ablaufen kann. Da wir unabhängig davon, welche Bedingung zutrifft, immer die gleichen Anweisungen ausführen, nämlich Zuweisung einer x- und y-Position, lassen sich die Abfragen vereinfachen. 16. Ersetzen Sie beide if-Bedingungen durch: if(this._x >= Stage.width || this._y >= Stage.height){ this._x = nPosX; this._y = nPosY; }
Beim Testen ergibt sich visuell kein Unterschied. Der Code vereinfacht sich jedoch, indem wir unsere Bedingungen mit einem logischen Oder verknüpfen. In beredter Prosa formuliert lautet unsere Bedingung: Wenn die x-Position des Rechtecks größer oder gleich der Bühnenbreite ODER die y-Position des Rechtecks größer oder gleich der Bühnenhöhe ist, dann setze dessen x- und y-Position auf die in den angegebenen Variablen gespeicherten Werte. Damit haben wir zwar die Bewegung nach rechts unten definiert, doch was geschieht, wenn eine oder beide Tempovariablen einen negativen Wert enthalten? 17. Ändern Sie die Initialisierung von nTempoX und nTempoY (Fettdruck):
321
nach links oben – und verschwindet über den Rand hinaus. Denn die von uns definierten Bedingungen können nie zutreffen, da sie sich ausschließlich auf den rechten und unteren Rand beziehen. 18. Erweitern Sie die if-Bedingung (Fettdruck): if(this._x >= Stage.width || this._y >= Stage.height || this._x Stage.width || this._x < 0){
Nun bewegt sich der Kreis permanent zwischen rechtem und linkem Rand hin und her. Die Umkehr der Bewegung erfolgt einfach über eine Umkehr des Vorzeichens der Tempovariablen. Wenn sich der Kreis nach rechts bewegt, weil die Variable einen positiven Wert enthält, führt die Vorzeichenumkehr dazu, dass der Wert negativ wird. Die Addition eines negativen Wertes entspricht, wie wir bereits mehrfach gesehen haben, einer Subtraktion, der Kreis bewegt sich also nach links. Dort am Rand angekommen, kehren wir das Vorzeichen erneut um, so dass aus der negativen wieder eine positive Zahl wird. Die permanente Addition sorgt dann wieder für eine Bewegung nach rechts. Anstelle einer Multiplikation mit –1 können wir das Vorzeichen auch folgendermaßen umkehren:
24.3 Animation mit onEnterFrame und fester Schrittweite
323
Erreichen wir den rechten Rand, setzen wir den Wert auf –100 %, was einer horizontalen Spiegelung entspricht. Der Kreis schaut nun nach links, seiner neuen Bewegungsrichtung. Am linken Rand wiederholen wir den Vorgang, so dass aus –100 wieder der ursprüngliche Wert 100 entsteht, was zu einer erneuten Spiegelung führt. Der Kreis wird mit seiner ursprünglichen Blick- und Bewegungsrichtung dargestellt. Betrachten wir nun die betreffende Lösung für ein Ein- und Ausblenden. Abbildung 89: Bewegung und Vorzeichenumkehr
nTempoX = -nTempoX;
Neben der Eigenschaftsänderung durch eine Addition zeigt sich hier ein weiteres wichtiges Animationsprinzip, nämlich die Vorzeichenumkehr. Sie ermöglicht es auf eine simple Art, prinzipiell jede permanente Animation, deren Start- und Endwert durch eine Addition abgebildet werden kann, zu realisieren. Dabei spielt es keine Rolle, ob es sich bei der angesprochenen Eigenschaft wie hier um die Position handelt, oder die Deckkraft, Größe und Drehung. In allen Fällen können wir mit Addition und Vorzeichenumkehr arbeiten. Im Kapitel zur MovieClip-Klasse haben wir die Eigenschaften _xscale und _yscale kennen gelernt. Wir können sie in unserem Fall verwenden, um den Eindruck zu erwecken, als drehe sich unser Kreis tatsächlich um, wenn er die Richtung wechselt. 10. Öffnen Sie mcKreis im Symbolbearbeitungsmodus. 11. Weisen Sie ihm zwei dunkle Ellipsen als Augen zu, die nach rechts zeigen. 12. Erweitern Sie die if-Bedingung (Fettdruck): if(this._x > Stage.width || this._x < 0){ nTempoX *= -1;
this._xscale *= -1; }
Jedes mal, wenn der Kreis einen Rand erreicht, dreht er sich um und bewegt sich in die entgegen gesetzte Richtung. Wir verwenden dasselbe Prinzip wie im Fall der Bewegung, indem wir einfach das Vorzeichen der Eigenschaft _xscale umdrehen. Ihr Wert beträgt, wenn keine Änderung vorgenommen wurde, 100 %.
1. Erstellen Sie eine Standarddatei. 2. Importieren Sie eine Grafik beliebiger Größe in die Bibliothek. 3. Erstellen Sie einen leeren MovieClip namens mcPict. 4. Fügen Sie dort auf der Position 0,0 die importierte Grafik ein. Damit wird die linke obere Ecke des MovieClips zu seinem Registrierungspunkt. 5. Weisen Sie mcPict eine gleichnamige Verknüpfung zu. 6. Kehren Sie zur Hauptzeitleiste zurück. 7. Weisen Sie actions folgendes Bildskript zu: //-–----------- vars –---------------
var mBeh:MovieClip = this.createEmpty MovieClip(„behaelter“, this.getNextHighestDepth()); var mPict:MovieClip;
var nAlpha:Number = -2;
Zunächst richten wir wieder einen Behälter sowie eine Variable zur Aufnahme des Bildes ein. Anschließend legen wir das Tempo der Animation fest. Da wir im ersten Schritt subtrahieren müssen, enthält die Variable einen negativen Wert. 8. Erweitern Sie das Skript: //--––--------- functions –--------- function init():Void {
mPict = mBeh.attachMovie(„mcPict“, „pict“, 1); mPict._x = mPict._y = 0;
mBeh._x = Stage.width/2-mBeh._ width/2;
mBeh._y = Stage.height/2-mBeh._ height/2; }
//--------------- aufruf –---------- init();
324
Kapitel 24 Geskriptete Animationen
Flash fügt den händisch erstellten MovieClip mit der importierten Grafik mittig auf der Bühne ein. Wir weisen in der init()-Funktion der anfangs deklarierten Variable mPict eine Referenz auf den aus der Bibliothek per attachMovie() eingefügten Clip zu. Innerhalb des Behälters positionieren wir ihn auf der Position 0,0. Wir hätten auf diese Zeile zwar verzichten können, denn Flash weist automatisch diese Position zu, aber das wäre eher unsauber. Den Behälter möchten wir gerne exakt in die Bühnenmitte setzen. Die horizontale Mitte entspricht der halben Bühnenbreite. Da der Registrierungspunkt von mBeh links oben liegt (Sie erinnern sich: Das attachMovie() wurde auf 0,0 ausgeführt), müssen wir ihn zusätzlich um seine halbe Breite nach links verschieben. Dasselbe führen wir mit der vertikalen Position durch.
keit hin- und herpendeln wollen. Größere Flexibilität besitzen wir jedoch, wenn wir die betreffenden Werte als Argumente übergeben, was wir uns am Beispiel einer permanenten Größenänderung anschauen wollen.
9. Erweitern Sie die init()-Funktion nach der Zuweisung für mBeh._y:
this._xscale += pScaling;
1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects in der Bühnenmitte ein Rechteck (40 × 60, Farbe beliebig). 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcRecht, Instanzname recht, Registrierungspunkt mittig). 4. Weisen Sie actions folgendes Bildskript zu: //-------------- functions –-------- function init(pWer:MovieClip, pScaling:Number):Void {
pWer.onEnterFrame = function() { this._yscale = this._xscale;
mBeh.onPress = function(){
};
this._alpha += nAlpha;
//------------- aufruf –-------------
this.onEnterFrame = function(){
}
if(this._alpha = 100){
init(recht, 10);
nAlpha *= -1; } }
delete this.onPress; }
Bei Mausklick auf die Grafik blendet sie permanent langsam aus und wieder ein. Wir weisen dem Behälter ein Mausklick-Ereignis zu, das seinerseits ein onEnterFrame-Ereignis aufruft. Darin ändern wir den Alpha-Wert des Behälters auf die gleiche Weise, wie wir zuvor die Position des Rechtecks animiert haben. Wir addieren zunächst den in der Variablen nAlpha initialisierten Wert. Da er negativ ist, erfolgt eine Subtraktion. Anschließend fragen wir permanent den aktuellen Alpha-Wert ab. Ist er kleiner als oder gleich 0 bzw. größer als oder gleich 100, so drehen wir das Vorzeichen von nAlpha um und steuern so die Addition bzw. Subtraktion. Die Grenzwerte müssen nicht zwangsläufig im Klartext in die Bedingung geschrieben werden. Im obigen Beispiel empfiehlt es sich, da wir i. d. R. zwischen völliger Sichtbarkeit und völliger Unsichtbar-
Wenn Sie testen, springt Ihnen schon nach kürzester Zeit scheinbar ein wild gewordenes Rechteck ins Gesicht. Wir definieren unsere init()-Funktion, die zwei Parameter entgegen nimmt: Den Instanznamen des zu animierenden Objekts und die Schrittgröße bzw. Animationsgeschwindigkeit. Der übergebenen Instanz weisen wir ein onEnterFrame zu, innerhalb dessen permanent die Skalierung in x-Richtung um den angegebenen Wert erhöht wird. Um eine gleichmäßige Skalierung zu gewährleisten, setzen wir die y-Skalierung auf den Wert der x-Skalierung. Beim Aufruf der init()-Funktion übergeben wir als Argumente die Instanz recht sowie das Tempo 10. Da wir jedoch noch keinen Grenzwert definiert haben, wird die Skalierung endlos ausgeführt. 5. Fügen Sie innerhalb des onEnterFrame-Ereignisses nach der Zuweisung für _yscale folgende ifBedingung ein: if (this._xscale>=pMaxScale || this._ xscale=Stage.width || mKreis._x=Stage.width || pWer._ x nGrenze){
7. Erweitern Sie die Funktion bewegung() um folgende if-Bedingung (Fettdruck): function bewegung(pWer: MovieClip, pWohin: Number):Void{ pWer.onEnterFrame = function(){ nDif = pWohin – this._x;
this._x += nDif * nFaktorX; }else{
this._x = pWohin;
delete pWer.onEnterFrame; } } }
Das Ausführen der Bewegung machen wir jetzt von der Größe der Entfernung abhängig. Nur wenn diese über einem bestimmten Wert liegt – hier konkret: 0.5 – addieren wir den errechneten Wert zur x-Position. Wichtig ist, dass wir in nGrenze einen deutlich niedrigeren Wert speichern als 1. Andernfalls können wir am Ende der Animation mehr oder minder deutlich einen kleinen Ruck wahrnehmen. Sie können das gerne testen, indem Sie nGrenze mit 1 initialisieren. Da die if-Bedingung auch dann korrekt ausgeführt werden muss, wenn sich der Kreis nach links bewegt, nDif also einen negativen Wert enthält (der in jedem Fall kleiner 0.5 ist), verwenden wir an dieser Stelle den vorzeichenunabhängigen Wert mit Hilfe von Math.abs(). Trifft die Bedingung nicht mehr zu, befindet sich der Kreis so nahe am Zielpunkt, dass wir ihn, wie im else-Fall definiert, direkt auf die gewünschte Position setzen und das nun nicht weiter benötigte onEnterFrame löschen können. Die Deklaration von bewegung() ist logisch korrekt, lässt sich aber noch etwas vereinfachen. 8. Ändern Sie die Deklaration von bewegung() folgendermaßen (Fettdruck): function bewegung(pWer: MovieClip, pWohin: Number):Void{ pWer.onEnterFrame = function(){
24.7 Feste Schrittanzahl
nDif = pWohin – this._x;
333
this._x += nDif * nFaktorX;
letzte Variable dient dazu, abhängig von der Gesamtzahl und der zurück zu legenden Strecke das Tempo pro Einzelschritt zu ermitteln.
this._x = pWohin;
4. Erweitern Sie das Skript um folgende Funktionsdeklaration:
delete pWer.onEnterFrame;
//------------- functions –----------
if(Math.abs(nDif) =nMaxSchritt) {
var nMaxSchritt:Number = 10;
delete pWer.onEnterFrame;
Zunächst benötigen wir eine Zählvariable, mit deren Hilfe wir herausfinden können, wie viele Schritte bereits vollzogen wurden. Sie startet notwendigerweise mit dem Wert 0. In der Variablen nMaxSchritt legen wir die Gesamtzahl der gewünschten Schritte fest. Die
};
var nSchritt:Number = 0;
this._x = pWohin;
var nTempoX:Number;
} }
//------------ aufruf –------------- init(kreis, kreis._width/2, Stage. height/2, 420);
334
Kapitel 24 Geskriptete Animationen
Das lässt sich in einem einfachen Test überprüfen. 6. Initialisieren Sie testweise nMaxSchritt mit einem anderen Wert: nMaxSchritt = 7;
7. Kommentieren Sie im onEnterFrame-Ereignis innerhalb des if-Blocks die Positionszuweisung aus (Fettdruck): //this._x = pWohin; Abbildung 92: Berechnung der Schrittweite aus Schrittanzahl und Entfernung
Die Funktion bewegung() erhält, wie erwähnt, beim Aufruf die Instanz, die wir animieren wollen, sowie das Ziel. Das Tempo, mit dem die Bewegung erfolgt, ergibt sich aus der Entfernung zwischen Ziel und aktueller Position dividiert durch die Anzahl der einzelnen Schritte. Wenn wir beispielsweise an der x-Position 20 starten und sich das Ziel auf 420 befindet, beträgt die Entfernung 400 Pixel. Da wir in jedem Schritt die gleiche Entfernung zurücklegen wollen und die Strecke in zehn Schritten bewältigt werden soll, muss der Kreis um 400/10, also 40 Pixel pro onEnterFrame nach rechts bewegt werden. Daher addieren wir jeweils den errechneten Wert zur aktuellen x-Position hinzu. Gleichzeitig wird nSchritt inkrementiert, um die Anzahl der ausgeführten Schritte zu protokollieren. In der if-Bedingung kontrollieren wir, ob mindestens so viele Schritte ausgeführt wurden, wie wir als maximale Anzahl im Variablen-Block definiert haben. Trifft das zu, setzen wir das animierte Objekt exakt auf die Zielposition und löschen das onEnterFrame. Schließlich erfolgt der Aufruf der init()-Funktion, der wir als Argumente die Instanz kreis, dessen halbe Breite als horizontale und die Bühnenmitte als vertikale Startposition sowie 420 als horizontale Zielposition übergeben. Innerhalb des Anweisungsblocks der if-Bedingung haben wir den Kreis auf die korrekte Position gesetzt, was eigentlich nicht notwendig wäre, da wir ja die richtige Schrittweite errechnet haben. Es hat jedoch den Vorteil, dass durch die Berechnung ermittelte Fliesskommawerte nicht zu Ungenauigkeiten bei der Positionierung am Ende führen.
8. Fügen Sie dort folgende Zeile ein: trace(„Erreichte Endposition: „ + this._x);
9. Rufen Sie die init()-Funktion mit einer veränderten Zielposition auf (Fettdruck): init(kreis, kreis._width/2, Stage. height/2, 427);
Ausgabe im Nachrichtenfenster: Erreichte Endposition: 426.85 Der am Ende erreichte Wert weicht geringfügig von dem als Ziel definierten Wert ab. In visueller Hinsicht dürfte das kaum Probleme bereiten, sehr wohl aber, wenn weitere Berechnungen von dieser Position abhängen. Daher schalten wir in Schritt 5 eine potentielle Fehlerquelle aus, indem wir abschließend die gewünschte Position setzen. 10. Kehren Sie zur korrekten Version in Schritt 5 zurück. Die Verwendung einer Zählvariablen ermöglicht es, diese Animation leicht mit einer anderen, die in einer geringeren Frequenz erfolgen soll, zu kombinieren. Nehmen wir an, wir möchten in jedem zweiten Schritt unseren fleißigen Kreis zusätzlich vertikal bewegen. 11. Erweitern Sie den Variablen-Block um folgende Initialisierung: var nTempoY:Number = 25; var nDivisor:Number = 2;
Für die vertikale Bewegung benötigen wir ein festes Tempo, das wir mit 25 auf einen etwas höheren Wert festlegen, so dass wir später den Effekt deutlicher erkennen können. Mit Hilfe des Divisors ermitteln wir, ob eine vertikale Bewegung erfolgen soll.
24.7 Feste Schrittanzahl
12. Fügen Sie unmittelbar nach Beginn des onEnterFrame-Ereignisses folgende if-Bedingung ein: if (nSchritt % nDivisor == 0) { this._y += nTempoY; }
Beim Testen bewegt sich der Kreis schräg nach rechts unten, während zuvor eine rein horizontale Bewegung erfolgte. In jedem onEnterFrame kontrollieren wir, ob nSchritt dividiert durch nDivisor einen Restwert von 0 liefert. Da nDivisor 2 beträgt, ist das in jedem zweiten Durchlauf der Fall, nämlich immer dann, wenn eine gerade Anzahl vorliegt: 0, 2, 4 usw. Die Verwendung des Modulo-Operators mit dem Operanden 0 ergibt in jedem Fall 0, weswegen auch im allerersten Schritt, in dem nSchritt 0 beträgt, die vertikale Bewegung ausgeführt wird. Möchte man das unterbinden, müsste in die if-Bedingung noch eine Abfrage eingefügt werden, ob nSchritt größer 0 ist. Die Schrittanzahl muss nicht zwangsläufig durch eine Variable vorgegeben werden, sondern kann sich aus einer spezifischen Eigenschaft eines Objekts ergeben. So legt beispielsweise die Länge des einzublendenden Strings beim bekannten Schreibmaschinen-Effekt die Anzahl der Schritte fest. Oder wir definieren in einem Array Objektpositionen, die sukzessive eingenommen werden sollen. Die Länge des Arrays gibt dann an, in wie vielen Schritten die Animation erfolgt. Wir wollen uns hier das Prinzip in aller Kürze anschauen. 1. Erstellen Sie eine Standarddatei. 2. Erstellen Sie auf objects an beliebiger Stelle einen MovieClip mit einem Kreis (40 × 40, Farbe beliebig, Bibliotheksname mcKreis, Instanzname kreis, Registrierungspunkt mittig). 3. Setzen Sie die Bildwiederholrate auf den Wert 8. 4. Weisen Sie actions folgendes Bildskript zu:
335
Wie zuvor benötigen wir einen Startwert für unsere Zählvariable, der wiederum 0 beträgt. In dem mehrdimensionalen Array aPositionen legen wir jeweils einen x- sowie einen y-Wert für die einzunehmende Position fest. Da wir mit jedem Einzelschritt auf eine Index-Position innerhalb des Arrays zugreifen und der höchste zulässige Index immer der Gesamtlänge des Arrays minus 1 entspricht, speichern wir diesen Wert in nMaxSchritt. Wollten wir auf diese Variable verzichten, müssten wir in der späteren Berechnung jeweils direkt auf die Eigenschaft aPositionen.length zugreifen. 5. Erweitern Sie das Skript um eine gegenüber vorher etwas vereinfachte init()-Funktion: //------------ functions –---------- function init(pWer: Movie Clip, pStartX: Number, pStartY: Number):Void { pWer._x = pStartX; pWer._y = pStartY; bewegung(pWer); }
Auch hier müssen wir eine Startposition setzen sowie Flash mitteilen, welches Objekt wir überhaupt bewegen wollen. Aber die Angabe einer Zielposition entfällt, da sie sich ja aus dem angelegten Array ergibt. Wer will, kann auch für die Startposition das Array verwenden, indem man einfach den ersten dort enthaltenen Wert zuweist. 6. Erweitern Sie das Skript: function bewegung(pWer: MovieClip):Void {
pWer.onEnterFrame = function() {
this._x = aPositionen[nIndex][0]; this._y = aPositionen[nIndex][1]; nIndex++;
//------------ vars –----------------
if (nIndex>nMaxSchritt) {
var aPositionen:Array = [[40, 75], [124, 180], [207, 207], [300, 207], [300, 207], [255, 280], [230, 344], [200, 500]];
}
var nIndex:Number = 0;
var nMaxSchritt:Number = aPositionen. length-1;
delete this.onEnterFrame; }; }
//----------–– aufruf –------------- init(kreis, kreis._width/2, Stage. height/2);
336
Der Kreis wird beim Ausführen der Datei eine mehr oder minder merkwürdige Bewegung über den Screen vollführen, die eher der Tanz-Performance eines modernen Künstlers entsprechen mag als dem, was man von einem anständigen Kreis erwarten würde. Aber über Ästhetik lässt sich bekanntermaßen streiten und so wollen wir dem Kreis seine Animation gönnen. In jedem onEnterFrame-Ereignis weisen wir dem Kreis denjenigen x- und y-Wert zu, der sich über die als Index verwendete Variable nIndex ermitteln lässt. Beträgt die Variable 0 wie bei der Initialisierung, greifen wir im Array auf das erste Element zu, also das Wertepaar [40, 75]. Dessen erster Wert entspricht der x-, der zweite Wert der y-Position. Der Index dieser Werte beträgt in jedem Fall 0 bzw. 1. Danach erhöhen wir nIndex und fragen ab, ob der neue Wert den Grenzwert, der sich aus der Gesamtlänge des Arrays ergeben hat, überschreitet. Falls nein, erfolgt im nächsten Durchlauf der gleiche Prozess, nur dass nun das nächste Element ausgewählt wird. Andernfalls wissen wir, das die letzte Position innerhalb des Arrays zugewiesen wurde und damit die Animation an ihrem Ende angelangt ist. Dann löschen wir einfach das nicht mehr benötigte onEnterFrame-Ereignis. Die Verwendung von onEnterFrame in dieser Variante erweist sich insofern als nachteilig, als sie nur über die Bildwiederholrate eine Steuerung der Ablaufgeschwindigkeit zulässt. Zwar stellt das in unserem konkreten Beispiel kein Problem dar, doch kann es durchaus sein, dass eine höhere Bildwiederholrate zwingend erforderlich ist. Daher lässt sich in diesem Fall ein setInterval() sinnvoll einsetzen. 7. Ändern Sie das Skript wie folgt (Fettdruck): //----------–– vars –--------------- var nIndex:Number = 0;
var aPositionen:Array = [[40, 75], [124, 180], [207, 207], [300, 207], [300, 207], [255, 280], [230, 344], [200, 500]];
var nMaxSchritt:Number = aPositionen. length-1; var nIntBewegung:Number;
/ /------------ functions –----------- function init(pWer: MovieClip, pStartX: Number, pStartY: Number, pZeit: Number):Void { pWer._x = pStartX;
Kapitel 24 Geskriptete Animationen
pWer._y = pStartY;
nIntBewegung = setInterval(bewegung, pZeit, pWer); }
function bewegung(pWer: MovieClip):Void {
pWer._x = aPositionen[nIndex][0]; pWer._y = aPositionen[nIndex][1]; nIndex++;
if (nIndex>nMaxSchritt) {
clearInterval(nIntBewegung); }
updateAfterEvent(); }
//------------ aufruf –------------- init(kreis, kreis._width/2, Stage. height/2, 200);
Zwar ändert sich die Bewegung prinzipiell nicht, doch lässt sich nun die Ausführungsgeschwindigkeit gezielt steuern. Sie können das gerne selbst testen, indem Sie beim Aufruf von init() an letzter Stelle einen beliebigen anderen Wert übergeben. Wir richten zusätzlich zu den vorhandenen Variablen eine neue ein, in der später die ID-Nummer des für die Animation benötigten setInterval() gespeichert wird. Die Funktion init() erweitern wir um einen Parameter, der eine Angabe über den zeitlichen Abstand zwischen den Aufrufen der Bewegungsfunktion enthält. Außerdem speichern wir in der bereits eingerichteten Variablen nIntBewegung ein Intervall, das die Funktion namens bewegung() alle 200 Millisekunden ausführt und die zu animierende Instanz übergibt. In der Deklaration von bewegung() entfällt das onEnterFrame-Ereignis, da sie durch das Intervall bereits permanent aufgerufen wird. Statt dessen setzen wir direkt die als Argument erhaltene Instanz auf den genauso wie in der vorhergehenden Variante ermittelten Wert. Auch die Kontrolle des Grenzwerts erfolgt gleich. Allerdings löschen wir am Ende der Animation kein onEnterFrame mehr, sondern das Intervall. Um unabhängig von der konkreten Bildwiederholrate das Bild anzeigen zu können, erzwingen wir mit updateAfterEvent() ein Rendern des Screens. Den Aufruf der init()-Funktion erweitern wir um eine Zeitangabe, in unserem Beispiel die erwähnten 200 Millisekunden.
24.8 Fehlerquelle Schleife
24.8 Fehlerquelle Schleife Ein häufiger Fehler, dem Anfänger gerne erliegen, besteht in der Verwendung einer Schleife. Das zu animierende Objekt wird dann nicht über ein onEnterFrame oder ein setInterval() verändert, sondern mit Hilfe einer for- oder while-Schleife. Der entscheidende Denkfehler liegt in der Ausführungsgeschwindigkeit: Die Schleife wird vollständig in einer möglichst kurzen Zeit abgearbeitet und tritt einmalig auf, während ein permanentes Ereignis wie das onEnterFrame in zeitlich konstantem Abstand wiederholt auftritt. 1. Erstellen Sie eine Standarddatei. 2. Erstellen Sie auf objects an beliebiger Stelle einen MovieClip mit einem Kreis (40 × 40, Farbe beliebig, Bibliotheksname mcKreis, Instanzname kreis, Registrierungspunkt mittig). 3. Weisen Sie actions folgendes Bildskript zu: //------------- vars –------------- var nMaxSchritt:Number = 10; var nTempoX:Number;
//----------- functions –----------- function init(pWer: MovieClip, pStartX: Number, pStartY: Number, pWohin: Number):Void { pWer._x = pStartX; pWer._y = pStartY;
bewegung(pWer, pWohin); }
function bewegung(pWer: MovieClip, pWohin: Number):Void {
nTempoX = (pWohin-pWer._x)/nMaxSchritt; for (var i:Number = 1; i=linie1._x – this._ width/2) {
Das Ergebnis sieht durchaus besser aus als zuvor, kann aber nicht ganz befriedigen, da der Ball immer noch ein Stück in die Wand hinein ragt. Weil sich der Registrierungspunkt exakt in der Mitte befindet, dieser aber permanent mit der Position der Grenzlinie abgeglichen wird, müssen wir vom Ziel die halbe Breite des Kreises abziehen. Würde sich der Registrierungspunkt am linken Rand befinden, müssten wir sogar die gesamte Breite subtrahieren. Lediglich ein Registrierungspunkt am rechten Rand erfordert in diesem Fall keine weitere Korrektur, hätte aber den Nachteil, bei einer Umkehrung der Bewegungsrichtung am linken Rand Probleme zu verursachen. Wenn der Kreis nun allen Bemühungen zum Trotz euphorisch übers Ziel hinaus schießt, so liegt das schlicht an dem boshafterweise gewählten Startwert und dem Tempo. Die Entfernung zum Ziel beträgt vor dem ersten Schritt 190 Pixel (600 – 410). Da wir in jedem onEnterFrame jeweils 20 zur aktuellen x-Position addieren, ergeben sich Werte wie 430, 450 etc. Im letzten Schritt vor Erreichen der Grenze befindet sich kreis auf der horizontalen Position 590. Da die if-Bedingung noch nicht zutrifft, werden erneut 20 Pixel addiert – damit jedoch lautet seine neue Position 610, was deutlich über der Grenzbedingung von 600 liegt.
if (this._x > linie1._x – this._ width/2) { this._x = linie1._x – this._ width/2; delete this.onEnterFrame; }
Der Kreis stoppt nun exakt an der Zielposition. Glücklicherweise existieren zwei einfache Varianten zur Korrektur unseres vorherigen Fehlers: Wir setzen entweder im letzten Schritt vor oder im ersten Schritt nach Erreichen des Zielpunkts unser Objekt auf das gewünschte Ziel. In beiden Fällen erhalten wir eine saubere Bewegung, ohne das wir das Ziel sichtbar überschreiten. Das gilt selbst für den Fall, dass wir rein rechnerisch über das Ziel hinausschießen. Da der Screen erst gezeichnet wird, nachdem alle relevanten Skripte abgearbeitet wurden, erfolgt unsere Positionskorrektur vorher und Positionsfehler bleiben unsichtbar. Im konkreten Beispiel kontrollieren wir innerhalb der if-Bedingung nur noch, ob die errechnete Position größer ist als die Zielposition. Falls ja, setzen wir den Kreis direkt auf das Ziel und beenden die Animation.
24.10 Grenzbedingung und Verwendung von Animationsobjekten Wir haben in den bisherigen Beispielen jeweils implizit eine Vorentscheidung bezüglich der weiteren Existenz von Objekten für den Fall getroffen, dass sie die
340
Grenzbedingung der Animation erreichen: Sie führen die Animation fort, nur unter geänderten Bedingungen (z. B. Vorzeichenumkehr der für die Animation verantwortlichen Werte). Welche Möglichkeiten bestehen jedoch generell? Was kann mit Objekten geschehen, die einen solchen Grenzwert erreichen? Diese Frage stellt sich für alle Animationen, bei denen die visuelle Wahrnehmung betroffen ist (insbesondere: Positionsänderung, Deckkraft, Größe, in bestimmten Situationen auch Farbe, nicht jedoch Rotation). Wir wollen uns hier auf die Position beschränken, da sie die im Rahmen einer Animation am häufigsten geänderte Eigenschaft darstellt. Unabhängig von etwaigen philosophischen Erwägungen drohen einem animierten Objekt folgende Schicksale bei Erreichen eines Grenzwerts:
• Das Objekt wird brutal gelöscht. Dies beendet ent-
weder die vollständige Animation oder leitet zu einer neuen Phase über, aber für das betroffene Objekt hat sich das Karma unabänderlich erfüllt! • Das Objekt kehrt seine Bewegungsrichtung um (bouncing off); • Das Objekt setzt seinen Weg am entgegengesetzten Ende des Bildschirms unbeirrt fort (screen wrap); • Das Objekt taucht an einer zufällig ermittelten oder fest vorgegebenen Position innerhalb des Screens wieder auf und setzt i. d. R. die Animation fort. Welche Variante man wählt, hängt notwendigerweise mit dem Zweck zusammen, dem unsere Animation dient. Wir schauen uns kurz konkrete Beispiele an und werden in den weiteren Kapiteln insbesondere zur Trigonometrie darauf zurück kommen.
24.10.1 Löschen des Objekts 1. Erstellen Sie eine Standarddatei. 2. Erstellen Sie auf objects an beliebiger Stelle einen MovieClip mit einem Kreis (40 × 40, Farbe beliebig, Bibliotheksname mcKreis, gleichnamige Verknüpfung, Instanzname kreis, Registrierungspunkt mittig). 3. Löschen Sie den Kreis von der Bühne, da wir ihn später per Skripting einfügen wollen. 4. Fügen Sie eine neue Ebene namens rand ein. 5. Verschieben Sie diese Ebene im Ebenenstapel der Zeitleiste ganz nach unten.
Kapitel 24 Geskriptete Animationen
6. Erstellen Sie dort ein nicht gefülltes Rechteck (Linienfarbe schwarz, Linienstärke 2) in Größe der Bühne, um den Rand des sichtbaren Bereichs zu markieren. 7. Weisen Sie actions folgendes Bildskript zu: //--------------- vars –------------– var nTempoX:Number, nTempoY:Number; var nPosX:Number = Stage.width/2;
var nPosY:Number = Stage.height/2; var mKreis:MovieClip;
In gewohnter Weise richten wir unsere Tempo-Variablen für eine horizontale und vertikale Bewegung ein; die konkreten Werte ermitteln wir später per ZufallsFunktion. Für eine Positionierung legen wir zwei Variablen an. Zum Schluss richten wir eine Variable ein, die unseren Kreis aufnehmen soll. 8. Erweitern Sie das Skript um eine init()-Funktion: //------------- functions –--------- function init():Void {
mKreis = this. attachMovie(„mcKreis“, „kreis“, 1); mKreis._x = nPosX; mKreis._y = nPosY;
nTempoX = zufallOhne(12, -3, 3); nTempoY = zufallOhne(12, -3, 3); bewegung(mKreis); }
Diese Funktion sorgt für das Einblenden des Kreises, indem wir ihn per attachMovie() auf die Bühne bringen, positioniert ihn in Bühnenmitte, weist mit Hilfe einer noch zu deklarierenden Zufallsfunktion jeweils ein beliebiges Tempo zwischen –12 und –4 sowie 4 und 12 zu und ruft eine ebenfalls noch zu deklarierende Bewegungsfunktion auf. Da das Zufallstempo einen negativen oder einen positiven Wert enthalten kann, wird sich unser Kreis später in einer nicht vorhersagbaren Richtung über die Bühne bewegen. 9. Fügen Sie die Deklaration der Zufallsfunktion ein: function zufallOhne(pMax:Number, pOhneMin:Number, pOhneMax:Number):Number {
var nZufall:Number = Math.round(Math. random()*(pMax*2))-pMax;
24.10 Grenzbedingung und Verwendung von Animationsobjekten
while (nZufall>=pOhneMin && nZufallStage.width-this._ width/2 || this._xStage.height-this._ height/2 || this._yStage.width+this._ width/2 || this._xStage.height+this._height/2 || this._y) und kleiner (=) und ein kleiner gleich (=pOhneMin && nZufallStage.width-this._ width/2) {
this._x = Stage.width-this._ width/2; nTempoX *= -1;
} else if (this._xStage.height-this._ height/2) {
this._y = Stage.height-this._ height/2;
24.10 Grenzbedingung und Verwendung von Animationsobjekten
nTempoY *= -1;
} else if (this._yStage.width+this._ width/2) { this._x = -this._width/2;
} else if (this._xStage.height+this._ height/2) { this._y = -this._height/2;
} else if (this._yStage.width + this._ width/2 || this._x < -this._width/2 || this._y > Stage.height + this._ height/2 || this._y < -this._ height/2) { starten(nPosX,nPosY); }
Wie zuvor bewegt sich der Kreis mit einem Zufallstempo auf die Bühnenränder zu. Sobald er horizontal und/oder vertikal einen Rand überschritten hat, startet er in Bühnenmitte mit veränderten Geschwindigkeiten wieder von Neuem. Da wir das Starten wiederholen müssen, lagern wir die Definition der entsprechenden Bedingungen in eine eigene Funktion namens starten() aus, die in init() einmal aufgerufen wird. Dabei übergeben wir als Argument jeweils die gewünschte x- und yPosition des Kreises. Die zuvor in init() vorgenommene Ermittlung einer Zufallsgeschwindigkeit erfolgt nun ebenfalls in der Funktion starten(), die wir nach dem Beginn der Animation jedes Mal dann erneut aufrufen, wenn der Kreis einen Bühnenrand überschreitet. Diese Form der Animation haben Sie bereits oben im Abschnitt „Animation mit onEnterFrame und fester Schrittweite“ kennen gelernt.
25
Trigonometrie
Der eine oder andere mag an dieser Stelle zusammen zucken, gelten doch Mathematik und Physik als recht schwierige Angelegenheiten, denen die meisten schon als Schüler soweit irgend möglich aus dem Weg gegangen sind. In der Programmierung kommt man jedoch nicht ganz ohne derartiges Fachwissen aus. Denn erst deren Kenntnis ermöglicht ebenso interessante wie ausgefallene Effekte und Animationen. Dazu zählt beispielsweise das momentan ausgesprochen beliebte Karusselmenü und kreis- bzw. spiralförmige Animationen ebenso wie die physikalisch überzeugende Bewegung eines Fahrzeugs mit Beschleunigung und Abbremsen oder die in einem Spiel eingesetzte Kanonenkugel, deren Flugbahn bestimmten Gesetzmäßigkeiten unterliegt. Solange wir nicht gezwungen sind, eine physikalisch absolut korrekte Simulation zu erstellen, können wir uns mit realistisch anmutenden Näherungen begnügen, die auf insgesamt recht einfachen Formeln beruhen. Selbst von Schulphysik und -mathematik Geschädigte werden insofern leichten Zugang zur Materie finden.
25.1 Einige Grundlagen der planen Trigonometrie Die wichtigsten für uns relevanten Formeln stellt die plane oder ebene Trigonometrie zur Verfügung, soweit sie sich mit Dreiecken und dem Verhältnis von Seitenlängen und Winkelgrößen befasst. Am einfachsten gestaltet sich die Berechnung, wenn ein rechtswinkliges Dreieck wie Abbildung 97 verdeutlicht. In einem derartigen Fall beträgt der größte Innenwinkel 90 Grad, während die beiden übrigen Innenwinkel zusammen genommen ebenso 90 Grad ergeben. Die Summe der Innenwinkel entspricht somit immer 180 Grad. Die dem größten Innenwinkel gegenüberliegende Seite bezeichnet man als Hypotenuse, die übrigen als Katheten. Von einem der spitzen Innenwinkel (d. h. < 90 Grad) aus betrachtet, stellt die angrenzende Seite die Ankathete, die gegenüberliegende die Gegenkathete dar. Zwischen diesen Seiten und Winkeln existiert eine ganz spezifische Beziehung, die es uns ermöglicht, die benötigten Werte zu errechnen. Wollen wir beispielsweise die Länge der Hypotenuse herausfinden, hilft der Satz des Pythagoras weiter, der den meisten noch dunkel im Gedächt-
Abbildung 97: Rechtwinkliges Dreieck A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
345
346
Kapitel 25 Trigonometrie
Abbildung 99: Trigonometrische Funktionen und Kreisberechnung Abbildung 98: Abstandsmessung zwischen zwei beliebigen Punkten und der Satz des Pythagoras
nis haften dürfte als a2 + b2 = c2. In beredter Prosa bedeutet dies: Addiert man das Quadrat der beiden Katheten, erhält man das Quadrat der Hypotenuse. Berechnet man die Wurzel der Summe, erhält man die Länge der Hypotenuse. Sie entspricht dem Abstand zwischen zwei beliebigen Punkten. Die trigonometrischen Funktionen Sinus, Cosinus und Tangens erfassen ebenfalls wichtige Verhältnisse zwischen Seitenlängen und Winkelgrößen. Sie ermöglichen es u. a., die exakte Position beliebiger Punkte auf einem Kreis zu bestimmen. Dabei gilt: Sinus Winkel = Gegenkathete/Hypotenuse Cosinus Winkel = Ankathete/Hypotenuse Tangens Winkel = Gegenkathete/Ankathete Sind in einer der Gleichungen zwei Werte bekannt, lässt sich der dritte einfach ermitteln. Jeder Punkt auf einem Kreis lässt sich beschreiben, indem wir ihn als Eckpunkt eines rechtwinkligen Dreiecks betrachten, wobei folgende Entsprechungen gelten: Ankathete = horizontaler Abstand a Gegenkathete = vertikaler Abstand b Hypotenuse = Kreisradius c Winkel = Drehwinkel d
Um also beispielsweise die Position des Punktes e in Abbildung 99 feststellen zu können, müssten bei gegebenem Drehwinkel und Kreisradius diese Berechnungen durchgeführt werden: Gegenkathete = Sinus Winkel * Hypotenuse Ankathete = Cosinus Winkel * Hypotenuse Beziehungsweise: Vertikaler Abstand = Sinus Drehwinkel * Kreisradius Horizontaler Abstand = Cosinus Drehwinkel * Kreisradius Wir erhalten diese Berechnung durch Umstellung der ursprünglichen Formeln, indem wir beide Seiten der Gleichungen jeweils mit der Hypotenuse multiplizieren. Da die Position eines Punktes in einem zweidimensionalen Raum durch zwei Werte bestimmt wird, müssen wir auch zwei Berechnungen, nämlich eine für die horizontale und eine für die vertikale Position durchführen. Entscheidend für eine korrekte Darstellung der Position ist neben obiger Berechnung das Koordinatensystem, auf das man sich bezieht. Da gibt es nämlich Unterschiede zwischen Flash und dem System, in dem die trigonometrischen Funktionen gelten. Sie
25.2 Abstände zwischen Objekten
347 Abbildung 100: Der Einheitskreis
lassen sich am besten anhand des Einheitskreises verdeutlichen, den Abbildung 100 zeigt. Der Einheitskreis ist ein idealtypischer Kreis mit dem Radius 1, wobei die konkrete Maßeinheit unerheblich ist. Alle Eigenschaften des Einheitskreises gelten unabhängig davon, ob der Radius nun 1 Kilometer, 75 Zentimeter oder 12 Mikrometer beträgt. Die Drehung des Kreises wird ausgehend von der x-Achse entgegen dem Uhrzeigersinn in Grad angegeben, wobei eine Volldrehung 360 Grad umfasst. Die Nullstellung entspricht einer Ausrichtung nach rechts. In Flash dagegen erfolgt eine Drehung im Uhrzeigersinn und die Nullstellung entspricht derjenigen Position, die das betreffende Objekt beim erstmaligen Einfügen auf der Bühne einnimmt. Zur Winkelberechnung verwendet Flash das Bogenmaß mit der Hilfsmaßeinheit Radiant; lediglich die Eigenschaft _rotation arbeitet wie gewohnt mit Grad. Die Umrechnung zwischen beiden Maßeinheiten erfolgt einfach mit:
25.2 Abstände zwischen Objekten Wie wir gesehen haben, kann man mit Hilfe des Satzes des Pythagoras die Entfernung zwischen zwei beliebigen Punkten ermitteln. Im folgenden Beispiel wollen wir zwei Objekte zufallsbedingt auf der Bühne einfügen. Das erste Objekt kann gezogen werden und gibt während der Bewegung die Entfernung zum zweiten Objekt aus. 1. Erstellen Sie eine Standarddatei. 2. Erzeugen Sie einen leeren MovieClip (Bibliotheksund Verknüpfungsname mcKugel). 3. Zeichnen Sie einen Kreis mit radialem Farbverlauf (beliebige Farben, Größe 30 × 30). 4. Positionieren Sie ihn auf –15,–15, so dass sich der Registrierungspunkt exakt in der Mitte der Grafik befindet.
Grad = Radiant * (180/PI) Radiant = Grad * (PI/180) Bei den bisherigen trigonometrischen Funktionen haben wir den Winkel als bekannt angenommen und nach einer Seitenlänge gesucht. Trifft jedoch der umgekehrte Fall zu und wir wollen bei gegebener Seitenlänge einen Winkel ermitteln, können wir die inversen trigonometrischen Funktionen verwenden. Das alles klingt noch recht abstrakt, weswegen wir uns in den folgenden Abschnitten einige konkrete Umsetzungen in Flash anschauen wollen.
Abbildung 101: Abstandsmessung zwischen MovieClips
348
5. Kehren Sie zur Hauptzeitleiste zurück. 6. Fügen Sie in actions folgendes Bildskript ein: //------------- vars –-------------- var bBewegung:Boolean = false;
//----------- functions –----------- function init() {
this.attachMovie(„mcKugel“, „kugel1“, this.getNextHighestDepth()); this.attachMovie(„mcKugel“, „kugel2“, this.getNextHighestDepth()); kugel1._x = kugel2._x=Stage. width/2; kugel1._y = kugel2._y=Stage. height/2;
kugel2.onPress = function() { this.startDrag(); bBewegung = true; };
kugel2.onRelease = kugel2. onReleaseOutside=function () { this.stopDrag();
bBewegung = false; };
kugel2.onMouseMove = function() { if (bBewegung) {
var nEntfX:Number = kugel1._xthis._x; var nEntfY:Number = kugel1._ythis._y; var nEntfernung:Number = Math. sqrt((nEntfX*nEntfX)+(nEntfY*n EntfY));
trace(Math.round(nEntfernung)); } }; }
Kapitel 25 Trigonometrie
initialisieren wir die Variable mit dem Wert false. Die init()-Funktion fügt zwei Kugeln ein und weist der zweiten ein Zieh-Verhalten zu. Die Kugeln werden aus der Bibliothek der Einfachheit halber exakt in Bühnenmitte eingefügt. Alternativ können Sie auch mit Zufallswerten arbeiten. Beide Kugeln liegen exakt übereinander. Die zweite Kugel erhält ein onPress-Ereignis, das die startDrag()-Methode aufruft, also ein Ziehen der Kugel erlaubt, und das die erwähnte Variable auf true setzt. Im korrespondierenden onRelease- bzw. onReleaseOutside-Ereignis beenden wir das Ziehen und weisen der Variablen wieder den Ausgangswert false zu. Die Entfernungsmessung findet im onMouseMoveEreignis statt, vorausgesetzt, die Variable bBewegung beinhaltet den Wert true. Denn nur dann liegt ja eine Bewegung vor, die zu einer Änderung der Entfernung führt. Ist sie dagegen false, hat der Anwender nicht auf die Kugel gedrückt. In dem Fall eine Berechnung durchzuführen, wäre unsinnig. Um den bestehenden Abstand korrekt zu ermitteln, müssen wir in Gedanken die Entfernung als Hypotenuse in einem rechtwinkligen Dreieck betrachten. Dann entsprechen die beiden Katheten der horizontalen und vertikalen Entfernung, die wir in den beiden lokalen Variablen nEntfX und nEntfY erfassen. Dabei spielt es keine Rolle, ob sie einen positiven oder negativen Wert enthalten, denn die nachfolgende Berechnung resultiert infolge der Multiplikation in jedem Fall in einer positiven Zahl, aus der wir gemäß der Formel des Pythagoras nur die Wurzel ziehen müssen, um die gewünschte Entfernung zu erhalten. Mit dem seit Flash zur Verfügung stehenden geomPackage gestaltet sich die Berechnung sogar noch einfacher, denn dort ist bereits eine entsprechende Methode vorhanden. 7. Fügen Sie am Beginn des Scripts ein:
//-------------- start –-------------
//---------–- import –---------------
init();
import flash.geom.*;
Wenn Sie mit gedrückter Maustaste eine Kugel ziehen, sehen Sie im Nachrichtenfenster den aktuellen Abstand zu der in der Bühnenmitte verbleibenden Kugel. Wir richten zunächst eine Variable ein, in der wir mit Hilfe einer Boolean speichern wollen, ob eine Kugel gezogen wird. Da das anfangs nicht der Fall ist,
8. Ersetzen Sie im onMouseMove-Ereignis in der Bedingung die ersten drei Zeilen durch: var nPunkt1:Point = new Point(kugel1._x, kugel1._y); var nPunkt2:Point = new Point(this._x, this._y);
25.3 Kreise und Ellipsen
349
var nEntfernung:Number = Point. distance(nPunkt1, nPunkt2);
mKreis.lineTo(xPos, yPos);
Zwar ergibt sich beim Testen optisch kein Unterschied, aber anstelle der vorherigen Formel verwenden wir eine vorgegebene Methode, die nach dem gleichen Prinzip eine Entfernung bestimmt. Wir benötigen zunächst Zugriff auf die Point-Klasse, die über das geom-Package importiert wird. Innerhalb des onMouseMove-Ereignisses übergeben wir mit nPunkt1 und nPunkt2 Anfangs- und Endpunkt der gedachten Hypotenuse an die distance()-Methode und erhalten wie zuvor als Ergebnis die Entfernung.
}
25.3 Kreise und Ellipsen Zur Kreisberechnung stehen uns die Cosinus- und Sinus-Funktionen zur Verfügung, auf die wir mit Hilfe der Math-Klasse zugreifen können. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: //-------------- vars –------------- var mKreis:MovieClip; var nWinkel = 0;
var nRadius:Number = 100;
var nSchritte:Number = 20;
var nWinkelDist:Number = 360/ nSchritte;
//------------ functions –---------- function init():Void {
var xPos:Number, yPos:Number;
mKreis = this.createEmptyMovieClip („behKreis“, this.getNextHighest Depth()); mKreis.lineStyle(1, 0 × 000000, 100);
mKreis.moveTo(Math. cos(nWinkel*(Math.PI/180))*nRadius, Math.sin(nWinkel*(Math. PI/180))*nRadius); for (var i = 0; i=360) {
trace(„habefertisch“);
delete this.onEnterFrame; } };
Beginnend in der Nullposition baut sich im Uhrzeigersinn ein Kreis auf. Mit minimalen Änderungen realisieren wir hier einen animierten Kreis. In jedem onEnterFrameEreignis wird eine Linie von einem Kreispunkt zum nächsten gezeichnet. Berechnen und Zeichnen entsprechen dabei dem Code, der zuvor verwendet wurde. Nur die Ende-Bedingung unterscheidet sich: Wir greifen nicht mehr auf die Variable nSchritte zurück, sondern kontrollieren statt dessen jeweils den aktuellen Winkel. Entspricht er einer Drehung von 360 Grad, wissen wir, dass der Kreis vollständig gezeichnet wurde und das onEnterFrame-Ereignis kann gelöscht werden. Um eine Ellipse zu erhalten, reicht es aus, im bestehenden Code mit zwei verschiedenen Radii zu ar-
25.4 Spiralen
beiten, denn die Ellipse ist nichts weiter als ein gestauchter Kreis. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu (Änderungen gegenüber der nicht animierten Kreisversion in Fettdruck): //-------------- vars –------------- var mKreis:MovieClip;
351
wir nun im Variablen-Block jeweils eine Variable für den horizontalen und eine für den vertikalen Abstand fest. Sie wird jeweils bei der Berechnung von Sinus und Cosinus verwendet. Die größte Ausdehnung in horizontaler Richtung entspricht nRadiusX und wird erreicht, wenn die Berechnung von Sinus 0 ergibt und die größte Ausdehnung in vertikaler Richtung ist mit nRadiusY bei einem Cosinus-Wert von 0 identisch.
var nWinkel = 0;
var nRadiusX:Number = 100;
25.4 Spiralen
var nSchritte:Number = 120;
mKreis._x = Stage.width/2;
Aus diesen Beispielen lässt sich recht leicht eine Spirale zeichnen bzw. ein Objekt spiralförmig animieren. Wir wollen uns dabei mit einer simplen Variante begnügen, die Strichlänge und Tempo nicht unabhängig voneinander behandelt. Das reicht aus, um das Prinzip einer derartigen Animation zu verstehen. Im Unterschied zum Kreis erhöht sich bei der Spirale der Radius permanent, so dass die Kreisbahn immer weiter nach außen verzogen wird. Der Radius kann linear oder exponentiell verändert werden, je nachdem, wie stark der gewünschte Effekt auftreten soll.
mKreis.lineStyle(1, 0 × 000000, 100);
1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu:
var nRadiusY:Number = 60;
var nWinkelDist:Number = 360/ nSchritte;
//------------- functions –--------- function init():Void {
var xPos:Number, yPos:Number;
mKreis = this.createEmptyMovieClip („behKreis“, this.getNextHighest Depth()); mKreis._y = Stage.height/2;
mKreis.moveTo(Math. cos(nWinkel*(Math. PI/180))*nRadiusX, Math. sin(nWinkel*(Math. PI/180))*nRadiusY);
//-------------- vars –--------------
nWinkel += nWinkelDist;
Die Variable mSpirale nimmt eine Referenz auf den MovieClip auf, in dem wir zeichnen wollen. In nWinkel legen wir den Startwinkel und in nRadius den Startradius fest. Mit nWinkelTempo definieren wir einen Wert, um den bei jedem Zeichenvorgang der in der Berechnung verwendete Winkel erhöht wird. Da er einem fixen Wert entspricht, wird die Spirale mit wachsendem Radius kantiger. Im vorhergehenden Abschnitt über Kreise und Ellipsen haben wir diesen Wert aus der Gesamtanzahl an Zeichenschritten errechnet.
for (var i = 0; i=nMaxBreite) { trace(„habefertisch!“);
delete this.onEnterFrame; }
Erreicht die Kurve den rechten Bühnenrand, erhalten wir eine entsprechende Meldung und der Zeichenvorgang stoppt. Wir machen das Ende des Zeichnens einfach von der Breite der Bühne abhängig. Den gewünschten Wert speichern wir in einer Variablen und kontrollieren permanent nach dem Zeichnen eines Liniensegments, ob die gesamte Breite des übergeordneten Zeichenbehälters diesen Wert erreicht oder überschritten hat. Ist das der Fall, geben wir eine Meldung aus und löschen das für die Zeichnung verantwortliche Ereignis. Alternativ kann man auch die Anzahl der Amplituden vorgeben, wobei das gleiche Prinzip zum Tragen käme, das wir in der vorhergehenden Übung hinsichtlich der Anzahl der Umdrehungen einer Spirale kennen gelernt haben. Daraus lässt sich einfach die erwähnte optische Täuschung erstellen, die in Abbildung 106 zu sehen ist. In dieser Abbildung scheint es, als würden Linien verschiedener Länge gezeichnet. Tatsächlich sind jedoch alle Linien gleich lang. 5. Erweitern Sie den Variablen-Block um: var nHoehe:Number = 100;
6. Ersetzen Sie im onEnterFrame-Ereignis den Aufruf der lineTo()-Methode durch: this.moveTo(nPosX, nPosY);
this.lineTo(nPosX, nPosY+nHoehe);
Als Ergebnis erhalten Sie die in obiger Abbildung zu sehende Kurve. Dahinter steckt die einfache Überlegung, dass wir an jedem ermittelten Punkt eine vertikal ausgerichtete
Abbildung 106: Sinustäuschung
Gerade von jeweils gleicher Länge zeichnen müssen. Zwischen den Punkten darf es keine Verbindungslinie mehr geben. Dazu initialisieren wir zuerst eine Variable mit der gewünschten Linienhöhe. Im on EnterFrame-Ereignis müssen wir nun nur noch den Zeichenstift jedes Mal auf den neu ermittelten Punkt setzen, ohne vom vorhergehenden Punkt bis dorthin zu zeichnen. Daher ersetzen wir das vorhergehende lineTo() durch ein moveTo(). An den betreffenden Punkten zeichnen wir dann vertikal nach unten mit Hilfe von lineTo() eine 100 Pixel lange Gerade und fertig ist unsere Sinnestäuschung. Je steiler die Kurve und je kürzer die Abstände zwischen den Linien, desto stärker tritt der Effekt hervor.
25.8 Drehung eines Objekts zu einem anderen Objekt An einem einfachen Beispiel wollen wir uns den umgekehrten Effekt, nämlich die Berechnung eines Winkels bei gegebenen Seiten, anschauen. Denn bei der Drehung zu einem Objekt liegen ja gerade die beiden Seiten bzw. Katheten als horizontale und vertikale Entfernung zum Ziel vor, während der benötigte Drehwinkel gesucht wird. In diesem Fall hilft eine umgekehrte trigonometrische Funktion, die Flash mit Math.atan2() zur Verfügung stellt. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie in der Bühnenmitte auf objects ein einfaches Fahrzeug in der Draufsicht (ca. 82 × 45).
25.8 Drehung eines Objekts zu einem anderen Objekt
Abbildung 107: Ausrichtung des Fahrzeugs vor der Drehung zur Maus
Achten Sie darauf, dass Ihr Fahrzeug nach rechts ausgerichtet ist wie in Abbildung 107. 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcAuto, Instanzname auto, Registrierungspunkt mittig). 4. Weisen Sie actions folgendes Bildskript zu: //------------ functions –---------- function init(){
auto.onMouseMove = drehen; auto.onMouseMove(); }
function drehen() {
var nXMouse:Number = _xmouse – this._x; var nYMouse:Number = _ymouse – this._y;
this._rotation = Math. atan2(nYMouse,nXMouse)*(180/Math. PI);
359
thete und die Ankathete erwartet. Wie wir aus obiger Diskussion wissen, handelt es sich dabei um den vertikalen und den horizontalen Abstand zwischen Zielpunkt und aktueller Position. Er wird jeweils für zwei Variablen berechnet und anschließend an die genannte Methode übergeben. Da der Arkustangens mit Radiant arbeitet, müssen wir in Grad umrechnen und weisen das Ergebnis, nämlich den gesuchten Drehwinkel, der Eigenschaft _rotation von auto zu. Beachten Sie die Reihenfolge der Parameter: Entgegen unserer Gewohnheit, zuerst den horizontalen, anschließend den vertikalen Wert zu übergeben, steht bei Math.atan2() der vertikale Abstand an erster Stelle, gefolgt vom horizontalen. Ebenfalls wichtig ist die Reihenfolge der Werte bei der Berechnung des Abstands. Es muss von der Zielposition die aktuelle Position abgezogen werden, also in unserem Beispiel _xmouse – this._x. Kehren wir die Subtraktion um, dreht sich das Fahrzeug immer in die entgegen gesetzte Richtung, was zwar recht lustig ausschaut, aber nicht unbedingt unseren Wünschen entspricht. Last not least spielt die Ausrichtung der Grafik innerhalb von mcAuto eine Rolle. Da die Nulldrehung im Einheitskreis einer Ausrichtung nach rechts entspricht, müssen wir dementsprechend das Fahrzeug zeichnen. Würden wir statt dessen das Fahrzeug vertikal zeichnen, erhielten wir bei der Interaktion eine um 90 Grad versetzte Drehung. Das können Sie testen, indem Sie in mcAuto alles markieren und um 90 Grad drehen, so dass die Fahrzeugfront entweder nach oben oder unten zeigt.
};
//-------------- start –------------ init();
Beim Testen dreht sich das Auto in Richtung ihrer Maus. In der init()-Funktion definieren wir für auto ein onMouseMove-Ereignis, dem die Funktion drehen zugewiesen wird. Gleichzeitig lösen wir einmal dieses Ereignis aus, so dass die besagte Funktion bereits zum Einsatz kommt, bevor der Anwender die Maus bewegt hat. Dadurch erreichen wir, dass sich das Fahrzeug von Anfang an Richtung Mauszeiger dreht. Die Funktion drehen() richtet das Fahrzeug am Mauszeiger aus. Das geschieht mit Hilfe der Math. atan2()-Methode, die als Parameter die Gegenka-
Abbildung 108: Veränderung des Schlagschattens per Maus
360
25.9 Interaktiver Schlagschatten Das letzte Beispiel kombiniert Abstandsmessung und Berechnung eines Winkels mit einem Filter-Effekt: Mit gedrückter Maustaste bestimmt der Anwender Größe und Ausrichtung eines Schlagschattens. In Abbildung 108 ist eine Phase dieses Effekts zu sehen. 1. Erstellen Sie eine Standarddatei. 2. Importieren Sie in die Bibliothek eine beliebige Grafik. Wir wählen hier beispielhaft erdhorn1.jpg. 3. Erstellen Sie einen leeren MovieClip (Bibliotheksname und Verknüpfung mcBild). 4. Fügen Sie dort die Grafik an der Position 0,0 ein, so dass sich der Registrierungspunkt in der linken oberen Ecke befindet. 5. Kehren Sie zur Hauptzeitleiste zurück. 6. Weisen Sie actions folgendes Bildskript zu: //------------- import –------------ import flash.filters.*;
//---------––––– vars –------------- var fSchatten:DropShadowFilter;
//------------ functions –-----------
Kapitel 25 Trigonometrie
In der Screenmitte taucht das von uns gewählte Bild auf, versehen mit einem leichten, nach rechts unten weisenden Schlagschatten. Wir importieren den benötigten Filter als Teil des filters-Packages und erzeugen eine Variable zur Referenzierung eines DropShadow-Filters. In der init()Funktion legen wir die Eigenschaften fest, die unser instanziiertes Filter-Objekt besitzen soll:
• Entfernung von 5 Pixel; • Winkel 0 Grad; • Farbe dunkles Grau; • Deckkraft 100 % (zur Erinnerung: Bei den Filtern
erstreckt sich der zulässige Wertebereich von 0.0 bis 1.0); • Horizontale und vertikale Weichzeichnung von 5 Pixel; • Stärke 2 (Wertebereich 0 bis 255); • Qualität 3 (Wertebereich 1 bis 3). Zwar verfügt der Schlagschatten-Filter noch über weitere Eigenschaften, die wir aber nicht setzen wollen, so dass sie mit den Standardwerten initialisiert werden. Anschließend fügen wir das Bild aus der Bibliothek ein, positionieren es horizontal und vertikal mittig und weisen ihm den zuvor definierten Filter zu.
function init() {
7. Erweitern Sie den Variablen-Block:
fSchatten.distance = 5;
var nDifX:Number, nDifY:Number, nDif:Number;
fSchatten.color = 0 × 333333;
var aKlick:Array = [];
fSchatten = new DropShadowFilter(); fSchatten.angle = 45; fSchatten.alpha = 1; fSchatten.blurX = 5; fSchatten.blurY = 5;
fSchatten.strength = 2; fSchatten.quality = 3;
this.attachMovie(„mcBild“, „bild“, this.getNextHighestDepth()); bild._x = Stage.width/2-bild._ width/2;
bild._y = Stage.height/2-bild._ height/2; bild.filters = [fSchatten]; }
//-----------––– start –------------ init();
var nFaktor:Number = 0.1;
8. Erweitern Sie die Deklaration von init() nach der Zuweisung des Filters: bild.onPress = function() {
aKlick = [_xmouse, _ymouse]; };
bild.onMouseMove = schatten;
9. Erweitern Sie den Funktions-Block: function schatten() {
nDifX = _xmouse-aKlick[0]; nDifY = _ymouse-aKlick[1];
nDif = Math.sqrt((nDifX*nDifX)+(nDi fY*nDifY)); fSchatten.angle = Math.atan2(nDifY, nDifX)*(180/Math.PI);
25.9 Interaktiver Schlagschatten
fSchatten.distance = nDif*nFaktor; bild.filters = [fSchatten]; }
Per Mausklick können Sie den Schatten verschieben. Zur Berechnung des Drehwinkels sowie des Abstands werden drei Variablen eingerichtet. Da wir die Schattengröße aus der Entfernung berechnen, diese aber nicht 1 zu 1 übernehmen können, benötigen wir den Faktor nFaktor, mit dessen Hilfe die Entfernung reduziert werden kann. Als Ausgangspunkt für unsere Berechnung wollen wir die Mausposition beim Klick auf das Bild verwenden. Sie wird in einem Array erfasst (alternativ könnten wir auch je eine Variable für die horizontale und für die vertikale Mausposition nehmen). In der init()-Funktion erhält das Bild ein onPress-Ereignis, das die aktuelle Mausposition speichert, um von dieser Stelle aus den Abstand sowie die Drehung ermitteln zu können. Prinzipiell hätte man auch den Mittelpunkt der Grafik verwenden können, aber die Mausposition ist erheblich intuitiver. Zusätzlich weisen wir ein onMouseMove-Ereignis zu, in dem permanent die Position des Schattens neu berechnet wird. Die eigentliche Berechnung erfolgt in der Funktion schatten(). Wir ermitteln den Abstand zwischen der aktuellen Mausposition und derjenigen Position, die bei Klick gespeichert wurde. Dabei verwenden wir den oben vorgestellten Satz des Pythagoras. Mit einer umgekehrten trigonometrischen Funktion ermitteln wir darüber hinaus den Winkel zwischen der eben berechneten Strecke und der Nullposition. Die Eigenschaft angle unseres Filters erhält diesen Wert, während wir den Abstand auf ein Zehntel reduzieren und ihn der Eigenschaft distance zuweisen. Damit der Filter mit den neuen Werte dargestellt wird, müssen wir in der letzten Zeile erneut darauf zugreifen. So ganz glücklich macht das Skript noch nicht, denn bei der geringsten Mausbewegung – selbst vor einem Mausklick – wird bereits die Funktion schatten() aufgerufen. Zu dem Zeitpunkt kann aber keine korrekte Berechnung stattfinden, da noch keine Mausposition gespeichert wurde. Außerdem hört der Effekt nach dem Loslassen der Maus nicht mehr auf. 10. Erweitern Sie die Deklaration von init() nach der Zuweisung des onPress-Ereignisses:
361
bild.onRelease = bild. onReleaseOutside=function () {
aKlick = [];
};
11. Machen Sie innerhalb der Deklaration von schatten() die Ausführung aller Befehle von folgender Bedingung abhängig (Fettdruck): if (aKlick.length>0) {
//hier stehen alle bisherigen Anweisungen der Funktion schatten() }
Flash ändert den Schatten nur noch, wenn zuvor ein Mausklick erfolgte. Sobald Sie die Maus loslassen, endet auch der Effekt. Wir benötigen ein Kriterium, anhand dessen Flash erkennen kann, ob die Funktion schatten() auszuführen ist. Das fügen wir ein durch den Bezug auf den Inhalt von aKlick: Nur dann, wenn sich darin Werte befinden bzw. die Länge größer 0 ist, wissen wir, dass bereits eine Mausposition gespeichert wurde. Das ist bekanntermaßen der Fall, wenn der Anwender auf das Bild klickt. Alternativ hätten wir das onMouseMoveEreignis bei Mausklick einrichten und beim Loslassen der Maus wieder löschen können. Aus Gründen der Usability empfiehlt es sich, zumindest den Startpunkt bei einem Mausklick zu visualisieren, so dass der Anwender visuell wahrnehmen kann, welche Strecke er bereits gezogen hat. Alternativ kann man auch eine vollständige Linie vom Startpunkt bis zur aktuellen Mausposition ziehen, eine Variante, die wir uns anschauen wollen. 12. Erweitern Sie den Variablen-Block:
var mLinie:MovieClip;
13. Erweitern Sie in der Deklaration von init() das onRelease-Ereignis unmittelbar vor dessen schließender Klammer:
mLinie.clear();
14. Erweitern Sie die Deklaration von init() unmittelbar vor der schließenden Klammer: mLinie = this.createEmptyMovieClip („behLinie“, this.getNextHighest Depth());
362
Kapitel 25 Trigonometrie
15. Erweitern Sie in der Deklaration von schatten() die if-Bedingung unmittelbar nach der öffnenden Klammer: mLinie.clear();
mLinie.lineStyle(2, 0 × 000000, 100);
mLinie.moveTo(aKlick[0], aKlick[1]);
mLinie.lineTo(_xmouse, _ymouse);
Bei Mausklick zeigt eine schwarze Linie die Strecke an, die Sie mit gedrückter Maustaste zurück legen. Wir legen eine Variable an, in die wir eine Referenz auf einen MovieClip aufnehmen wollen. Wir benötigen ihn, um mit den Zeichnungsmethoden der MovieClip-Klasse arbeiten zu können. In der init()-Funktion erstellen wir diesen leeren MovieClip, in den die Linie gezeichnet wird. Damit sie zu sehen ist, muss besagter Clip auf einer höheren Tiefe liegen als die Grafik. Das erreichen wir dadurch, dass er erst am Ende von init() Funktion erstellt wird. Das Zeichnen erfolgt in der Funktion schatten(). Wir legen als Linieneigenschaften eine Breite von 2 Pixeln, eine schwarze Farbe sowie eine Deckkraft von 100 fest. Da der Linienbeginn der Mausposition beim Klick entspricht, bewegen wir den Zeichenstift zu dieser Stelle, von der aus wir jeweils zur aktuellen Mausposition zeichnen. Allerdings müssen wir bei einer vorhergehenden Mausbewegung gezeichnete Linien löschen, was mit Hilfe der clear()-Funktion sinnvollerweise geschieht, bevor wir neu zeichnen, sowie beim Loslassen der Maus. Eventuell empfindet man es als störend, dass sich bei Mausklick noch keine unmittelbare Änderung einstellt. Erst eine Mausbewegung löst den Effekt aus. Das können Sie einfach ändern. 16. Fügen Sie in der Deklaration von init() im onPress-Ereignis unmittelbar vor dessen schließender Klammer ein:
schatten();
Sobald Sie auf das Bild klicken, passt sich der Schatten an die Mausposition entsprechend unserer Berechnungen an. Die Lösung ist ausgesprochen simpel: Da schatten() für den gewünschten Effekt verantwortlich ist, rufen wir diese Funktion bereits direkt beim Mausklick und nicht erst bei einer nachfolgenden Mausbewegung auf.
Beachten Sie, dass der hier programmierte Effekt nicht kumulativ ist, d. h. jeder Klick setzt den Schatten unabhängig von seiner vorhergehenden Position neu. Damit ist es nicht möglich – entsprechenden schwarzen Humor vorausgesetzt –, dass der Anwender den Schatten außerhalb des Screens zu verschieben versucht.
25.10 Code //--------------- import –------------– import flash.filters.*;
//---------------- vars –-------------var mLinie:MovieClip;
var fSchatten:DropShadowFilter; var nDifX:Number, nDifY:Number, nDif:Number; var nFaktor:Number = 0.1; var aKlick:Array = [];
//-------------- functions –----------function init() {
fSchatten = new DropShadowFilter(); fSchatten.distance = 5; fSchatten.angle = 45;
fSchatten.color = 0 × 333333; fSchatten.alpha = 1; fSchatten.blurX = 5; fSchatten.blurY = 5;
fSchatten.strength = 2; fSchatten.quality = 3;
this.attachMovie(„mcBild“, „bild“, this.getNextHighestDepth()); bild._x = Stage.width/2-bild._ width/2;
bild._y = Stage.height/2-bild._ height/2; bild.filters = [fSchatten];
bild.onPress = function() {
aKlick = [_xmouse, _ymouse]; schatten(); };
25.10 Code
363
bild.onRelease = bild. onReleaseOutside=function () {
m Linie.moveTo(aKlick[0], aKlick[1]);
mLinie.clear();
nDifX = _xmouse-aKlick[0];
aKlick = []; };
bild.onMouseMove = schatten;
mLinie = this.createEmptyMovieClip („behLinie“, this.getNextHighest Depth()); }
function schatten() {
if (aKlick.length>0) { mLinie.clear();
mLinie.lineStyle(2, 0 × 000000, 100);
mLinie.lineTo(_xmouse, _ymouse); nDifY = _ymouse-aKlick[1];
nDif = Math.sqrt((nDifX*nDifX)+ (nDifY*nDifY));
fSchatten.angle = Math.atan2(nDifY, nDifX)*(180/Math.PI); fSchatten.distance = nDif*nFaktor; bild.filters = [fSchatten]; } }
//--------------- start –-------------init();
26
Effekte (Text)
Wie Sie wissen, können Texte nicht nur informatorischen, sondern auch ästhetischen Zwecken dienen. Schon sehr früh wurden die besonderen Animationsfähigkeiten von Flash genutzt, um mitunter recht ausgefallene, nichtsdestotrotz ansprechende Effekte zu kreieren. Trotz der schier unglaublichen Vielfalt an Effekten lassen sich einige grundlegende Prinzipien erkennen, die zum wesentlichen Teil bereits im Kapitel Geskriptete Animationen vorgestellt wurden und im nachfolgenden an einigen einfachen exemplarischen Animationen erläutert werden. Bei den Eigenschaften, die in derartigen Effekten modifiziert werden, handelt es sich i. d. R. um:
• Deckkraft; • Größe; • Position; • Farbe; • Drehung; • Textinhalt. Manchen Effekten begegnen wir auch im Zusammenhang mit Grafiken wieder, andere sind spezifischer Natur. So lässt sich die Größe von Texten im Gegensatz zu Grafiken natürlich nicht nur durch eine Änderung der Ausdehnung des übergeordneten Behälters bzw. des Textfeldes erreichen, sondern auch durch eine entsprechende Änderung der Größe des verwendeten Fonts. Eine wichtige Besonderheit stellt die Art dar, wie Flash Schriften behandelt. Um mit dynamischen Textfeldern Größenänderungen und Drehungen realisieren zu können, muss nämlich der betreffende Font eingebettet werden, ansonsten stellt Flash schlicht nichts dar. Generell gilt natürlich für den Einsatz von Effekten, dass man sparsam mit ihnen umgehen sollte, da
sie sich sehr schnell abnutzen und dann einfach nur noch als störend empfunden werden.
26.1 Text vergrößern und ein-/ausblenden Als sehr beliebt erweist sich das Ein- und Ausblenden von Text, das wir uns im ersten Beispiel näher anschauen wollen. Dabei taucht Text in der Bildschirmmitte auf und wird immer größer, während sich gleichzeitig seine Deckkraft reduziert, bis er nicht mehr zu sehen ist. Anschließend beginnt die ganze Animation erneut. Abbildung 109 zeigt eine frühe Phase der Animation. Ein derartiger Effekt eignet sich bei sparsamem Einsatz gut für Überschriften sowie Catch Phrases, also Schlagwörter, die eine Aussage auf den Punkt bringen sollen. Für Fließtexte verzichtet man zwangsläufig auf solche Effekte, denn dort steht die schnelle Informationsaufnahme im Vordergrund.
Abbildung 109: Animierter Text (Änderung von Größe, Deckkraft, Position)
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
365
366
Kapitel 26 Effekte (Text)
1. Erstellen Sie eine Standarddatei. 2. Öffnen Sie innerhalb der Bibliothek das Kontextmenü, indem Sie mit der rechten Maustaste auf eine freie Stelle klicken. 3. Wählen Sie die Option . 4. Wählen Sie aus der unter <Schriftart> angezeigten Liste eine beliebige Schriftart aus. Die Liste entspricht den Schriftarten, die dem Betriebssystem bekannt sind. Wir wählen beispielhaft den etwas ausgefallenen Font Chinyen. 5. Weisen Sie dem ausgewählten Font einen Namen zu, der dem Font entspricht. Wir nennen unsere Schrift beispielhaft chinyen. 6. Weisen Sie dem neu erstellten Font in der Bibliothek per rechter Maustaste eine gleichnamige Verknüpfung zu, so dass wir per Skript darauf zugreifen können. 7. Weisen Sie actions folgendes Bildskript zu:
var tTxt:TextField;
//--------------- vars –-------------
function init():Void {
var sTxt:String = „Hallo Flasher in
aller Welt!“;
var mTxt:MovieClip;
Abbildung 110: Einbetten eines Fonts in der Bibliothek
var tfTxt:TextFormat;
Zunächst teilen wir Flash mit, welchen Text wir einblenden wollen. Beispielhaft geschieht dies mit Hilfe der Variablen sTxt, an deren Stelle Sie natürlich auch einen externen Text einlesen oder einen String übernehmen können, den der Anwender in ein Eingabefeld geschrieben hat. Um flexibler zu sein, richten wir eine Variable zur Referenzierung eines Textbehälters ein. Die dritte Variable verweist später das auf dynamisch zu erzeugende Textfeld, in dem der anzuzeigende Text dargestellt wird. Die Variable tfTextFormat dient dazu, die Eigenschaften des zu definierenden Textformats aufzunehmen, so dass wir dieses Format einem String zuweisen können. 8. Fügen Sie folgenden Funktions-Block ein: //------------ functions –-----------
tfTxt = new TextFormat(); tfTxt.font = „chinyen“; tfTxt.size = 1;
26.1 Text vergrößern und ein-/ausblenden
tfTxt.color = 0 × 000000;
mTxt = this. createEmptyMovieClip(„behTxt“, 0); mTxt._x = Stage.width/2;
mTxt._y = Stage.height/2;
tTxt = mTxt.createTextField(„tf“, 1, 0, 0, 0, 0); tTxt.embedFonts = true;
tTxt.autoSize = „center“; tTxt.selectable = false; tTxt.text = sTxt;
tTxt.setTextFormat(tfTxt); tTxt._x = -tTxt._width/2;
tTxt._y = -tTxt._height/2; }
9. Fügen Sie im Anschluss an diese Zeilen einen Aufruf ein: //------------- aufruf –------------ init();
Die Funktion init() erzeugt den Behälter und dessen Textfeld sowie das Textformat, mit dem der animierte String dargestellt wird. Das gewünschte Textformat referenzieren wir mit der Variablen tfTxt. Im Hinblick auf die Animation sind für uns Font, Größe und Farbe interessant, die wir jeweils auf folgende Werte setzen:
• „chinyen“, womit wir Flash anweisen, denjenigen Font zu verwenden, der in der Bibliothek über den angegebenen Verknüpfungsnamen verfügt. Wenn Sie mit einem Standardfont arbeiten möchten, können Sie auf diese Zeile verzichten. Flash verwendet dann bei der Erzeugung eines neuen Textfeldes „Times New Roman“ unter Windows und „Times“ unter Mac; • 1, so dass der zugewiesene Text zunächst nahezu unsichtbar scheint; • 0 × 000000, also schwarz, um einen möglichst großen Kontrast zum Hintergrund herzustellen. Selbstverständlich können Sie andere Farbwerte wählen. Möglich wäre auch, den Anwender eine Farbe beispielsweise in Abhängigkeit von der aktuellen Mausposition wählen zu lassen oder jeweils eine Zufallsfarbe zu verwenden. Im letztgenannten Fall sollte man zulässige Farbwerte speichern, um nicht versehentlich eine zu helle Farbe auszuwählen.
367
Anschließend erstellen wir einen neuen leeren MovieClip und positionieren ihn exakt in der Bühnenmitte. In diesem Clip wird ein neues Textfeld erzeugt. Wir müssen dabei aus formalen Gründen Argumente für Position und Größe mitgeben, die jedoch erst dann korrekt bestimmt werden können, wenn ein konkreter Textinhalt zugewiesen wurde. Deswegen verwenden wir an dieser Stelle für die fraglichen Werte einfach 0. Logischerweise ist bei solchen Textfeldern die Eigenschaft embedFonts auf false gesetzt, was man übernehmen kann, wenn nur ein Standardfont verwendet werden soll (und weder Größe noch Deckkraft animiert werden). In unserem Fall möchten wir einen speziellen Font verwenden und weisen daher der genannten Eigenschaft true zu. Da ein neues Textfeld die Auswahl des Inhalts durch den Anwender zulässt, was in unserem Zusammenhang aber keinen Sinn machen würde, schalten wir diese Möglichkeit mit false aus. Der in sTxt gespeicherte String wird dem Textfeld zugewiesen. Um zu verhindern, dass er direkt in voller Größe erstrahlt, formatieren wir das Textfeld mit dem zuvor definierten Format. Da dort die Buchstabengröße mit 1 initialisiert wurde, bleibt der Text zunächst fast unsichtbar. Erst jetzt können wir eine korrekte Position zuweisen. Wenn es sich dabei wie in unserem Fall um die Bildschirmmitte handeln soll, muss das Textfeld um seine halbe Breite nach links und seine halbe Höhe nach oben verschoben werden. Da sich der übergeordnete MovieClip bereits in der exakten Mitte befindet, muss nur noch der Text in Bezug auf den Registrierungspunkt dieses Clips gesetzt werden, was hiermit erfolgt. In der letzten Zeile des Scripts rufen wir init() auf, so dass in der Mitte des Screens ein ausgesprochen bescheidenes Textfeldchen mit unleserlichem Inhalt auftaucht. 10. Erweitern Sie den Variablen-Block: var nGrenze:Number = 5; var nAlpha:Number = 1;
11. Fügen Sie innerhalb der Deklaration von init() unmittelbar vor der schließenden Klammer ein:
mTxt.onEnterFrame = animieren;
12. Erweitern Sie den Funktions-Block um folgende Deklaration: function animieren():Void { tfTxt.size += 1;
368
Kapitel 26 Effekte (Text)
tTxt.setTextFormat(tfTxt); tTxt._y = -tTxt._height/2; this._alpha –= nAlpha;
if (this._alpha=sTxt.length) {
clearInterval(nInterval); } }
4. Fügen Sie am Ende des Skriptes einen Aufruf ein:
Kapitel 26 Effekte (Text)
Die eigentliche Animation gestaltet sich denkbar einfach: Mit Hilfe von nIndex legen wir fest, welches Zeichen in das Textfeld geschrieben werden muss. Da wir sie mit 0 initialisieren, greift Flash auf das Zeichen an der Position 0, also das erste Zeichen zu, in sTxt zu und hängt es an den im Feld vorhandenen Text (momentan ein leerer String) an. Danach inkrementieren wir nIndex, so dass beim nächsten Intervall das Zeichen an Position 1 genommen wird. Dies geschieht solange, bis das Ende des Strings in sTxt erreicht wird, was der Fall ist, wenn, wie in der if-Bedingung kontrolliert, nIndex mindestens der Länge von sTxt entspricht. Dann löschen wir das Intervall, wodurch die Animation automatisch beendet wird. Wir können einen Textumbruch auch automatisch von Flash durchführen lassen. In dem Fall benötigt das Textfeld eine feste Größe, z. B. 450 Pixel als Breite, und die Eigenschaften multiline sowie wordWrap müssen auf true gesetzt werden. Genau genommen reicht es sogar aus, nur wordWrap zu definieren, da Flash dann automatisch ein mehrzeiliges Textfeld verwendet (merkwürdigerweise bleibt dessen Eigenschaft multiline auf dem bei einem derartigen Feld standardmäßig initialisierten Wert false stehen, obwohl der Text korrekt in mehreren Zeilen dargestellt wird). Logischer ist es jedoch, auch multiline entsprechend zu setzen. Abschließend rufen wir die init()-Funktion auf und übergeben ihr die zeitliche Dauer des Intervalls in Millisekunden sowie die Position des Textfeldes. Wollten wir das Feld per Skript mittig positionieren, müssten wir entweder über getTextExtent() die Feldbreite ermitteln oder, was sich programmiertechnisch einfacher gestaltet, in init() das Feld einmal komplett mit Text befüllen, die benötigten Werte auslesen und anschließend den Text wieder löschen, bevor das Intervall eingerichtet wird. Der Anwender wird davon nichts merken, da Flash den Screen erst dann zeichnet, nachdem das komplette Skript abgearbeitet wurde.
//------------- aufruf –-------------
26.10 Text einblenden aus Zufallsbuchstaben In einem zeitlichen Abstand von 80 Millisekunden init(80, 100, 80);
blendet Flash einen Text ein, der sich 100 Pixel vom linken und 80 Pixel vom oberen Rand entfernt befindet.
Das so beliebte Einblenden ist nicht auf Deckkraft und Farbe beschränkt, sondern funktioniert auch mit konkreten Textinhalten. Dabei sieht man zunächst ein
26.10 Text einblenden aus Zufallsbuchstaben
387
5. Fügen Sie folgenden Funktions-Block ein: //------------ functions –---------- function init():Void { Abbildung 117: Text einblenden aus Zufallsbuchstaben
tfTxt = new TextFormat(); tfTxt.font = „candles“;
Feld mit beliebigen Zeichen, die sich dann sukzessive zu einem richtigen Text anordnen. Abbildung 117 zeigt eine Phase einer derartigen Animation. Was die Erstellung des Textes und dessen Formatierung anbelangt, greifen wir wie gewohnt auf bereits Bekanntes zurück. 1. Erstellen Sie eine Standarddatei. 2. Ändern Sie die Hintergrundfarbe auf schwarz. 3. Fügen Sie in der Bibliothek einen beliebigen Font wie in der ersten Übung gezeigt ein. Wir verwenden hier beispielhaft den Font Candles mit dem Verknüpfungsnamen candles. 4. Fügen Sie in actions als Bildskript folgenden Variablen-Block ein: //------------ vars –--------------- var mTxt:MovieClip;
tfTxt.size = 40;
tfTxt.color = 0xffffff;
mTxt = this. createEmptyMovieClip(„behTxt“, 0); tTxt = mTxt.createTextField(„tf“, 1, 0, 0, 0, 0); tTxt.embedFonts = true; tTxt.autoSize = „left“;
tTxt.selectable = false; tTxt.text = „“; }
Wie schon öfters erzeugen wir ein Textfeld, das mit unserem speziellen Format ausgezeichnet wird. Zum Schluss weisen wir ihm einen leeren String zu. Würden wir die Funktion jetzt schon aufrufen, wäre daher notgedrungen wenig zu sehen.
var tTxt:TextField;
6. Erweitern Sie die Funktion unmittelbar vor der schließenden Klammer:
var sTxt:String = „Hallo Flasher!“;
for (var i:Number = nStart; i