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 <=, nur dass sie bereits dann true zurück geben, wenn der genannte Vergleichswert erreicht, aber noch nicht unbedingt überschritten wurde. nZahl = 6;
trace(nZahl >= 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
<
kleiner als
true, wenn der erste Operand kleiner als der zweite Operand
links rechts
größer als
true, wenn der erste Operand größer als der zweite Operand
links rechts
kleiner gleich
true, wenn der erste Operand kleiner oder gleich dem zweiten Operanden
links rechts
größer gleich
true, wenn der erste Operand größer oder gleich dem zweiten Operanden
links rechts
gleich
true, wenn der erste Operand gleich dem zweiten Operanden
links rechts
nicht gleich
true, wenn der erste Operand nicht gleich dem zweiten Operanden
links rechts
strikte Gleichheit
true, wenn Wert und Datentyp der Operanden gleich
links rechts
>
<= >= == !=
=== !==
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 < 5; i++, j+= 2){
In der ersten Zeile definieren wir zwei Variablen vom Datentyp MovieClip, in der Zeile darunter zwei Schleifen mit verschiedenen Zählvariablen. Der Komma-Operator tritt in beiden Fällen mit zwei Operanden auf, die von links nach rechts ausgewertet werden. Der Punkt dient dazu, eine Objekthierarchie abzubilden und den Zugriff auf untergeordnete Elemente zu gestatten. In mancher Hinsicht gleicht er dem Backslash bei der Angabe von Dateipfaden. Dazu einige Beispiele: auto.auspuff.aQualm.play(); auto.onPress = function(){ auto._x = 125;
auto.animieren();
In der ersten Zeile sprechen wir einen verschachtelten MovieClip mit dem Instanznamen auto an. Er enthält einen anderen MovieClip namens auspuff, in dem sich seinerseits ein MovieClip aQualm befindet. Dessen Zeitleiste starten wir durch den Befehl play(). Ähnlich würde ein Pfad aussehen, wenn wir beispielsweise auf der Festplatte d: eine Datei im Ordner works mit dem Unterordner flash ansprechen wollten, z. B.: d:\works\flash\operatoren.fla. Die folgende Zeile legt ein Ereignis namens onPress fest, das dem durch den Punkt-Operator vorangestellten Objekt auto zugeordnet wird. Dieses Ereignis kann also nur zusammen mit dem MovieClip auto auftreten. Danach greifen wir auf eine Eigenschaft von auto zu, in diesem Fall auf dessen horizontale Position. In der letzten Zeile wird eine selbst definierte Methode des besagten MovieClips aufgerufen. Bei dem genannten Operator muss auf der linken Seite immer ein Objekt, auf der rechten eine zugehörige Eigenschaft (einschließlich weiterer Objekte wie im Fall des verschachtelten MovieClips), Methode oder Ereignis stehen. Eine derartige Punkt-Syntax zeichnet übrigens neben ActionScript auch die meisten modernen Skriptsprachen und natürlich Programmiersprachen aus. Mit dem Gruppierungs-Operator, der in Form runder Klammern Verwendung findet, kann die Reihenfolge der Auswertung von Ausdrücken festgelegt werden. Darin gleicht er den Klammern, wie wir sie aus der Schulmathematik kennen: nErgebnis = 2 * (5 + 4);
52
Die Berechnung der Addition hat in diesem Fall Vorrang vor der Multiplikation. Ohne die Klammern würde zuerst multipliziert, anschließend addiert, was zu einem anderen Ergebnis führt. Befindet sich innerhalb eines Klammer-Ausdrucks ein weiterer Klammer-Ausdruck, so hat dieser Vorrang vor allen anderen. Programmierspezifisch ist der Einsatz von Klammern, um Funktionen ohne/mit Parametern zu kennzeichnen sowie Bedingungen und Schleifen zu definieren: stop();
trace(„Ich bin ein Parameter“); animieren(5, 3, true);
for(i = 0; i < 5; i++){ if(this._x >= 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 <= 5; i++){
behaelter.attachMovie(„kreis“,„kreis“ +i,i); behaelter[„kreis“+i]._x = i * 50; }
Mit diesem Code, der Ihnen in späteren Kapiteln in ähnlicher Form noch recht häufig begegnen wird, fügen wir fünf Objekte auf der Bühne ein, deren Instanznamen in der zweiten Zeile aus dem String „kreis“ und der Zählvariablen i gebildet wird. Damit Flash darin ein gültiges Objekt erkennt, greifen wir in der dritten Zeile mit Hilfe der eckigen Klammer auf diesen Ausdruck zu. Flash evaluiert ihn und ist infolgedessen in der Lage, eine Instanz auf der Bühne anzusprechen. In
6.8 Sonstige
dem Fall entspricht unsere Schreibweise der älteren, nicht mehr zu verwendenden eval()-Methode. Wir haben vorhin ein Objekt mit Hilfe der geschweiften Klammern erzeugt. Eine Alternative dazu besteht in der Verwendung des new-Operators, gefolgt von einem Konstruktor. Das Beispiel mit dem Objekt auto sieht dann so aus: auto = new Object(); auto.raeder = 4; auto.tueren = 2;
Wir richten ein neues Objekt ein, das über alle Eigenschaften der Object-Klasse verfügt. Anschließend weisen wir ihm darüber hinausgehende, spezielle Eigenschaften (raeder, tueren) zu. Eine derartige Vorgehensweise ist – im Gegensatz zu den geschweiften Klammern – mit nahezu allen Klassen möglich, die eine Instanziierung erlauben. Den Stammbaum der zahllosen Objekte, die eine Anwendung bevölkern, können wir uns mit dem typeof-Operator anzeigen lassen. Das ist notwendig, um herauszufinden, von welcher Klasse ein konkretes Objekt instanziiert wurde; denn davon hängt es u. a. ab, welche Operationen mit dem betreffenden Element durchgeführt werden können. Am Beispiel des vorher erzeugten Objektes namens auto würde der Zugriff lauten: trace(typeof(auto));
Ausgabe im Nachrichtenfenster: object Der Datentyp des befragten Elements besteht in diesem Fall aus einem Object, wie Flash im Nachrichtenfenster mitteilt. Durch den instanceof-Operator können wir ermitteln lassen, ob das konkrete Element von einer bestimmten Klasse instanziiert wurde. Wenn wir bei unserem lieb gewonnenen auto bleiben, könnten wir schreiben: trace(auto instanceof Object);
53
Ausgabe im Nachrichtenfenster: true Während typeof einen String zurück liefert, erhalten wir mit instanceof lediglich einen Wahrheitswert. Wir müssen also konkret mitteilen, gegen welche Klasse wir ein bestimmtes Element testen wollen. In obigem Beispiel wollen wir herausfinden, ob auto der Klasse Object zugehört, was Flash mit einem true im Nachrichtenfenster beantwortet. Für Anfänger gewöhnungsbedürftig und selbst langjährigen Flash-Entwicklern nicht unbedingt vertraut, ermöglicht es der Konditional-Operator, eine if-else – Bedingung in Kurzform zu fassen: bSchalter = false;
(bSchalter == true)?trace(„eingeschaltet“) :trace(„ausgeschaltet“);
Ohne Konditional-Operator lautet das Skript: bSchalter = false;
if (bSchalter == true) {
trace(„eingeschaltet“); } else {
trace(„ausgeschaltet“); }
Wir initialisieren die Variable bSchalter mit dem Wert false. Anschließend fragen wir ab, ob sie den Wert true enthält. Ist das der Fall, geben wir die Nachricht „eingeschaltet“ aus, andernfalls die Nachricht „ausgeschaltet“. Da Skripte so allerdings schwerer lesbar werden, sollte man sparsam mit diesem Operator umgehen, es sei denn, diejenigen, die Ihren Code zu sehen bekommen, sind geübte Programmierer. Das ist unter Multimedia-Entwicklern allerdings immer noch nicht selbstverständlich, da sich viele Quereinsteiger in der Branche tummeln. In den Skripten des vorliegenden Buches wird auf diesen Operator verzichtet.
7
Variablen
Oft ist es notwendig, Informationen zu speichern, um zu einem späteren Zeitpunkt wieder darauf zugreifen zu können. Was uns simpel erscheint, da wir ein Gedächtnis als Datenspeicher verwenden können, bedarf in der Programmierung eines Hilfskonstrukts, um solche Daten abzulegen. Dieses Konstrukt bezeichnet man als Variable. Darunter ist ein Platzhalter, ein Stellvertreter für einen beliebigen Wert, der sich ändern kann, zu verstehen.
7.1 Deklaration Bei der Verwendung von Variablen unterscheidet die Programmierung zwei Prozesse, nämlich die Deklaration (oder Definition) und die Initialisierung. Mit der Deklaration wird ein Programm angewiesen, im Arbeitsspeicher einen Platz zur Aufnahme von Daten zu reservieren. Bezogen auf ActionScript besteht sie aus dem Schlüsselwort var, gefolgt von dem VariablenNamen und gegebenenfalls dem Datentyp, z. B.:
wiesen werden darf, während die Variable nAnzahl nur eine Zahl aufnimmt. In der Autorenumgebung achtet Flash darauf, dass wir in der späteren Verwendung der Variablen exakt den definierten Datentyp einhalten, ansonsten gibt das Programm eine Fehlermeldung aus. Flash bietet nach Eingabe des Doppelpunktes eine Liste der zur Verfügung stehenden Datentypen an, aus der Sie per Mausklick wählen können, wie Abbildung 11 zeigt. Weitergehende Informationen zu den Datentypen finden Sie am Ende dieses Kapitels. Alternativ ist auch eine einzeilige Deklaration möglich: var sVorname: String, sNachname: String, nAnzahl: Number;
var sVorname;
var sNachname; var nAnzahl;
Während solche Definitionen für AS 1.0 ausreichen, sollte man in AS 2.0 die zulässigen Datentypen benennen: var sVorname: String;
var sNachname: String; var nAnzahl: Number;
Unsere Definition besagt, dass der Variablen sVorname nur ein String, also ein beliebiger Text zuge-
Abbildung 11: Codehinweise zu Datentypen
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
55
56
Beachten Sie bei einer einzeiligen Deklaration, dass nur einmal ganz am Anfang der Schlüsselbegriff var verwendet werden darf, der automatisch alle kommaseparierten Variablen-Namen erfasst, die in dieser einen Zeile auftreten. Wenn wir auf eine Typisierung verzichten wie bei den Beispielen am Anfang, dann ermittelt Flash den Datentyp jeweils beim Zugriff auf die Variable automatisch. Außerdem führt Flash eine automatische Typkonvertierung durch, wenn wir bei einer Zuweisung den Datentyp ändern. Das ist für Schreibfaule zugegebenermaßen praktisch, aber schlechter Stil und fehleranfällig. Für die Namensgebung existieren bestimmte Regeln:
• Der Name darf nicht mit einer Zahl beginnen. • Er darf keine Sonderzeichen wie z. B. % enthalten.
Ausgenommen sind das Dollarzeichen $ und der Unterstrich _, die auch am Wortanfang stehen können. • Er darf keine Leerzeichen enthalten (falsch z. B. var meine variable). • Zwar sind deutsche Umlaute wie ä oder ü möglich, aber damit gewöhnt man sich einen schlechten Programmierstil an. Da andere Sprachen diese Zeichen nicht zulassen, sollte man auch in ActionScript darauf verzichten, um sich nicht unnötig Ausnahmeregelungen merken zu müssen. In der folgenden Tabelle führen wir die Verwendung von Umlauten daher als syntaktisch falsch auf. • In AS 2.0 wird zwischen Groß- und Kleinschreibung unterschieden. • Absolut tabu sind natürlich die von der Skriptsprache reservierten Begriffe. Ansonsten kann Flash nicht mehr unterscheiden, ob Sie sich nun auf Ihre eigene Variable oder auf den betreffenden reservierten Begriff beziehen wollen. Leider erhalten Sie keine Fehlermeldung, wenn Sie allen Warnungen zum Trotz Schlüsselbegriffe auf diese Weise vergewaltigen. Immerhin hilft die farbige Syntaxhervorhebung des AS-Editors: Sollte der von Ihnen gewählte Begriff nicht in reinstem Schwarz erstrahlen, haben Sie versehentlich einen Schlüsselbegriff verwendet. • Der Name muss eindeutig sein, d. h. innerhalb derselben Zeitleiste können nicht zwei Variablen des gleichen Namens existieren. Ist dies trotzdem der Fall, überschreibt die zweite die erste gleichnamige Variable.
Kapitel 7 Variablen
• Die in früheren Flash-Versionen gültige Längenbe-
schränkung von maximal 256 Zeichen wurde zwar aufgehoben, aber es macht keinen Sinn, mit derart langen Namen zu arbeiten.
In der Tabelle sehen Sie einige Beispiele für falsche und korrekte Namen. Nicht korrekt
Fehler
Korrekt
1Zahl
Beginnt mit Zahl
Zahl1
meine%e
Enthält Sonderzeichen
meineProzente $Prozente _Prozente
meine Variable Enthält Leerzeichen meineVariable gültigeBezeichnung
Enthält Umlaut
gueltigeBezeichnung
nAnzahl nanzahl
Missachtet Groß-/ Kleinschreibung
entweder nur: nAnzahl oder nur: nanzahl
Number
Verwendet reservierten Begriff
nNumber
Beispiele für Variablen-Namen
Obgleich Flash uns relativ viel Spielraum bei der Namensgebung gewährt, sollte man sich an gewisse Konventionen halten, damit ein Skript lesbar bleibt und von anderen (etwa bei Teamprojekten) verstanden werden kann. Der Name sollte, wenn möglich, etwas über den Einsatz der Variablen verraten. Solche Namen bezeichnet man als sprechende Namen. Wenn wir z. B. einen Vornamen speichern wollen, dann wäre die Bezeichnung sVorname naheliegend. Stoßen wir beim Lesen eines längeren Skripts auf eine solche Bezeichnung, können wir uns schon ungefähr vorstellen, wozu diese Variable dient. Hätten wir sie, was formal durchaus möglich wäre, statt dessen a genannt, wäre ihr Verwendungszweck nicht unmittelbar ersichtlich. Nach dem Motto „In der Kürze liegt die Würze“ sollte man zugleich auf allzu lange Namen verzichten. So wäre, um bei unserem Beispiel zu bleiben, zwar möglich: sVorname_des_ersten_spielers_der_sich_ bei_diesem_spiel_anmeldet
Aber spätestens beim wiederholten Tippen solcher Bandwürmer fallen Ihnen irgendwann garantiert die Finger ab und der Name bläht ein Skript unnötigerweise auf.
7.1 Deklaration
Ein vorangestelltes Kürzel wie beispielsweise das
s hilft in einem längeren Skript, die selbst definierten
Elemente in Kategorien einzuordnen und logischen Fehlern auf die Spur zu kommen. Denn so können wir gerade in komplexen Skripten oft erkennen, ob eine bestimmte Operation zulässig ist oder nicht. Eine Ausnahme stellen Zählvariablen in Schleifen dar, bei denen man i. d. R. auf ein Präfix verzichtet. In der Literatur existieren verschiedene mehr oder minder sinnvolle Vorschläge für derartige Präfixe. Auch wenn der Autor dieser Zeilen nicht die notwendige Selbstdisziplin besitzt, um sich konsequent an zumindest einen dieser Vorschläge zu halten (darf man so etwas überhaupt in einem Lehrbuch eingestehen?), so erweist sich die von Reinhardt/Lott modifizierte und hier leicht ergänzte Variante als durchaus empfehlenswert. Die sogenannte Ungarische Notation geht auf Microsoft zurück und dient dazu, über eine Namenskonvention die Lesbarkeit von Source Code zu erhöhen. Im vorliegenden Fall binden wir die Namen der Variablen an den Typ der zugewiesenen Daten. So kann eine Variable, die den Datentyp Number aufnimmt, mit einem vorangestellten n gekennzeichnet werden, etwa nZahl. Macromedia/Adobe bieten dazu eine Alternative an, die darin besteht, Ihre Variable mit einem kennzeichnenden Suffix zu versehen, etwa auto_mc. Von einem derartigen Vorgehen sollte man möglichst absehen, da es eine flashspezifische Lösung darstellt, die so in anderen Sprachen nicht gängig ist. Insofern erscheint die von Reinhardt/Lott vorgeschlagene, sich an der Ungarischen Notation orientierende Lösung sinnvoller. Gerade bei längeren Skripten empfiehlt es sich, Variablen ganz am Anfang in einem zusammengehörigen Block zu deklarieren. So behält man leichter den Überblick und muss sich nicht im Fall der Fälle durch ein ganzes Skript durchwühlen, um nachzusehen, wie und wo eine entsprechende Variable definiert wurde. Ausgenommen sind Objektvariablen, die natürlich erst dann deklariert werden können, wenn das Objekt, dem sie zugeordnet sind, existiert (s. u.). Wie bereits erwähnt, können die benötigten Objekte, und damit auch die Variablen, dynamisch zusammengesetzt werden, etwa mit Hilfe einer Schleife. for (var i:Number = 1; i<=10; i++) { this[„zahl“+i] = i;
57 Präfix
Datentyp
a
Array
bm
BitmapData
b
bt c
cam cm
cmi d f
fu lc lv m
ml
mic mx n
nc ns o
pt pj r
rs s
sd so t
tf tr vd
xml
xmls
Boolean Button
ColorTransform Camera
ContextMenu
ContextMenuItem Date
Filter
Function
LocalConnection LoadVars
MovieClip
MovieClipLoader Microphone Matrix Number
NetConnection NetStream Object Point
PrintJob
Rectangle RecordSet String Sound
SharedObject TextField
TextFormat Transform Video XML
XMLSocket
Variablen-Präfixe (nach Reinhardt/Lott: Flash MX 2004 ActionScript Bible, p. 98)
trace(this[„zahl“+i]); }
58
Das Nachrichtenfenster zeigt beim Testen die Werte 1 bis 10 an. Wir richten insgesamt zehnmal eine Variable ein, deren Name aus dem Präfix „zahl“ und dem aktuellen Inhalt von i besteht. Die Variablen-Namen lauten also zahl1, zahl2, zahl3 etc. Ihnen weisen wir jeweils den aktuellen Inhalt von i zu, so dass im Nachrichtenfenster von 1 bis 10 hochgezählt wird. Die Schleifenanweisung for sorgt dafür, dass ein Vorgang mehrfach ausgeführt wird. Bei der Deklaration sollte man durch einen vorangestellten Kommentar den Einsatzzweck verdeutlichen: //Vorname des Spielers var sVorname;
//Nachname des Spielers var sNachname;
//Anzahl der Objekte, die im Spiel auf gefangen werden können var nAnzahl;
Stößt man in einem längeren Skript auf eine Variable, bei der man trotz korrekter Namensgebung nicht so recht weiß, was sie denn macht, dann genügt ein Blick in den Deklarationsteil, wo die Kommentare die entsprechende Information bereithalten. Bei unbekannten Skripten können sie zudem bereits eine erste Vorstellung vom Aufbau der Anwendung geben und damit ein erstes Vorverständnis vermitteln.
Kapitel 7 Variablen
3. Fügen Sie nach dem bisherigen Skript ein: if(nAnzahl == undefined){ nAnzahl = 10;
trace(nAnzahl) }
In diesem Fall testen wir, ob nAnzahl bereits einen gültigen Wert besitzt. Falls nicht, dann weisen wir der Variablen 10 zu und lassen uns diesen Wert im Nachrichtenfenster ausgeben. Hier spielt es keine Rolle, ob vor dem Skript eine Deklaration von nAn zahl erfolgt oder nicht. Fehlt sie, wird automatisch eine Variable namens nAnzahl eingerichtet und 10 zugewiesen. Wissen wir bereits von Anfang an, welchen Inhalt eine Variable besitzen soll, können wir ihn direkt bei der Definition mitgeben. Diesen Vorgang bezeichnet man als Initialisierung. 4. Ersetzen Sie das Skript durch: var nAnzahl:Number = 50;
Jetzt wird nicht nur ein Speicherbereich für unsere Variable reserviert, sondern ihm wird auch direkt ein Wert, nämlich die Zahl 50 zugewiesen. Lassen Sie sich die Variable erneut mit einem trace() ausgeben. 5. Ergänzen Sie den Code: var nAnzahl:Number = 50; trace(nAnzahl);
7.2 Wertezuweisung In unseren bisherigen Beispielen werden die Variablen lediglich eingerichtet. Sie erhalten von Flash automatisch den Wert undefined. Das können Sie testen, indem Sie nach der Deklaration die betreffende Variable tracen (aus Gründen der Vereinfachung verzichten wir hier und an anderer Stelle auf einen Kommentar im Skript).
Das Nachrichtenfenster zeigt nun den bei der Initialisierung zugewiesenen Wert 50 an. Eine Variable kann zu einem bestimmten Zeitpunkt immer nur exakt einen einzigen Wert enthalten. Folgende Initialisierung wäre also falsch: var sName:String = „Zaphod“, „Beeble brox“;
1. Erstellen Sie eine Standarddatei. 2. fügen Sie in actions folgendes Bildskript ein:
Durch das Komma auf der rechten Seite der Zuweisung teilen wir Flash mit, zwei Werte in die Variable sName zu packen, was zu einer Fehlermeldung führt. Korrekt dagegen wäre wie im vorhergehenden Beispiel mit nAnzahl:
var nAnzahl: Number;
var sName:String = „Zaphod“;
Im Nachrichtenfenster erscheint undefined. Das lässt sich natürlich auch abfragen.
Im Laufe des Skripts kann sich dieser eine Wert natürlich ändern, es ist also möglich, der Variablen im Laufe der Zeit jeweils einen anderen Wert zuzuweisen.
trace(nAnzahl);
7.2 Wertezuweisung
6. Ersetzen Sie das Skript durch: var sName:String = „Zaphod“ trace(sName);
sName = „Beeblebrox“; trace(sName);
Zuerst erscheint im Nachrichtenfenster Zaphod, dann Beeblebrox, denn der ursprüngliche Wert der Variablen sName wird in der dritten Zeile durch einen neuen Wert überschrieben. Die hier beschriebene Deklaration und Initialisierung ist in Flash im Gegensatz zu Programmiersprachen nicht unbedingt notwendig. Bei Bedarf kann man auch an einer beliebigen Stelle innerhalb eines Skriptes eine Variable durch simple Zuweisung eines Wertes einrichten. Das vereinfacht zwar den Umgang mit AS, hat jedoch in den meisten Fällen den Nachteil der Unübersichtlichkeit, was sich gerade in größeren, komplexen Skripten negativ auswirken kann. Insofern empfiehlt es sich, den etwas umständlicheren Weg der eindeutigen Deklaration, gegebenenfalls inklusive Initialisierung zu wählen und eher selten auf die direkte Zuweisung zuzugreifen. Bereits mehrfach wurde der Zuweisungsoperator = verwendet. Damit sorgen wir dafür, dass eine Variable einen Wert erhält, was ja an irgendeiner Stelle unseres Skriptes notwendig ist, sonst können wir uns den Einsatz der Variablen gleich sparen. Die Syntax ist recht einfach: var nZahl:Number; var sName:String; nZahl = 5;
sName = „Hans“;
Auf der linken Seite steht immer der Variablen-Name, gefolgt vom Zuweisungsoperator (also =), während sich rechts davon der Wert befindet, den wir in die Variable packen wollen. Dieser Wert muss nicht zwangsläufig aus einer einzelnen Zahl oder einem einzelnen String wie in obigem Beispiel bestehen. Statt dessen kann man auch einen kompletten Ausdruck zuweisen, etwa eine Rechenoperation: 7. Ersetzen Sie das Skript durch: var nSumme: Number; nSumme = 5 + 7 + 2; trace(nSumme);
59
Zuerst arbeitet Flash alle Anweisungen ab, die sich auf der rechten Seite des Zuweisungsoperators befinden, also die Addition von 5, 7 und 2. Anschließend wird das Ergebnis der Variablen auf der linken Seite zugewiesen, so dass diese nun den Wert 14 enthält. Die Ausdrücke auf der rechten Seite können dabei beliebig komplex sein. Das gilt beispielsweise auch für Strings, da ja ein Ausdruck nicht zwangsläufig nur eine Berechnung enthalten muss: 8. Ersetzen Sie das Skript durch: var sName: String;
sName = „Hans“ + „ Andersen“; trace(sName);
Zunächst zieht Flash die beiden Strings zusammen und weist dann das Ergebnis der Variablen zu, so dass diese nun den Wert „Hans Andersen“ enthält. Der Ausdruck auf der rechten Seite kann sogar den bisherigen Wert der Variablen auf der linken Seite enthalten, so dass es einfach möglich ist, den aktuellen Wert zu verrechnen. 9. Ersetzen Sie das Skript durch: var nSumme: Number; nSumme = 5 + 7 + 2;
nSumme = nSumme + 1; trace(nSumme);
Der Inhalt von nSumme beträgt, wie wir gesehen haben, nach der ersten Zuweisung 14. In der darauf folgenden Zeile nimmt Flash genau diesen Wert, addiert 1 hinzu und weist das Ergebnis wieder der Variablen zu. Das Element, das auf der linken Seite der Zuweisung steht, repräsentiert für Flash den Speicherplatz der Variablen, den man auch als Linkswert bezeichnet. Auf der rechten Seite dagegen befindet sich der aktuelle Inhalt der Variablen, auch Rechtswert genannt. Durch diese Unterscheidung ist es dem AS-Interpreter möglich, in einer einzigen Zuweisung mehrfach den gleichen Variablen-Namen zu verwenden, damit jedoch verschiedene Prozesse zu verbinden. Eine Variable kann einen Wert erhalten, indem man auf andere Variablen zugreift: 10. Ersetzen Sie das Skript durch: var nSummand1:Number = 5;
var nSummand2:Number = 10;
60
Kapitel 7 Variablen
var nSumme:Number = nSummand1 + nSummand2;
trace(nSumme);
Im Nachrichtenfenster erscheint die Zahl 15. Nach der Deklaration und Initialisierung von nSummand1 und nSummand2 weisen wir die Addition dieser beiden Variablen nSumme unmittelbar als Initialisierung zu. Natürlich kann die Zuweisung auch eine Kombination aus Variable und Wert enthalten. 11. Erweitern Sie das Skript:
var sNachname:String = „Kohl“;
trace(„Mein Nachname lautet “+ sNachname);
Das Nachrichtenfenster setzt den im Klartext angegebenen String „Mein Nachname lautet “ und den Inhalt der Variablen sNachname zu einem Ausdruck zusammen.
7.3 Reichweite Jede Variable besitzt eine sogenannte Reichweite bzw. Gültigkeit, die festlegt, von welcher Stelle unseres Skriptes aus sie bekannt ist. Man unterscheidet zwischen globalen und lokalen Variablen. Das entspricht in gewisser Weise dem, was wir mit einem Langzeitund einem Kurzeitgedächtnis bezeichnen. Die globale Variable ist ab dem Zeitpunkt ihrer Deklaration überall bekannt, während ihr lokales Pendant nur zu ganz bestimmten Zeitpunkten an ganz bestimmten Orten gilt. Wenn wir beispielsweise eine personalisierbare Lernanwendung erstellt haben, dann ist es notwendig, die ganze Zeit über auf den Namen des Lerners zugreifen zu können. Hier könnte es also sinnvoll sein, mit einer globalen Variablen zu arbeiten. Sie wäre ab ihrer Deklaration in der kompletten Anwendung bekannt und müsste beim Aufruf nicht mit einem speziellen Pfad verknüpft werden. Realisieren wir statt dessen eine Webseite, die aus mehreren Modulen wie z. B. Main, Produkte, Service besteht, dann wird man modulbezogene Informationen zumeist in lokalen Variablen speichern, da sie nur innerhalb eines speziellen Moduls von Bedeutung sind. Oft ist es auch notwendig, für Berechnungen nur zu diesem einen Zeitpunkt mit Variablen zu arbeiten, die
danach ohne Bedeutung sind. Lokale Variablen sind dann nur ein Hilfsmittel, um vorübergehend Daten aufzunehmen. Nachdem das Ergebnis der Berechnung feststeht, benötigt man sie nicht mehr. Im Gegensatz zu globalen Variablen müssen sie nicht explizit gelöscht werden, denn das übernimmt Flash automatisch und gibt so den belegten Speicherbereich frei. Eine globale Variable richtet man mit Hilfe des Schlüsselbegriffs _global ein: _global.gZahl = 111;
Damit ist diese Variable nicht nur von jeder Stelle innerhalb des Films, in dem sie eingerichtet wurde, abrufbar, sondern sie ist auch allen Filmen bekannt, die extern geladen werden. Sie besitzt selbst dann Gültigkeit, wenn die Funktion, innerhalb der wir sie einrichteten, bereits ausgeführt wurde. Leider lässt Flash es nicht zu, bei _global den Datentyp festzulegen. Der Zugriff erfolgt einfach über den Namen ohne Angabe eines Pfades bzw. zugehörigen Objektes: trace(gZahl);
Möglich ist auch: trace(_global.gZahl);
Das Gegenstück dazu ist die lokale Variable, mit denen wir in den früheren Beispielen gearbeitet haben: var nZahl:Number = 112;
Die Variable nZahl gilt, falls sie in einer Funktion deklariert wird, nur innerhalb dieser Funktion, bzw. falls sie innerhalb der Zeitleiste eines Objekts erstellt wird, nur solange, wie dieses Objekt existiert. Um den Unterschied zu verstehen, wollen wir uns ein konkretes Beispiel anschauen. 1. Erstellen Sie einen neuen Film. 2. Weisen Sie actions folgendes BildSkript zu: var nZahl1:Number = 100;
trace(„nZahl1: „+nZahl1); function zaehlen(){
var nZahl2:Number = 5;
trace(„nZahl2 in der Funktion: „+nZahl2); }
zaehlen();
trace(„nZahl2 außerhalb der Funktion: „+nZahl2);
7.3 Reichweite
Beim ersten und zweiten trace()-Befehl erhalten wir jeweils die korrekte Antwort (100 bzw. 5). Lediglich der dritte trace()-Befehl in der letzten Zeile ergibt ein undefined im Nachrichtenfenster. Wie bisher richten wir zunächst eine Variable nZahl1 ein und weisen ihr den Wert 100 zu. Es handelt sich hier um eine lokale Variable, da wir den Schlüsselbegriff var verwenden. Ihren Wert fragen wir in der nächsten Zeile ab. Anschließend definieren wir einen Code-Block als eine Funktion namens za ehlen(). Immer dann, wenn wir zaehlen() aufrufen, soll genau dieser Code-Block ausgeführt werden. Die Funktion sorgt dafür, dass eine zweite lokale Variable namens nZahl2 eingerichtet wird und den Wert 5 erhält. Direkt danach fragen wir nach ihrem Inhalt. In den beiden letzten Skriptzeilen wird die genannte Funktion aufgerufen bzw. ausgeführt, und danach erneut der Inhalt von nZahl2 angezeigt. Wir haben nZahl1 wie bisher eingerichtet, nämlich außerhalb einer Funktion mit Hilfe des Schlüsselbegriffs var. Dabei handelt es sich um eine Zeitleistenvariable, d. h. um eine Variable, die innerhalb der Zeitleiste, in der sie deklariert wurde, gilt. Sie existiert solange, wie diese Zeitleiste existiert. Wird die Zeitleiste gelöscht, verschwindet auch die Variable. Da sich die Variable in unserem Beispiel auf der Hauptzeitleiste bzw. _root befindet, kann sie von jeder Stelle des Films aus angesprochen werden. Würden wir sie ohne var einrichten, erhielten wir das gleiche Ergebnis. Solche Variablen stellen eine Besonderheit von Flash dar, zu denen in höheren Programmiersprachen insofern kein Pendant existiert, als diese nicht auf das Konzept der Zeitleisten zurückgreifen. Anders verhält es sich mit nZahl2. Sie wurde innerhalb der Funktion zaehlen() eingerichtet. Da wir dabei var verwenden, handelt es sich um eine lokale Variable, die nur innerhalb dieser einen Funktion bekannt ist. Die trace()-Methode in zaehlen() zeigt den korrekten Wert an, während ein trace() außerhalb der Funktion – egal, an welcher Stelle es erfolgt – immer ein undefined zurückgibt. 3. Fügen Sie in Frame 2 auf der Ebene actions ein Schlüsselbild ein und schreiben folgendes Skript: trace(nZahl1); trace(nZahl2); stop();
61
Erneut gibt Flash 100 für nZahl1 und undefined für nZahl2 aus. Wie erwähnt, kann man auf die Zeitleistenvariable zugreifen, die lokale Variable dagegen bleibt nach wie vor unbekannt. Aus der lokalen Variablen entsteht innerhalb der Funktion eine Zeitleistenvariable, wenn wir bei der Deklaration auf var verzichten. 4. Ändern Sie die Funktionsdeklaration in Frame 1 (Fettdruck): function zaehlen(){ nZahl2 = 5;
trace(„nZahl2 in der Funktion: „+nZahl2); }
Anstelle des undefined zeigt das Nachrichtenfenster nun die zugewiesenen Variablenwerte an, d. h. nZahl2 existiert nun ebenso lange wie nZahl1, obwohl wir sie innerhalb einer Funktion eingerichtet haben. Was für die Zeitleiste von _root gilt, besitzt prinzipiell für jeden anderen MovieClip, dem wir eine Variable zuordnen, Gültigkeit. 5. Löschen Sie den eben mühsam erstellten Frame 2. 6. Zeichnen Sie in objects an beliebiger Stelle einen Kreis (80 × 80) 7. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Instanzname kreis, Registrierungspunkt beliebig). 8. Fügen Sie vor der Deklaration von zaehlen() ein: kreis.nTempoX = 25;
9. Schreiben Sie am Ende des bisherigen Codes: trace(„Tempo: “+kreis.nTempoX);
Nach den bisherigen Angaben zeigt Flash den Inhalt der Variablen kreis.nTempoX an. Hier wird einer Instanz mit dem Namen kreis eine Zeitleistenvariable zugewiesen. Sie kann von jeder Stelle innerhalb des Flashfilms angesprochen werden, vorausgesetzt, die Instanz kreis existiert bereits zu dem Zeitpunkt, zu dem diese Codezeile ausgeführt wird. Und sobald wir kreis mit removeMovieClip() löschen, verschwindet automatisch auch die Variable nTempoX. Wollten wir mit var in kreis eine Zeitleistenvariable einrichten, müssten wir im Symbolbearbeitungsmodus des betreffenden MovieClips auf dessen Zeitleiste zugreifen, um dort ein Bildskript zu schreiben.
62
Kapitel 7 Variablen
Lokale Variablen lassen sich korrespondierend zur oben beschriebenen Vorgehensweise einrichten. 10. Ersetzen Sie das bisherige Skript durch:
kreis.ausgabe = function(){
var nZahl:Number = 5 trace(nZahl)
}
kreis.onPress = function(){
trace(nZahl)
die nur beim Zeitpunkt eines Funktionsaufrufs Gültigkeit besitzen. Sie ermöglichen es, Funktionen flexibler zu gestalten und werden im Kapitel über Funktionen ausführlicher behandelt. Bei Zählvariablen handelt es sich um gewöhnliche Variablen, die für einen besonderen Zweck, nämlich das Durchzählen von Schleifendurchläufen, verwendet werden. Wir besprechen sie im Kapitel Schleifen.
}
kreis.ausgabe()
Das Nachrichtenfenster zeigt den korrekten Wert von nZahl an. Wenn Sie jedoch auf den Kreis klicken, erhalten Sie ein undefined. Denn durch den Schlüsselbegriff var haben wir zum Zeitpunkt des Aufrufs von kreis.ausgabe() eine lokale Variable eingerichtet, die außerhalb der Funktion nicht mehr zur Verfügung steht. Die Verwendung von _global ist nicht unbedingt notwendig. Denn wenn wir den kompletten Code in der Hauptzeitleiste schreiben und im Falle modularer Anwendungen mit loadMovie() anstatt des weniger zu empfehlenden loadMovieNum() arbeiten, dann können wir jederzeit auf unsere Variablen ohne Pfad angabe zugreifen, solange der Code in der Hauptzeitleiste steht.
7.4 Parameter und Zählvariablen Zwei besondere Arten von Variablen stellen Parameter sowie Zählvariablen dar. Parameter sind Variablen,
7.5 Konstanten Trotz des Namens handelt es sich bei Konstanten ebenfalls um Variablen, aber eben um solche, die einmal Daten aufnehmen und diese unverändert während des Programmablaufs beibehalten. Dabei könnte es sich z. B. um die Angabe eines Ordners handeln, aus dem Bilder eingelesen werden müssen, oder einen String, den man benötigt, um daraus verschiedene Datei- oder Objektnamen zusammen zu setzen. Um Konstanten nicht versehentlich während des Programmierens mit wechselnden Werten zu füllen, sollte man auf eine Namenskonvention zurückgreifen wie etwa die Verwendung des Präfix sk (z. B. skPfad) oder von Majuskeln (z. B. PFAD). Leider haben Majuskeln den optischen Nachteil, eine enorme Signalwirkung zu besitzen, so dass sich solcher Code mitunter schwieriger lesen lässt. Flash kennt eine Reihe vordefinierter Konstanten: undefined, true, false, null und NaN, um die häufigsten zu nennen. Verschiedene Objekte wie die Array-Klasse verfügen ebenfalls über Konstanten. So kann man der sort()-Methode Konstanten wie NUMERIC als Parameter übergeben, was im Kapitel Array ausführlicher dargestellt wird.
8
Datentypen
An wiederholter Stelle war von sogenannten Datentypen die Rede, die wir beim Deklarieren der Variablen festlegen. Damit ist schlicht die Art der Daten gemeint, mit denen wir arbeiten wollen. Dieser Typ ist von entscheidender Bedeutung für den richtigen Umgang mit Daten. So macht es z. B. nur Sinn, eine Berechnung durchzuführen, wenn wir es mit Zahlen zu tun haben, wenn also der Datentyp Number vorliegt. Das entspricht unserem Alltagsverhalten und nicht anders agiert auch eine Programmiersprache. Flash kennt zahlreiche Datentypen, von denen wir uns an dieser Stelle nur die wichtigsten anschauen werden. Einige eher ausgefallene Typen begegnen uns an späterer Stelle in den Workshops. Flash unterscheidet zwischen Grunddatentypen wie Boolean, Number und String sowie komplexen oder Referenzdatentypen wie Object und MovieClip, die ihrerseits auf Grunddatentypen verweisen. Zu den komplexen Datentypen gehören auch selbst definierte Klassen, die neue Datentypen bilden. In folgender Tabelle sehen Sie eine Auflistung der häufigsten Datentypen. Datentyp
Art
Boolean
Grunddatentyp
Number String null
undefined
MovieClip Object Void
Häufige Datentypen
Grunddatentyp Grunddatentyp Grunddatentyp Grunddatentyp Referenzdatentyp Referenzdatentyp Referenzdatentyp
Verwenden Sie AS 1.0 und verzichten damit auf eine strikte Typisierung, versucht Flash, automatisch den korrekten Datentyp einer Variablen zu ermitteln, sobald ihr ein Wert zugewiesen wurde. Zudem findet eine automatische Typumwandlung statt, wenn dieser Variablen ein Wert mit einem anderen Datentyp zugewiesen wurde. In manchen Fällen ist eine temporäre Typumwandlung möglich. AS 2.0 erlaubt ebenfalls bei manchen Datentypen eine temporäre wie eine permanente Typumwandlung (Array, Boolean, Object, Number, String). Letztere macht allerdings prinzipiell wenig Sinn, da man sich ja ganz bewusst auf einen bestimmten Datentyp festgelegt hat. Benötigt man die gespeicherten Daten dennoch als anderen Datentyp – z. B. einen String als Number, um Berechnungen durchführen zu können –, dann empfiehlt sich eine temporäre Typumwandlung. Sie ist uns bereits oben über den Weg gelaufen. 1. Erstellen Sie eine Standarddatei. 2. Fügen Sie auf objects ein dynamisches Textfeld mit beliebigen Eigenschaften ein (Instanzname zahl). 3. Schreiben Sie in das Textfeld die Zahl 125. 4. Weisen Sie actions folgendes Bildskript zu: var nZahl1:Number = 50; var nSumme:Number = 0;
nSumme = zahl.text + nZahl1; trace(nSumme)
trace(typeof(nSumme));
Bei der Ausgabe im Nachrichtenfenster tun sich Abgründe auf, zeigt Flash doch an: 12550 string
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
63
64
Wir richten zwei Variablen mit dem Datentyp Number ein und weisen ihnen die Werte 50 und 0 zu. Anschließend überschreiben wir den Inhalt von nSumme durch eine (scheinbare) Addition des Inhalts unseres händisch angelegten Textfeldes und der ersten Variable. Wie im Kapitel zu den Operatoren gesehen interpretiert Flash hier den Ausdruck auf der rechten Seite als Konkatenation und schreibt infolgedessen die erwähnten Werte hintereinander anstatt eine Berechnung durchzuführen. Automatisch wird dabei der nSumme zugewiesene Datentyp Number durch den Datentyp String überschrieben, es erfolgt also eine permanente Typumwandlung. Wann immer wir in den nachfolgenden Zeilen innerhalb des Codes auf nSumme zugreifen, ist sie vom Datentyp String, wie der typeof-Operator in der letzten Zeile zeigt. Um das Skript korrekt auszuführen, benötigen wir jedoch eine temporäre, also zeitweise Typumwandlung, die sich nicht auf nSumme, sondern auf das Textfeld bezieht. 5. Ändern Sie die Zuweisung von nSumme (Fettdruck): nSumme = Number(zahl.text) + nZahl1; Ausgabe im Nachrichtenfenster: 175 number Die Berechnung wird korrekt durchgeführt und der gewünschte Datentyp Number bleibt für nSumme erhalten. Wir weisen Flash an, zu einem einzigen Zeitpunkt, der faktisch dem Bruchteil einer Sekunde entspricht, den Inhalt des Textfeldes zu nehmen und ihn in den Datentyp Number umzuwandeln. Dann erfolgt die Addition, deren Ergebnis unserer Variablen zugewiesen wird. Vor und nach dem Auslesen des Textfeldes bleibt der ursprüngliche Datentyp String erhalten. Auf diese Weise kommt es zu keiner unerwünschten permanenten Typumwandlung. Generell gilt, dass auf permanente Umwandlungen verzichtet werden soll. Sinnvoll sind statt dessen nur temporäre Umwandlungen, denen Sie hauptsächlich dann begegnen werden, wenn Sie wie in unserem Beispiel mit den Inhalten von Textfeldern arbeiten. AS 3.0 kennt als weitere Grunddatentypen int (Integer, d. h. Ganzzahlen) sowie uint (unsignierte Integer, also Zahlen ohne Bit-Wert für das Vorzeichen), die komplexe Berechnungen beschleunigen können. In AS 2.0 können wir nicht darauf zugreifen.
Kapitel 8 Datentypen
Beachten Sie, dass Grunddatentypen als Wert, nicht als Referenz, weiter gegeben werden. Wenn Sie also beispielsweise eine Reihe an Variablen mit verschiedenen Zahlenwerten anlegen und diese Variablen dann in einem Array aufnehmen, speichert Flash dort nicht wirklich die betreffenden Variablen, sondern nur deren konkreter Inhalt. Wenn Sie dann versuchen, über den Zugriff auf das Array die Variablen zu ändern, ignorieren diese ungerührt Ihre Anweisungen. Statt dessen werden nur die Werte im Array geändert.
8.1 Boolean Beim Bool’schen Datentyp handelt es sich um einen Wahrheitswert, der nur zwei mögliche Inhalte kennt: Wahr oder falsch bzw. in Flash-Syntax true oder false. Diese Werte lassen sich auch als Zahlen darstellen, wobei die 1 dem Wert true, die 0 dem Wert false entspricht. Ein Wahrheitswert sagt etwas darüber aus, ob ein Ausdruck zutrifft oder nicht zutrifft. Ausdrücke werden i. d. R. in if-Bedingungen oder in Schleifen auf ihren Wahrheitsgehalt getestet. Abhängig davon, ob eine Bedingung wie beispielsweise die Aussage, die Variable nZahl enthalte den Wert 12 oder die Variable sVorname enthalte nicht den String „Zaphod Beeblebrox“, zutrifft, wird eine bestimmte Aktion ausgelöst.
8.2 Number Während Programmiersprachen normalerweise zwischen floating point, integer und anderen Zahlen differenzieren, subsumiert sie ActionScript 2.0 unter einem einzigen Datentyp, nämlich Number. Positive oder negative Zahlen, Ganzzahlen oder Fließkommawerte gehören alle zu einem einzigen Datentyp. Das Schreiben erfolgt einfach durch Eingabe der gewünschten Zahl: 5
-17
220378 3.14
Und als Variablendeklaration z. B.: var nZahl:Number = 5;
8.2 Number
Beachten Sie, dass wir anders als im Deutschen üblich zur Abtrennung der Tausenderstellen keinen Punkt verwenden dürfen, was große Zahlen recht unleserlich macht. Syntaktisch gesehen wäre also falsch: var nAnzahl:Number = 800.000;
var sReichtum:String = „In Deutschland gibt es rund „ + nAnzahl + “ DollarMillionäre“;
Unter krasser Missachtung der tatsächlichen Reichtumsverhältnisse würde Flash schlicht bescheidene 800 Millionäre vermuten, da die angegebene Zahl als Fließkommawert und der Punkt als Kommastelle interpretiert wird. Korrekt würde der String ohne Punkt in der Werteangabe initialisiert: var nAnzahl:Number = 800.000;
Unter den angeführten Beispielen befanden sich sowohl Ganzzahlen bzw. Integer (positive Werte, negative Werte jeweils ohne Nachkommastelle, 0) wie auch Fließkommawerte bzw. Gleitkommazahlen (Werte mit Nachkommastellen). Der Einsatzzweck von Ganzzahlen dürfte selbstredend sein: Wenn Sie Objekte zählen, auf eine Frame-Nummer zugreifen oder wissen wollen, an welcher Stelle innerhalb eines Arrays die benötigten Daten liegen, arbeiten Sie notwendigerweise mit Integer. Fließkommawerte dagegen kommen oft in Berechnungen vor (Kreisumfang, Entfernung etc.), aber selbst bei alltäglichen Problemen wie Preisen spielen sie eine bedeutende Rolle. Nicht immer liegen die benötigten Zahlen als Number vor. Das gilt beispielsweise, wenn Sie Ihre Daten extern laden, oder der Anwender die Werte in ein Eingabetextfeld schreibt. In den Fällen müssen die Werte in den Datentyp Number konvertiert werden, was mit Hilfe der oben vorgestellten temporären Typ umwandlung erfolgt. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var sZahl:String = „10“;
var nZahl:Number = Number(sZahl); trace(nZahl + 5);
Ausgabe im Nachrichtenfenster: 15 Wir deklarieren die Variable sString als String und weisen ihr die Zeichenkette „10“ zu. Wohlgemerkt: Der zugewiesene Wert wird von Flash nicht
65
als Zahl, sondern als Text gelesen. Anschließend legen wir eine zweite Variable an, diesmal jedoch mit dem Datentyp Number. Ihr weisen wir den Inhalt von sZahl zu, den wir dabei als Number, also Ganzzahl, auslesen. Mit Hilfe des trace()-Befehls lassen wir uns eine Berechnung ausgeben, bei der zur zweiten Variable ein Wert hinzu addiert wird. Was wir vorher mit dem Inhalt eines Textfeldes durchgeführt haben, geschieht nun mit einer Variablen: Temporär, nur für den kurzen Augenblick des Zugriffs, erfolgt eine Typ umwandlung von einem String in eine Number, so dass eine sinnvolle Berechnung möglich wird. Eine genauere Kontrolle über die Typumwandlung bieten Ihnen die globalen Funktionen parseInt() und parseFloat(). 3. Ändern Sie die Initialisierung von sZahl wie folgt (Fettdruck): var sZahl:String = „10.25“;
4. Erweitern Sie den Code um folgende Zeilen: trace(„Integer: „ + parseInt(sZahl)); trace(„Gleitkomma: „ + parseFloat(sZahl));
Ausgabe im Nachrichtenfenster: 15.25 Integer: 10 Gleitkomma: 10.25 In sZahl speichern wir eine Zeichenkette, die eine Zahl mit zwei Nachkommastellen enthält. Wenn wir die Initialisierung von nZahl gegenüber zuvor unverändert durchführen, dann liest Flash (in den meisten Fällen) den String korrekt als Fließkommazahl aus und führt die Berechnung durch, die jetzt 15.25 ergibt. Mit den Funktionen parseInt() und parse Float() können wir darüber hinaus genau festlegen, ob wir eine Fließkomma- oder Ganzzahl erhalten möchten. Die parseInt()-Funktion gibt uns eine Ganzzahl, also 10, während die parseFloat()Funktion eine Fließkommazahl generiert, also 10.25. Beide Funktionen bieten zusätzlich die Möglichkeit an, die Zahlenbasis zu definieren. Das Zahlensystem, mit dem Sie im Alltag umgehen, beruht auf der Zahlenbasis 10, weswegen man es auch als Dezimalsystem bezeichnet. Es verwendet die Zeichen 0 – 9. Eine beliebige Zahl lässt sich darstellen, indem man jede ihrer Ziffern mit der Basis 10 multipliziert. Andere insbesondere im Bereich der Medienproduktion
66
Kapitel 8 Datentypen
verwendete Zahlensysteme sind das binäre mit der Basis 2 und den Ziffern 0 und 1 sowie das hexadezimale mit der Basis 16 und den Ziffern 0 – 9 und a – f. Wen wir eine derartige Zahl aus einem String auslesen und in das Dezimalsystem umwandeln wollen, bietet parseInt() gegenüber Number() einen deutlichen Vorteil.
Ob überhaupt eine Umwandlung in eine gültige Zahl möglich ist, hängt naturgemäß vom vorliegenden String ab.
5. Erweitern Sie das Skript um folgende Zeilen:
trace(„parseInt: „ + parseInt(sZiffern));
var sHex:String = „0xff“;
trace(„Hexadezimal mit Number: „ + Number(sHex));
trace(„Hexadezimal mit parseInt: „ + parseInt(sHex,16));
Ausgabe im Nachrichtenfenster : Hexadezimal mit Number: 255 Hexadezimal mit parseInt: 255 Wir legen in sHex einen Hexadezimalwert ab, der in Flash mit einem vorangestellten 0x kenntlich gemacht wird. Wenn wir diesen Wert mit Number() und mit parseInt() auslesen, erhalten wir in beiden Fällen den gleichen, korrekten Dezimalwert. Bei der parseInt()-Funktion übergeben wir als Argument neben dem zu verwertenden String die Zahlenbasis 16 für Hexadezimalzahl, so dass eine Umrechnung erfolgen kann. Da Hexadezimalzahlen nicht zwangsläufig mit einem Präfix versehen werden – was erst recht gilt, wenn die Daten aus anderen Applikationen stammen –, werden sie in unserem Fall verschieden behandelt. 6. Ändern Sie die Initialisierung von sHex folgendermaßen (Fettdruck): var sHex:String = „ff“;
Ausgabe im Nachrichtenfenster : Hexadezimal mit Number: NaN Hexadezimal mit parseInt: 255 Durch das Löschen des Präfixes für Hexadeziamlwerte erkennt Number()die Zahlenbasis nicht mehr und kann keine korrekte Umrechnung vornehmen. Daher erhalten wir die Ausgabe NaN. Die parseInt()Funktion dagegen kann nach wie vor den entsprechenden Dezimalwert ermitteln. Übrigens: Sollten Sie testweise versuchen, an Number beim Aufruf ebenfalls eine Zahlenbasis zu übergeben, erhalten Sie eine Fehlermeldung, da nur genau ein Argument, nämlich das auszuwertende Element, zulässig ist.
7. Erweitern Sie das Skript um folgende Zeilen: var sZiffern:String = „5 Euro“;
trace(„Number: „ + Number(sZiffern));
Ausgabe im Nachrichtenfenster: Number: NaN parseInt: 5 Wir definieren zunächst einen String, den wir auswerten wollen. Anschließend versuchen wir, daraus mit Number() die enthaltene Zahl, hier die 5 als beispielhafte Prognose für den Benzinpreis 2009, zu extrahieren. Da Number() keine nicht-numerischen Zeichen (mit Ausnahme des erwähnten Präfix 0x) auswerten kann, erhalten wir die Rückmeldung NaN. Die parseInt()-Funktion kann Zahlen erkennen, solange ihnen kein nicht-numerisches Zeichen vorausgeht. Daher gibt uns das Nachrichtenfenster den korrekten Wert 5 aus. 8. Ändern Sie die Initialisierung von sZiffern wie folgt (Fettdruck): var sZiffern:String = „Mehr als 5 Euro“;
Ausgabe im Nachrichtenfenster: Number: NaN parseInt: NaN Da der auszuwertende String mit einem nicht-numerischen Zeichen beginnt, kann auch die parseInt()Funktion keinen korrekten Wert mehr extrahieren und meldet wie Number() resignierend NaN. Bevor Sie mit als Zahlen benötigten, aber als String vorliegenden Daten arbeiten, sollten Sie sich daher immer vergewissern, dass der korrekte Datentyp generiert werden kann. Ansonsten kann es geschehen, dass eine fehlerhafte Berechnung durchgeführt wird, während Flash ganz verzweifelt versucht, Ihnen ein NaN mitzuteilen. Mit der globalen Funktion isNaN() besteht wie schon an anderer Stelle gezeigt die Möglichkeit, ganz gezielt zu ermitteln, ob ein Ausdruck einen mathematisch gültigen Wert ergibt.
8.4 Null, undefined
9. Erweitern Sie den Code um folgende Zeile: trace(„Keine Zahl? „ + isNaN(sZiffern));
Ausgabe im Nachrichtenfenster: Keine Zahl? true Die Funktion isNaN() kontrolliert, ob das übergebene Argument, hier der in sZiffern gespeicherte String, keine Zahl darstellt. Das Ergebnis kann true (keine Zahl) oder false (Zahl) lauten. In unserem Fall erhalten wir true, denn der String enthält nicht-numerische Zeichen und bildet somit keine verwertbare Zahl. Bitte beachten Sie, dass der Rückgabewert true bedeutet, dass keine Zahl vorliegt – gerade für Einsteiger erweist sich diese Funktion mitunter als etwas verwirrend. 10. Ändern Sie den String wieder in die ursprüngliche Variante (Fettdruck):
var sZiffern:String = „5 Euro“;
Ein Test ergibt keine Änderung gegenüber vorher, denn nach wie vor enthält der abgefragte String nichtnumerische Zeichen. 11. Ändern Sie die letzte trace()-Anweisung wie folgt (Fettdruck): trace(„Keine Zahl? „ + isNaN(parseInt(sZiffern)));
Ausgabe im Nachrichtenfenster: Keine Zahl? false Diesmal greifen wir nicht direkt auf den String zu, sondern fragen ab, ob das Ergebnis von parseInt(sZiffern) keine Zahl darstellt. Da wir, wie oben gesehen, mit parseInt() in diesem Fall sehr wohl eine gültige Zahl erhalten, gibt die isNaN()-Funktion false zurück, d. h. es trifft nicht zu, dass keine Zahl extrahiert werden kann. Um weitergehende Berechnungen mit dem Datentyp Number durchführen zu können, stehen einerseits zahlreiche Operatoren (insbesondere die arithmetischen – s. Kapitel Operatoren) sowie die Math-Klasse (s. Kapitel Math) zur Verfügung.
67
8.3 String Unter einem String versteht man ein einzelnes Zeichen oder eine beliebige Folge (Kette) von alphanumerischen Zeichen. Um einen String von anderen Datentypen zu unterscheiden, wird er mit einem einfachen oder doppelten Anführungszeichen eingeleitet und abgeschlossen: „a“
‘Hallo’
„Das ist ein längerer String. Er besteht aus zwei Sätzen.“ “.“
„1001“ ‘%&§ ’
Wichtig ist, dass das endende Anführungszeichen dem beginnenden entsprechen muss. Leiten Sie den String mit einem einfachen Anführungszeichen ein, können Sie nicht mit einem doppelten schließen. Neben Zahlen spielen Strings eine zentrale Rolle. Weitergehende Informationen finden Sie insbesondere im Kapitel zu den Operatoren (Konkatenation), zur String-Klasse sowie zu Textfeldern. Eine temporäre Typumwandlung wurde oben im Zusammenhang mit dem Konkatenationsoperator vorgestellt, als die Daten aus einem Textfeld vorübergehend, nämlich für den Augenblick der Zuweisung, mit Number(eingegebenerString) in eine Zahl umgewandelt wurden. Weitere Informationen finden Sie im Kapitel zur String-Klasse.
8.4 Null, undefined Der Datentyp null zeigt an, dass keine konkreten Daten vorhanden sind. Ruft man beispielsweise eine Methode auf, die mehr Parameter erwartet, als man übergibt, kann man null an der Stelle der nicht definierten Parameter verwenden. Dieser Datentyp besitzt nur einen einzigen Wert, nämlich den gleichnamigen Wert null. Der Datentyp undefined ist – prosaisch formuliert – ein enger Verwandter des Datentyps null. Auch er besagt dass Daten nicht vorhanden sind, wird aber anders als null von Flash automatisch Objekten zugewiesen, die nicht deklariert oder initialisiert wur-
68
den. So gilt eine nicht existente Variable ebenso wie eine nicht initialisierte Variable als undefined. Der Datentyp undefined besitzt als einzigen Wert undefined. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: trace(„nZahlen: “+nZahlen);
Ausgabe im Nachrichtenfenster: nZahlen: undefined Da es keine Deklaration von nZahlen gibt, ist dieses Objekt unbekannt, so dass Flash undefined zurückgibt. 3. Fügen Sie vor dem trace() ein: var nZahlen:Number;
Ausgabe im Nachrichtenfenster: nZahlen: undefined Das Ergebnis im Nachrichtenfenster ist das gleiche, da nZahlen zwar als Variable mit einem Datentyp angelegt wurde, aber noch über keinen konkreten Wert verfügt. Beachten Sie, dass die Deklaration bereits Einfluss auf den Arbeitsspeicher nimmt, auch wenn ein konkreter Wert als Inhalt noch fehlt. Alleine durch die Deklaration wird bereits Speicher belegt. Als Hinweis darauf, dass eine Variable bereits korrekt deklariert wurde, aber noch keinen gültigen Wert besitzt, verwenden Entwickler gerne eine Initialisierung mit dem Datentyp null:
Kapitel 8 Datentypen
8.6 Object Unter einem Object versteht man schlicht eine Sammlung an Eigenschaften, die ein wie auch immer geartetes „Etwas“ beschreiben. Da die Eigenschaften selbst einem beliebigen Datentyp – und damit auch dem Datentyp Object – angehören können, ist ein Object beliebig verschachtelbar. Das Object ist gewissermaßen die Mutter vieler anderer Klassen und damit anderer Datentypen wie etwa des erwähnten MovieClip. Zugriff auf die Objekteigenschaften erhalten wir durch den Punktoperator. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var oAuto:Object = new Object(); oAuto.tueren = 2; oAuto.raeder = 4;
trace(„Das Auto besitzt „+oAuto.raeder+“ Räder, „+oAuto.tueren+“ Türen.“);
Ausgabe im Nachrichtenfenster: Das Auto besitzt 4 Räder, 2 Türen.
var nZahlen:Number = null;
Wir haben ein Objekt mit spezifischen Eigenschaften eingerichtet. Man kann diese Deklaration noch erweitern, indem man dem Object Funktionen (Methoden) und Ereignisse zuweist. Das Objekt stellt beispielsweise eine ideale Möglichkeit dar, um Eigenschaften zu speichern und sie bei Bedarf auf visuell wahrnehmbaren Elementen wie einem MovieClip zu übertragen.
8.5 MovieClip
8.7 Void
Als einziger Datentyp ist der MovieClip mit einem grafischen Element verknüpft. Dies und die Tatsache, dass keine in AS 2 geskriptete Flash-Anwendung ohne MovieClips auskommt, macht ihn zu einem zentralen Datentyp. Er wird ausführlich im Kapitel MovieClips dargestellt und taucht in jedem der im zweiten Teil behandelten Workshops auf.
Wie die Datentypen null und undefined kennt dieser Datentyp nur einen einzigen Wert, nämlich Void. Er findet in Funktionsdeklarationen Verwendung, die keinen Rückgabewert besitzen. Eine solche Definition könnte so aussehen: function ohneRueckgabe():Void{ //Anweisungen; }
Das sieht momentan vielleicht etwas abstrakt aus, wird aber konkreter, wenn Sie einen Blick in das Kapitel Funktionen und Methoden werfen.
9
Arrays
Wie wir gesehen haben, speichern wir in einer Variablen einen Wert, um ihn später wiederverwenden zu können. Doch was geschieht, wenn wir nicht nur einen, sondern mehrere Werte speichern müssen? Für diese Fälle bietet Flash mit dem Array einen eigenen Datentyp an, der als eine Art Variable mit theoretisch unendlich viel Speicherplatz fungiert. In AS 2.0 ist es nicht möglich, wie bei Variablen einen Datentyp vorzugeben, da das Array selbst als Datentyp gilt, der einer Variablen zugewiesen wird.
9.1 Arrays einrichten und die Länge definieren Ein Array lässt sich mit dem Operator new definieren: var aNamen:Array = new Array();
Wir legen eine Variable mit dem Datentyp Array an. Durch den verwendeten Konstruktor erzeugen wir eine neue Objektinstanz und initialisieren alle Eigenschaften, die zur Klasse Array gehören. Da wir noch keinen konkreten Inhalt zugewiesen haben, stellt aNamen nichts weiter als eine leere Kiste undefinierter Größe dar. Wissen wir von Anfang an, wie viele Elemente aufgenommen werden sollen, können wir beim Erstellen die Größe angeben: var aNamen2:Array = new Array(10);
Nun haben wir ein Array angelegt, das über zehn leere Plätze zur Aufnahme von Werten verfügt. Den Unterschied zwischen beiden Array-Definitionen können wir über die wichtigste Eigenschaft, die ein Array besitzt, näher betrachten, nämlich die Gesamt-
länge bzw. Array.length. Die Länge entspricht der Anzahl der Elemente eines Arrays. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var aNamen:Array = new Array(); trace(„Länge aNamen: ”+aNamen. length);
Sie erhalten im Nachrichtenfenster die Ausgabe 0. Wir haben zwar ein Array angelegt, ihm aber weder konkrete Inhalte zugewiesen noch die Gesamtlänge definiert. Daher besitzt es die Länge 0. 3. Erweitern Sie das Skript: var aNamen2:Array = new Array(10); trace((„Länge aNamen2: ”+aNamen2. length);
Die zweite Ausgabe im Nachrichtenfenster lautet 10. Auch hier ist unser Array noch leer, doch geben wir diesmal die Gesamtlänge mit 10 vor. Für die Ausgabe spielt es keine Rolle, ob diese Stellen mit konkreten Werten belegt oder nur zur Aufnahme von Werten eingerichtet wurden. Denn wir greifen mit der Eigenschaft length auf die Größe anstatt auf den Inhalt eines Arrays zu. Die Größenangabe übrigens bedeutet keine unveränderliche Limitierung. Ein Array, dessen Länge wir beim Einrichten beispielsweise auf 10 festgelegt haben, kann zu jedem beliebigen Zeitpunkt mehr Elemente aufnehmen, falls erforderlich. Flash kennt eine alternative, etwas kürzere Schreibweise, um Arrays einzurichten. 4. Fügen Sie folgende Definition von aZahlen hinzu: var aZahlen:Array = [];
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
69
70
trace((„Länge aZahlen: ”+ aZahlen. length);
Anstelle des Konstruktors new Array() reicht die Verwendung von eckigen Klammern, um ein neues Array mit der Länge 0 einzurichten. Allerdings können wir bei dieser Vorgehensweise keine Gesamtlänge angeben. Dazu müssen wir explizit die Eigenschaft length ansprechen. 5. Ergänzen Sie nach der Array-Definition und vor dem trace()-Befehl: aZahlen.length = 6; trace(aZahlen);
trace(aZahlen.length);
Entsprechend unserer Vorgabe setzt Flash die Länge mit 6 fest und gibt diesen Wert aus. Da das Array jedoch keine konkreten Inhalte enthält, taucht im Nachrichtenfenster sechsmal die Angabe undefined auf.
9.2 Arrays bei der Deklaration füllen Natürlich lässt sich bei der Einrichtung ein konkreter Inhalt mitgeben. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var aNamen:Array = new Array(„Rince wind“,„Herrena“,„Alter Großvater“);
Die Gesamtlänge des Arrays aNamen beträgt 3. Darüber hinaus besitzt es einen konkreten Inhalt, bestehend aus drei Elementen, die in kommaseparierter Form übergeben werden. Die Elemente sind vom Datentyp String (und stammen aus der Scheibenwelt, aber das ist in diesem Universum an dieser Stelle nebensächlich). Eine gleichzeitige Angabe von Inhalt und Länge ist nicht möglich, da Flash aufgrund des unvermeidlichen Kommas, das zur Trennung der Argumente verwendet wird, von einer Definition des Inhalts ausgeht. Das können wir testen, indem wir wahlweise an erster oder letzter Stelle innerhalb der Klammern eine Zahl mitgeben. 3. Ändern Sie die Deklaration von aNamen folgendermaßen (Fettdruck): var aNamen:Array = new Array(7,„Rince wind“,„Herrena“,„Alter Großvater“);
Kapitel 9 Arrays
Trotz der Zahl 7 gibt das Nachrichtenfenster die Länge des Arrays mit 4 an, bestehend aus der eigentlich für die Größendefinition gedachten 7 sowie den drei Namen. 4. Löschen Sie wieder die 7, so dass die Definition von aNamen derjenigen aus Schritt 2 entspricht. Eine Längenangabe ist also nur möglich, wenn bei der Verwendung des Operators new ein einziges Argument, bestehend aus einer positiven Ganzzahl, übergeben wird. Das bedeutet allerdings zugleich, dass Sie auf diese Weise kein Array einrichten können, das als anfänglichen Wert eine einzige Zahl enthält. Auch die alternative Schreibweise lässt es zu, das Array bereits bei der Deklaration zu füllen. 5. Um konkrete Inhalte zuzuweisen, schreiben Sie: var aZahlen:Array = [5,7,9,11,13,15];
6. Ergänzen Sie danach einen trace()-Aufruf: trace(„aZahlen: “+aZahlen+ „, Länge: “+aZahlen.length);
Flash gibt im Nachrichtenfenster sowohl die konkreten Inhalte, also die zugewiesenen Zahlen, wie auch die Gesamtlänge 6 an. Wir werden im Nachfolgenden hauptsächlich mit dieser Form der Deklaration arbeiten, da sie derzeit noch die üblichere Schreibweise darstellt.
9.3 Zugriff auf Inhalte Wenn wir Arrays verwenden, dann reicht es nicht aus, sie nur zu füllen. Sinn machen sie erst, wenn wir auch wieder auf die Daten zugreifen können. Die einfachste Variante entspricht derjenigen, die wir von Variablen her kennen und oben bereits verwendet haben. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var aZahlen:Array = [5,7,9,11,13,15]; var aNamen:Array = [„Rincewind“,„Herrena“,„Alter Groß vater“]; trace(„aZahlen enthält folgende Elemente: “+aZahlen); trace(„aNamen enthält folgende Elemente: “+ aNamen);
9.3 Zugriff auf Inhalte
Flash listet im Nachrichtenfenster alle Zahlen bzw. alle Namen auf, die wir zugewiesen haben. Damit lässt sich noch relativ wenig anfangen. Denn wirklich hilfreich ist ein Array nur durch das gezielte Auslesen eines einzelnen Werts. Alle Elemente innerhalb eines Arrays liegen auf einer eindeutig definierten Position, wobei das erste Element dem Index 0 entspricht. Über diesen Index sind wir in der Lage, beliebig auf Werte zuzugreifen. 3. Schreiben Sie am Ende des bisherigen Codes: trace(„Erstes Element in aZahlen: “+aZahlen[0]);
Im Nachrichtenfenster erhalten wir als erstes Element die Zahl 5. Der Indexzugriff erfolgt über eine eckige Klammer, in die wir diejenige Positionsnummer eintragen, die wir auslesen wollen. Dabei spielt es selbstverständlich keine Rolle, welchen Datentyp die im Array gespeicherten Werte besitzen. 4. Schreiben Sie am Ende des bisherigen Codes: trace(„Erstes Element in aNamen: “+ aNamen [0]);
Das Nachrichtenfenster gibt den zauberhaften Namen „Rincewind“ an. Wenn wir mit 0 auf das erste Element eines Arrays zugreifen, dann lautet der Index des letzten Elements Array-Länge minus 1. 5. Schreiben Sie am Ende des bisherigen Codes: trace(„Letztes Element in aNamen: “+ aNamen [aNamen.length-1]);
Im Nachrichtenfenster (und zum Glück nur dort) taucht der Troll „Alter Großvater“ auf. Die Länge des Arrays aNamen beträgt 3, denn es enthält die drei Strings Rincewind, Herrena und Alter Großvater. Der erste String liegt auf Indexposition 0, wie wir gesehen haben. Der zweite besitzt dann Index 1 und der dritte Index 2, was der Gesamtlänge 3 minus 1 entspricht, wie Abbildung 12 verdeutlicht.
71
Um den vollständigen Inhalt eines Arrays elementweise auszulesen, müssen wir eine Schleife verwenden, deren Zählvariable wir bequemerweise für den Indexzugriff einsetzen können. 6. Ergänzen Sie den bisherigen Code am Ende: for(var i:Number = 0; i < aNamen. length; i++){ trace(„Element an Index „+i+“: „+ aNamen[i]); }
Anders als bei dem einfachen Auslesen mit dem trace()-Befehl aus Schritt 11 erhalten wir jetzt eine zeilenweise Auflistung jedes einzelnen Elements. Denken Sie daran, als ersten Index die 0 und als letzten die Arraylänge minus 1 zu verwenden. Letzteres geschieht automatisch, indem wir i als kleiner aNa men.length definieren. Sie werden sich sicher fragen, warum man für den Zugriff auf den letzten Index die Formulierung i < aNamen.length anstatt der konkreten 2 verwenden sollte. Die dynamische Angabe mit Hilfe der Arraylänge besitzt gegenüber der konkreten Zahl den unschätzbaren Vorteil, dass sie in jedem Fall funktioniert, also auch dann, wenn wir die Gesamtlänge des Arrays nicht kennen oder wenn wir die Länge durch Einfügen bzw. Löschen einzelner Elemente ändern. Selbstverständlich kann der Zugriff auch mit Hilfe der anderen Schleifen erfolgen, z. B. mit der for inSchleife. 7. Ersetzen Sie den Schleifenaufruf im vorherigen Schritt durch: for (var e:String in aNamen) {
trace(„Element an Index „+e+“: „+ aNamen[e]); }
Beachten Sie, dass die for in-Schleife beim letzten Element beginnt. Wenn die Reihenfolge, in der die Elemente ausgelesen werden, eine Rolle spielt, könnte diese Schleife problematisch sein. Alternativ kann die while-Schleife verwendet werden. 8. Ersetzen Sie den Schleifenaufruf im vorherigen Schritt durch:
Abbildung 12: Arraylänge, -index und -inhalte
var i:Number = 0;
while(i < aNamen.length){
72
trace(„Element an Index „+i+“: „+ aNamen[i]); i++; }
Am häufigsten ist die erste Variante, also die forSchleife.
9.4 Arrays dynamisch füllen Oft wissen wir nicht, welche Inhalte ein Array enthalten soll. Wenn wir beispielsweise userabhängig die besuchten Seiten unserer Website erfassen wollen, um eine Art Verlauf darstellen zu können, oder wenn wir Highscores speichern, dann sind unsere Arrays anfangs notwendigerweise leer. Flash stellt mehrere Möglichkeiten zur Verfügung, um komfortabel sukzessive Elemente in Arrays einzufügen. Der Operator [] ermöglicht neben dem Auslesen auch das gezielte Setzen von Elementen an einer bestimmten Position. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var aNamen:Array = [„Rincewind“,„Herrena“,„Alter Großva ter“]; trace(„aNamen: “+ aNamen); aNamen[1] = „Trymon“;
trace(„aNamen: “+ aNamen);
Zunächst erhalten Sie im Nachrichtenfenster wie zuvor die Ausgabe „Rincewind, Herrena, Alter Großvater“. In der zweiten Zeile dagegen lautet die Ausgabe „Rincewind, Trymon, Alter Großvater“. Wir haben das Element auf Index 1, also das Element an zweiter Stelle, durch den String Trymon ersetzt. Hätten wir das erste Element ersetzen wollen, wäre die Indexnummer 0, und beim letzten Element aNamen. length-1 gewesen. Was geschieht aber, wenn wir bei einer Zuweisung eine Indexposition verwenden, die nicht existiert, weil sie über die Gesamtlänge des angesprochenen Arrays hinausgeht? 3. Ändern Sie den Arrayzugriff durch folgenden Indexwert (Fettdruck): aNamen[6] = „Trymon“;
Kapitel 9 Arrays
Wie Sie im Nachrichtenfenster sehen können, ergänzt Flash einfach die fehlenden Indexpositionen 3 bis 6. Auf den Positionen 3 bis 5 liegen keine Elemente vor, Flash gibt also undefined aus, während auf der letzten Position der String „Trymon“ eingefügt wird. Ähnlich lässt sich auch die Methode splice(), das Schweizer Universalmesser der Array-Methoden, einsetzen, wie wir weiter unten im Zusammenhang mit dem Löschen näher besprechen werden. Da es häufig vorkommt, dass wir am Ende eines Arrays neue Elemente einfügen wollen, verfügt Flash über eine eigene Methode. 4. Ersetzen Sie den bisherigen Code durch: var aAutoren:Array = [„Terry Prat chett“]; trace(„aAutoren: “+ aAutoren);
aAutoren.push(„Robert Rankin“); trace(„aAutoren: “+ aAutoren);
Die erste Ausgabe zeigt nur den bei der Initialisierung mit gegebenen String an, während die zweite Ausgabe zusätzlich den über push() eingefügten String enthält. Mit jedem weiteren push() wird das Array um das betreffende, an letzter Stelle eingefügte Element verlängert. 5. Fügen Sie einen weiteren push()-Befehl ein: aAutoren.push(„Douglas Adams“); trace(„aAutoren: “+ aAutoren);
Erneut hängt Flash an das bestehende Array den zuletzt benannten String an. Die push()-Methode lässt es zu, mehrere Elemente auf einmal anzuhängen. 6. Erweitern Sie den bisherigen Code: aAutoren.push(„Tom Holt“,„Esther Friesner“); trace(„aAutoren: “+ aAutoren);
Der Reigen von Autoren erweitert sich um die mit der erneuten push()-Methode übergebenen Personen. Im Gegensatz zu push() können wir mit un shift() jeweils an erster Stelle ein oder mehrere neue Elemente einfügen. 7. Erweitern Sie den bisherigen Code: aAutoren.unshift(„Craig Shaw“,„Robert Asprin“); trace(aAutoren);
9.4 Arrays dynamisch füllen
Alle Elemente im Array werden um zwei Stellen verschoben, und an den ersten beiden Stellen befinden sich nun die Strings „Craig Shaw“ und „Robert Asprin“. Sowohl die push()- wie die unshift()-Methode geben die neue Länge des Arrays zurück, so dass man diese, falls benötigt, direkt beim Aufruf einer der Methoden ermitteln kann. 8. Ändern Sie den bisherigen Code (Fettdruck): trace(aAutoren.unshift(„Craig Shaw“,„Robert Asprin“));
Das Nachrichtenfenster gibt zusätzlich zu den bisherigen Informationen die Gesamtlänge des neuen Arrays mit 7 an. Wollen wir den Inhalt eines bestehenden Arrays in ein anderes kopieren oder mehrere Arrays in einem neuen Array zusammenführen, können wir die concat()-Methode verwenden. 9. Erweitern Sie den bisherigen Code: var aAutorenKopie:Array = aTitel.con cat(); trace(„aAutorenKopie: „+ aAutoren Kopie);
Wie im Nachrichtenfenster zu sehen, stellt aAu torenKopie eine vollständige Kopie von aAuto ren dar. Beide Arrays sind unabhängig voneinander, so dass nachfolgende Änderungen in aAutoren keine Auswirkungen auf seinen Zwilling haben. Die concat()-Methode erlaubt es, während des Kopierens weitere Elemente als Argument zu übergeben. 10. Ändern Sie den bisherigen Code (Fettdruck): var aAutorenKopie:Array = aAutoren. concat(„John Lucas“,„John Brosnan“);
Die Kopie wird gegenüber dem Original um die beiden an concat() als Parameter übergebene Strings erweitert. Dabei fügt Flash die neuen Elemente am Ende des Arrays ein. Als Parameter können auch komplette Arrays übergeben werden, die anders als etwa bei der push()-Methode in Form kommaseparierter Elemente in das neue Array übernommen werden. Besteht der Parameter aus einem mehrdimensionalen Array, erfolgt allerdings keine Trennung in kommaseparierte Elemente. Wollen Sie Elemente eines Arrays an beliebiger Stelle in ein anderes Array kopieren, bietet sich die slice()-Methode an.
73
11. Ersetzen Sie die im vorigen Schritt verwendete concat()-Methode durch: var aAutorenKopie:Array = aAutoren. slice(2,4);
Wie im Nachrichtenfenster zu sehen, enthält das neue Array alle Strings der Indexpositionen 2 bis 4 von aAutoren. Das Array, aus dem kopiert wird, bleibt dabei unverändert. Der erste Parameter definiert den Beginn, der zweite das Ende des Kopiervorgangs, jeweils übergeben als Indexposition. Da sich, wie wir im nächsten Abschnitt noch sehen werden, die slice()Methode mit der fast gleichlautenden splice()-Methode emulieren lässt, kann man im Einzelfall durchaus auf sie verzichten. Arrays sollten Werte gleichen Datentyps enthalten. Zwar ist es möglich, die Inhalte kunterbunt zu mischen, aber dann laufen wir Gefahr, bei der Verwendung der Inhalte Fehler zu machen, z. B. weil wir versuchen, mit den Elementen Berechnungen durchzuführen, obwohl wir Zahlen und Strings gespeichert haben. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript: var aInhalt:Array = [„Text“,5,true]; trace(aInhalt);
Das Anlegen des gemischten Arrays nimmt Flash klaglos hin und das Nachrichtenfenster zeigt alle Elemente an. 3. Erweitern Sie das Skript: for(var i:Number = 0; i < aInhalt. length; i++){ trace(aInhalt[i] * 2); }
Beim Testen gibt Flash nach dem korrekten Inhalt des Arrays NaN, 10, 2 aus. Das Ergebnis NaN erhalten wir bei dem Versuch, den String Text mit einer Zahl zu multiplizieren, was natürlich nicht funktionieren kann. Der darauf folgende Wert 10 ist korrekt, während die 2 vielleicht überrascht, schließlich haben wir im Array an der letzten Stelle einen Bool’schen Wert, nicht jedoch eine Zahl stehen. Da sich true und false auch mit 1 und 0 darstellen lassen, führt Flash eine automatisch Typkonvertierung durch und ermöglicht eine Rechnung mit dem Wert true. Wir haben also nur in einem einzigen Fall einen gültigen,
74
in einem Fall einen völlig falschen und in einem Fall einen scheinbar richtigen Wert erhalten, der im späteren Verlauf Folgefehler provozieren kann. Sollten Sie dennoch mit verschiedenen Datentypen arbeiten müssen, z. B. weil sich die benötigten Informationen aufeinander beziehen (Namens- und Altersangaben etc.), so bietet sich die Verwendung von parallelen oder Tandem-Arrays an. Damit sind einfach verschiedene Arrays gemeint, die an übereinstimmenden Array-Positionen miteinander verknüpfte Werte speichern. 4. Ersetzen Sie den bisherigen Code durch: var aNamen:Array = [„Peter“,„Ulla“, „Severin“,„Johannes“]; var aAlter:Array = [17,12,22,31]; var nIndex:Number = 2;
trace(aNamen[nIndex] + „ ist „+ aAlter[nIndex] + „ Jahre alt“);
Ausgabe im Nachrichtenfenster: Severin ist 22 Jahre alt In aNamen speichern wir die Namen diverser Personen und in aAlter ihr entsprechendes Alter. Die Verknüpfung zwischen den Daten erfolgt über eine Variable, in der wir jeweils den Index festlegen, mit dem wir die Daten auslesen. Solange dieser Wert für den gleichzeitigen Zugriff auf beide Arrays identisch ist, werden die zugehörigen Werte korrekt ausgelesen. Eine Alternative zu dieser Variante stellt ein Array dar, das jedes Wertepaar als assoziatives Array aufnimmt (s. u.).
Kapitel 9 Arrays
2. Weisen Sie actions folgendes Bildskript zu: var aTitel:Array = [„Das Licht der Phantasie“, „Das Erbe des Zaube rers“];
trace(„aTitel nach der ersten Initia lisierung: “+ aTitel); aTitel = [];
trace(„aTitel nach der Zuweisung: “+ aTitel);
Der erste trace()-Befehl gibt die anfangs zugewiesenen Titel aus, während der zweite Befehl nur noch den String anzeigt, den wir als Parameter übergeben. Das Array dagegen besitzt zu diesem Zeitpunkt keinen Inhalt mehr, da es mit einem leeren Array überschrieben wurde. Den gleichen Effekt erzielen wir, wenn wir der Längeneigenschaft 0 zuweisen. 3. Ersetzen Sie die zweite Zuweisung von aTitel durch: aTitel.length = 0;
Das Nachrichtenfenster zeigt das gleiche Ergebnis wie zuvor. Indem wir die Array-Länge auf 0 setzen, werden alle Elemente entfernt, die sich innerhalb des Arrays befanden. Wir haben vorhin mit push() und unshift() zwei Methoden kennen gelernt, die es erlauben, an letzter bzw. erster Stelle eines Arrays neue Elemente einzufügen. Die korrespondierenden Methoden zum Löschen lauten pop() und shift(). 4. Ersetzen Sie die dritte und vierte Zeile durch:
9.5 Löschen von Elementen Obgleich es prinzipiell möglich ist, macht es in der Praxis natürlich keinen Sinn, ein Array endlos zu füllen, ohne dass man die nicht mehr benötigten Elemente entfernen kann. Wie beim Einfügen bietet uns Flash gleich mehrere Möglichkeiten an. Die einfachste Variante haben wir eigentlich schon beim Einrichten der Arrays kennen gelernt. Denn wenn wir ein Array mit einem bereits verwendeten Namen neu einrichten, wird das zuvor existierende Array automatisch überschrieben. 1. Erstellen Sie einen neuen Film.
aTitel.pop();
trace(„aTitel nach dem Löschen der letzten Stelle: “+ aTitel);
Wie der zweite trace()-Befehl im Nachrichtenfenster zeigt, wird der zweite Titel im Array aTitel gelöscht, übrig bleibt der String „Das Licht der Phantasie“. Im Unterschied zu push() können wir bei pop() keine Parameter übergeben, es wird immer nur ein einziges Element angesprochen. Sie können das testen, indem Sie beim Aufruf von pop() beispielsweise 2 als Parameter übergeben. Das Ergebnis ändert sich nicht; allerdings erhalten Sie auch keine Fehlermeldung trotz des fehlerhaften Aufrufs der Methode.
9.5 Löschen von Elementen
5. Ersetzen Sie aTitel.pop() durch: aTitel.shift();
Ausgabe im Nachrichtenfenster: Das Erbe des Zauberers Jetzt fehlt beim zweiten trace() im Nachrichtenfenster nicht mehr das letzte, sondern das erste Element. Auch hier würde die Übergabe eines Arguments an die shift()-Methode, beispielsweise um die ersten beiden Elemente zu löschen, folgenlos bleiben. Wollen wir innerhalb eines Arrays ein oder mehrere Elemente löschen, bietet sich die splice()-Methode an. Dazu benötigen wir ein größeres Array. 6. Ersetzen Sie den bisherigen Code durch: var aTitel:Array = [„Das Licht der Phantasie“, „Das Erbe des Zauberers“, „Apocalypso“, „Warten auf OHO“, „Kohl des Zorns“, „Der Zeitdieb“];
trace(„aTitel nach der ersten Initia lisierung: „+aTitel); aTitel.splice(2, 3);
trace(„aTitel nach dem Löschen: „+aTitel);
Ausgabe im Nachrichtenfenster: aTitel nach der ersten Initialisierung: Das Licht der Phantasie,Das Erbe des Zauberers,Apocalypso,Warten auf OHO,Kohl des Zorns,Der Zeitdieb aTitel nach dem Löschen: Das Licht der Phantasie,Das Erbe des Zauberers,Der Zeitdieb Wie im Nachrichtenfenster zu erkennen, löscht Flash die Titel von der dritten bis fünften Stelle. Die splice()-Methode erwartet als Parameter den Index, ab dem die betreffende Operation beginnen soll, sowie die Anzahl der Elemente, die zu löschen sind. Da wir ab der dritten Stelle drei Titel entfernen wollen, übergeben wir als Index 2 (Sie erinnern sich: Index 0 entspricht dem ersten Element, also befindet sich das dritte Element auf Index 2) sowie 3 als Anzahl. Noch einfacher gestaltet sich das Löschen, wenn wir ab der dritten Stelle alles entfernen wollen, ohne mit der Eigenschaft length zu arbeiten. 7. Ändern Sie den Aufruf von splice() wie folgt (Fettdruck): aTitel.splice(2);
75
Nun bleiben die ersten beiden Titel übrig. Übergeben wir an splice() nur den Index, wird alles bis zum Ende des Arrays gelöscht. Damit besitzt splice() genügend Flexibilität, um alle zuvor beschriebenen Lösch-Methoden zu ersetzen. Das letzte Element etwa ließe sich mit aTitel.splice(aTitel. length-1) und das erste mit aTitel.splice(0,1) löschen. Anders als mit pop() und shift() können wir bei splice(), wie gesehen, gleich mehrere Elemente entfernen. Darüber hinaus erlaubt es splice(), Elemente durch andere zu ersetzen oder neue hinzuzufügen. 8. Ändern Sie den Aufruf von splice() wie folgt (Fettdruck): aTitel.splice(2,3,„Einfach göttlich“,„Schweinsgalopp“);
Damit ersetzen wir, wie das Nachrichtenfenster beim zweiten trace()-Befehl zeigt, die Titel von Rankin durch weitere Titel von Pratchett (sollten Sie damit jetzt wenig anfangen können, dann gehen Sie doch mal in die nächste Buchhandlung). Last not least ermöglicht es der Tausendsassa splice(), neue Elemente ohne gleichzeitiges Löschen einzufügen. 9. Ändern Sie den Aufruf von splice() (Fettdruck): aTitel.splice(2,0,„Einfach göttlich“,„Schweinsgalopp“);
Wie im Nachrichtenfenster zu erkennen, erweitern wir das Array ab Index 2 um die beiden als Argument an splice() übergebenen Strings. Da die Anzahl der zu löschenden Elemente auf 0 festgelegt wurde, bleiben dabei alle Elemente des Arrays erhalten. Mitunter müssen wir Elemente entfernen, ohne diese völlig zu verlieren. 10. Ändern Sie den bisherigen Code wie folgt (Fettdruck): var aTitel:Array = [„Das Licht der Phantasie“, „Das Erbe des Zaube rers“, „Apocalypso“, „Warten auf OHO“, „Kohl des Zorns“, „Der Zeit dieb“];
trace(„aTitel nach der ersten Initi alisierung: „+aTitel); var aGeloescht:Array = aTitel. splice(2,3);
76
Kapitel 9 Arrays
trace(„aTitel nach dem Löschen: „+aTitel);
trace(„aGeloescht: „+ aGeloescht);
Ausgabe im Nachrichtenfenster: aTitel nach der ersten Initialisierung: Das Licht der Phantasie,Das Erbe des Zauberers,Apocalypso,Warten auf OHO,Kohl des Zorns,Der Zeitdieb aTitel nach dem Löschen: Das Licht der Phantasie,Das Erbe des Zauberers,Der Zeitdieb aGeloescht: Apocalypso,Warten auf OHO,Kohl des Zorns Aus dem Array werden die Titel gelöscht, die nicht von Pratechett stammen, also die Elemente an Indexposition 2 bis 4. Anders als zuvor gehen diese Elemente jetzt nicht mehr verloren, sondern werden direkt dem neuen Array aGeloescht zugewiesen. Das funktioniert auf diese einfache Weise, weil die splice()Methode beim Ausführen ein Array zurückgibt. <<Meinen Sie vielleicht „Pratchett“?>> Auch pop() und shift() besitzen einen Rückgabewert, der jedoch zu einem anderen Datentyp gehört, so dass der Versuch, auf die gleiche Art diese Methoden einzusetzen, zu einer Fehlermeldung unter AS 2.0 führt. 11. Ändern Sie die Initialisierung von aGeloescht (Fettdruck):
var aGeloescht:Array = aTitel.pop();
Beim Ausführen meldet Flash empört: „Typenkonflikt in Zuweisungsanweisung: Object gefunden, aber Array wird benötigt“. Die direkte Zuweisung macht so keinen Sinn. Wie wir oben gesehen haben, erwartet Flash nun ein Array. 12. Ändern Sie die Initialisierung von aGeloescht (Fettdruck): var aGeloescht:Array = [aTitel. pop()];
Alternativ geht auch: var aGeloescht:Array = new Array(aTitel.pop());
Flash füllt das Array aGeloescht nun korrekt. Das gleiche funktioniert auch mit der shift()-Methode. Sollte dies noch zu abstrakt sein, mag es helfen, sich die genannten Titel in einer gut sortierten Buchhandlung zu besorgen.
Vorsicht ist beim Zugriff auf ein Array mit variabler Länge geboten. Wenn wir beispielsweise aus einem Array ein bestimmtes Element, das mehrfach auftritt, löschen wollen, dann verkürzt der Löschvorgang das Array natürlich jeweils um 1. Führen wir den Vorgang in einer Schleife aus, die innerhalb der Schleifenbedingung auf die Gesamtlänge des Arrays zugreift, schleichen sich unter bestimmten Umständen Fehler ein. Nehmen wir an, wir wollten aus einem Array die Zahl 7 löschen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: aZahlen = [2,3,5,7,7,11];
for(var i = 0; i < aZahlen.length; i++){ if(aZahlen[i]==7){
aZahlen.splice(i,1); } }
trace(aZahlen);
Ausgabe im Nachrichtenfenster: 2,3,5,7,11 Wie wir sehen können, wird die Zahl 7 nur einmal gelöscht. Diese Zahl taucht erstmals auf Index 3, also an vierter Stelle auf. Wenn Flash sie findet, wird sie entsprechend unserer Anweisung in der if-Bedingung gelöscht. Dadurch verkürzt sich jedoch die Gesamtlänge des Arrays und die zweite 7, die auf Index 4 stand, rutscht nun automatisch auf den gelöschten Index 3. Dieser Platz wird aber nicht noch mal kontrolliert, denn das ist ja eben erst geschehen. Flash inkrementiert statt dessen i korrekt auf den nächst höheren Wert und befragt das Element auf Index 4. Das ist nun die 11, nicht, wie vorher, die 7. Anders formuliert: Wenn zwei aufeinanderfolgende Elemente identisch sind, dann führt unsere Methode zu einem fehlerhaften Löschen. Würden wir dagegen mit dem letzten Element beginnen und uns zum ersten vorarbeiten, dann taucht dieses Problem nicht auf. Die Schleife muss also lediglich anders formuliert werden, um zu einem richtigen Ergebnis zu führen: 3. Ändern Sie die Schleife (Fettdruck): aZahlen = [2,3,5,7,7,11];
9.6 Arrays sortieren und durchsuchen
for(var i = aZahlen.length-1; i >= 0; i--){ if(aZahlen[i]==7){
aZahlen.splice(i,1); } }
trace(aZahlen);
Ausgabe im Nachrichtenfenster: 2,3,5,11 Alternativ kann man auch eine for in-Schleife verwenden, die automatisch beim letzten Element beginnt: 4. Ersetzen Sie die for- durch eine for in-Schleife (Fettdruck): aZahlen = [2,3,5,7,7,11]; for(var i in aZahlen){ if(aZahlen[i]==7){
aZahlen.splice(i,1); } }
trace(aZahlen);
Im Nachrichtenfenster erscheint die gleiche Zahlenfolge. Mitunter wollen wir die Länge des Arrays nicht ändern, aber es sollen trotzdem einzelne Elemente gelöscht werden. In diesem Fall hilft die delete-Anweisung. 5. Ersetzen Sie den Aufruf von splice() durch (Fettdruck): if(aZahlen[i]==7){
77
6. Erweitern Sie den Code um: trace(„-------------------“); for(e in aZahlen){
trace(„for in: „+aZahlen[e]); }
trace(„-------------------“);
for(i = 0; i < aZahlen.length; i++){ trace(„for: „+aZahlen[i]); }
Ausgabe im Nachrichtenfenster: 2,3,5,undefined,undefined,11 ------------------for in: 11 for in: 5 for in: 3 for in: 2 ------------------for: 2 for: 3 for: 5 for: undefined for: undefined for: 11 Die for in-Schleife kann die gelöschten Elemente nicht mehr finden, während die for-Schleife durch den Zugriff auf die unverändert gebliebenen Indexwerte alle Stellen des Arrays abfragen kann. Die beiden trace()-Anweisungen mit der gestrichelten Linie wurde nur der Lesbarkeit halber eingefügt.
delete aZahlen[i];
9.6 Arrays sortieren und durchsuchen
Ausgabe im Nachrichtenfenster: 2,3,5,undefined,undefined,11
Mitunter reicht es nicht aus, in einem Array Elemente in der Reihefolge ihres Speicherns zu verwalten. Wenn Sie beispielsweise eine Website betreiben, bei der sich User anmelden müssen, dann ist es notwendig, die Daten in irgendeiner Form zu sortieren. Flash bietet hierzu mehrere Möglichkeiten an.
}
Zwar greifen wir wie vor auf die selben Indexpositionen zu, sobald dort eine Übereinstimmung entsprechend der if-Bedingung gefunden wird. Doch diesmal löschen wir nicht diese Position, sondern nur den zugeordneten Wert. Dadurch wird er durch undefined ersetzt. Die Gesamtlänge des Arrays ändert sich im Gegensatz zu vorher nicht. Beachten Sie, dass Flash beim Auslesen des Arrays abhängig von der verwendeten Schleife die gelöschten Werte verschieden behandelt.
1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var aVornamen:Array = [„Robert“,„Peter“, „Hans“,„Ulrike“,„Brigitte“,„Claudia“]; trace(aVornamen.sort());
78
Ausgabe im Nachrichtenfenster: Brigitte,Claudia,Hans,Peter,Robert,Ulrike Flash führt mit Hilfe der sort()-Methode eine einfache alphabetische Sortierung durch. Führen wir denselben Test mit Zahlen durch. 3. Erweitern Sie das Skript wie folgt (Fettdruck): var aVornamen:Array = [„Robert“,„Peter“, „Hans“,„Ulrike“,„Brigitte“,„Claudia“]; var aZahlen:Array = [17, 26, 21, 35, 38, 43]; trace(aVornamen.sort()); trace(aZahlen.sort());
Ausgabe im Nachrichtenfenster: Brigitte,Claudia,Hans,Peter,Robert,Ulrike 17,21,26,35,38,43 Die Zahlen werden in aufsteigender Reihenfolge sortiert. Flash scheint also zwischen Zahlen und Strings zu unterscheiden und dementsprechend die Sortierung vorzunehmen. 4. Ersetzen Sie die letzte Zahl in aZahlen durch eine 2 (Fettdruck): var aZahlen: Array = [17, 26, 21, 35, 38, 2];
Ausgabe im Nachrichtenfenster: 17,2,21,26,35,38 Entgegen unserer Erwartung befindet sich die 2, die eindeutig die kleinste Zahl im Array darstellt, nicht an erster Stelle. Tatsächlich behandelt die sort()Methode alle Elemente, unabhängig von ihrem Datentyp, als String. Daher erfolgte die erste Sortierung mit den Namen korrekt, und auch die zweite mit den Zahlen war unproblematisch, da alle Zahlen die gleiche Ziffernanzahl aufwiesen. Erst in unserem dritten Beispiel mit Zahlen verschiedener Ziffernanzahl wird der Fehler deutlich: Wenn wir die Inhalte als Datentyp Number behandeln, ist die Sortierung falsch, als Datentyp String dagegen richtig. 5. Ändern Sie die zweite trace()-Anweisung wie folgt (Fettdruck): trace(aZahlen.sort(Array.NUMERIC));
Ausgabe im Nachrichtenfenster: 2,17,21,26,35,38
Kapitel 9 Arrays
Nun entspricht die Ausgabe einer numerischen Sortierung in aufsteigender Reihenfolge. Beim Aufruf kann an die Methode sort() eine Konstante als Argument übergeben werden, um den Sortiervorgang näher zu definieren. In unserem Fall erzwingen wir eine numerische Sortierung, wobei Flash davon ausgeht, dass das betreffende Array gültige Zahlen enthält. Verzichten wir auf einen derartigen Parameter, wird immer eine alphabetische Sortierung durchgeführt. Etwas anders sieht die alphabetische Sortierung aus, wenn sich Groß- und Kleinschreibung des ersten Zeichens der diversen Elemente voneinander unterscheiden. 6. Ändern Sie den Inhalt von aVornamen (Fettdruck): var aVornamen:Array = [„Robert“,„Peter“, „hans“,„Ulrike“,„Brigitte“,„Claudia“];
Ausgabe im Nachrichtenfenster: Brigitte,Claudia,Peter,Robert,Ulrike,hans Der arme „hans“ verliert nicht nur an Statur durch das kleine „h“, sondern offensichtlich auch an Bedeutung, so dass er nun auf dem letzten Platz erscheint. Die alphabetische Sortierung von Flash beruht auf dem Key-Code der Strings. Da den Großbuchstaben eine kleinere Nummer als den Kleinbuchstaben zugeordnet ist, erscheint ein Begriff, der mit einer Minuskel beginnt, automatisch nach Begriffen mit einer Majuskel. 7. Ändern Sie den trace()-Befehl bezüglich aVornamen (Fettdruck): trace(aVornamen.sort(Array.CASEINSENSITIVE));
Ausgabe im Nachrichtenfenster: Brigitte,Claudia,hans,Peter,Robert,Ulrike Der gute „hans“ kehrt wieder mitten in den Freundeskreis zurück. Die an sort() übergebene Konstante sorgt dafür, dass die alphabetische Sortierung unabhängig von der Groß- und Kleinschreibung erfolgt. Ein Array muss nicht zwangsläufig aus einmaligen Werten bestehen, unser geliebter Hans kann also durchaus mehrfach auftauchen. In dem Fall erfolgt das Sortieren in gewohnter Weise, d. h. alle doppelten Elemente werden an die alphabetisch korrekte Stelle verschoben, wenn Sie die sort()-Methode in der bisher vorgestellten Form aufrufen. In Ausnahme
9.6 Arrays sortieren und durchsuchen
fällen kann es jedoch erwünscht sein, dass eine Sortierung bei doppelten Elementen unterbleibt. 8. Ändern Sie die Initialisierung von aNamen und den betreffenden trace()-Befehl folgendermaßen (Fettdruck): var aVornamen:Array = [„Robert“, „Peter“,„Hans“,„Ulrike“,„Brigitte“, „Hans“, „Claudia“]; trace(aVornamen.sort(Array.UNI QUESORT));
Ausgabe im Nachrichtenfenster: 0 Die bescheidene 0 zeigt an, dass eine Sortierung nicht ausgeführt werden konnte, da mindestens ein Element mehr als einmal vorliegt, nämlich der Name „Hans“. Das Array aVornamen wurde nicht verändert. 9. Fügen Sie danach einen weiteren trace()-Aufruf ein: trace(aVornamen);
Ausgabe im Nachrichtenfenster: 0 Robert,Peter,Hans,Ulrike,Brigitte,Hans,Claudia Das Array wurde nicht, wie der vorhergehende trace()-Befehl mit der Sortierungsanweisung aufgrund des Rückgabewertes 0 erschreckenderweise nahelegen könnte, gelöscht. Dieser Wert bedeutet lediglich, dass die gewünschte Sortierung nicht durchführbar ist. In allen anderen Fällen entspricht die Rückgabe, wie wir oben gesehen haben, dem sortierten Array. Alle bisherigen sort()-Anweisungen sorgen für eine Änderung des davon betroffenen Arrays. Manchmal ist es jedoch erwünscht, einerseits die Reihenfolge, in der die Elemente in ein Array gefüllt wurden, beizubehalten, andererseits auf ein sortiertes Array etwa zwecks schnellerer Suche zugreifen zu können. Was nach Quadratur des Kreises klingt, gelingt mit einer weiteren Konstante, die an die sort()-Methode übergeben werden kann. 10. Ersetzen Sie den bisherigen Code durch: var aVornamen:Array = [„Robert“, „Peter“,„Hans“,„Ulrike“,„Brigitte“, „Claudia“];
var aVornamenSort:Array = aVornamen. sort(Array.RETURNINDEXEDARRAY);
79
trace(„aVornamen unsortiert: „+aVor namen); trace(„aVornamen sortiert: „+aVor namenSort);
Ausgabe im Nachrichtenfenster: aVornamen unsortiert: Robert,Peter,Hans,Ulrike,Brig itte,Claudia aVornamen sortiert: 4,5,2,1,0,3 Das Array aVornamen zeigt den Inhalt unverändert an, während in aVornamenSort eine alphabetisch korrekte Reihenfolge über die Indexnummern der Elemente von aVornamen dargestellt wird. In einem sortierten Array würde an erster Stelle das Element der Indexposition 4, also „Brigitte“, an zweiter das Element mit Index 5, also „Claudia“ usw. liegen. Das können wir mit einer Schleife überprüfen. 11. Fügen Sie nach dem letzten trace()-Befehl folgende Schleife ein: for(var i:Number = 0; i < aVornamen Sort.length; i++){ trace(aVornamen[aVornamenSort[i]]);
}
Ausgabe im Nachrichtenfenster: Brigitte Claudia Hans Peter Robert Ulrike Wir verwenden die Indexnummern, die in aVorna menSort gespeichert wurden, sukzessive, um auf die entsprechenden Elemente in aVornamen zugreifen zu können. Dadurch ergibt sich die im Nachrichtenfenster ausgegebene korrekte Reihenfolge, ohne dass wir im ursprünglichen Array eine Änderung durchführen müssen. Alle bisherigen Beispiele gehen davon aus, dass eine Sortierung in aufsteigender Reihenfolge erwünscht ist. Die lässt sich für den gegenteiligen Fall sehr einfach umkehren. 12. Ersetzen Sie den bisherigen Code durch: var aVornamen:Array = [„Robert“, „Peter“,„Hans“,„Ulrike“,„Brigitte“, „Claudia“];
80
trace(„aVornamen sortiert: „+aVor namen.sort()); trace(„aVornamen umgekehrt sortiert: „+aVornamen.reverse());
Ausgabe im Nachrichtenfenster: aVornamen sortiert: Brigitte,Claudia,Hans,Peter,Rob ert,Ulrike aVornamen umgekehrt sortiert: Ulrike,Robert,Peter, Hans,Claudia,Brigitte Wie der Name der Methode nahelegt, erfolgt eine Umkehrung des Arrays durch Vertauschen der Indexposition, so dass eine absteigende Sortierung vorliegt. Das funktioniert natürlich auch mit numerischen Werten. Unabhängig davon, ob ein Array in sortierter oder unsortierter Form vorliegt, müssen wir einzelne Elemente gezielt auslesen können. Wir haben oben bereits gesehen, wie das geschieht, wenn der Index des gesuchten Elements bekannt ist. Viel öfter kommt es jedoch vor, dass wir zwar wissen, wonach wir suchen müssen, kennen aber dessen Position in einem gegebenen Array nicht. In diesen Fällen können wir mit einer simplen Schleife auf das Array zugreifen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var aVornamen:Array = [„Robert“,„Peter“, „Hans“,„Ulrike“,„Brigitte“, „Claudia“]; var vSuche:String = „Hans“;
for (var i:Number = 0; i
trace(vSuche + „ liegt auf Index „+i); } }
trace(„habefertisch!“);
Ausgabe im Nachrichtenfenster: aktueller Index: 0 aktueller Index: 1 aktueller Index: 2 Hans liegt auf Index 2 aktueller Index: 3 aktueller Index: 4 aktueller Index: 5 habefertisch!
Kapitel 9 Arrays
Wir speichern den zu suchenden Begriff in einer Variablen. Anschließend schauen wir mit Hilfe einer Schleife nach, ob das Element an der durch die Zählvariable i definierten Indexposition mit unserem Suchbegriff übereinstimmt. Ist das der Fall, erfolgt eine entsprechende Ausgabe. Ganz zum Schluss lassen wir uns auf trabbatonisch anzeigen, dass die Schleife beendet wurde. Wie das Nachrichtenfenster zeigt, wird die Schleife allerdings selbst dann noch weiter ausgeführt, wenn wir den gesuchten Begriff bereits gefunden haben. Stellen Sie sich vor, unser Array würde 1.000 Namen enthalten, und schon an dritter Stelle befände sich das benötigte Element. Dann wäre es unsinnig, weiter Zeit zu verschwenden und alle übrigen Elemente ebenfalls anzuschauen. 3. Fügen Sie innerhalb der if-Bedingung nach dem trace() ein: break;
Ausgabe im Nachrichtenfenster: aktueller Index: 0 aktueller Index: 1 aktueller Index: 2 Hans liegt auf Index 2 habefertisch! Wie Sie sehen, hört Flash in dem Augenblick auf, in dem die Bedingung erfüllt ist, also das aktuelle Element dem gesuchten Begriff entspricht. Weitere Optimierungen wären über sortierte Arrays möglich, wobei man beispielsweise vergleichen könnte, ob das gesuchte Element größer oder kleiner als das Element ist, dass sich in der Mitte des Arrays befindet. Ist es größer, sucht man nur in der zweiten Hälfte, ansonsten in der ersten Hälfte des Arrays. Natürlich macht das nur ab einer gewissen Größe Sinn, in unseren Beispielen dagegen wäre das zugegebenermaßen etwas übertrieben.
9.7 Weitere Methoden Zwei Methoden ermöglichen es, den kompletten Inhalt eines Arrays in einen String zu verwandeln. Das ist beispielsweise dann nützlich, wenn wir ein Array einer Variablen zuweisen wollen. Bekanntermaßen nimmt eine Variable nur einen Wert auf, ein Array da-
9.8 Mehrdimensionale Arrays
gegen enthält naturgemäß viele Werte. Beim Versuch einer direkten Zuweisung erhalten wir eine Fehlermeldung aufgrund eines Typenkonflikts, d. h. zweier verschiedener Datentypen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var aZahlen:Array = new Array(2,4,6,8);
var aZahl:Number = aZahlen;
Wie erwähnt mokiert sich Flash darüber, dass wir aZahlen mit dem Datentyp Array aZahl mit dem Datentyp Number zuweisen wollen. Verzichten wir auf die Typisierung, führt Flash den Befehl klaglos aus – und ändert automatisch den Datentyp von aZahl in die allgemeinstmögliche Variante eines Objects. Wenn wir diese Schummelei nicht dulden wollen, können wir eine korrekte Zuweisung über die toString()-Methode erreichen. 3. Ändern Sie das Skript wie folgt (Fettdruck): var aZahlen:Array = new Array (2,4,6,8);
var aZahl:String = aZahlen.toString();
81
Ausgabe im Nachrichtenfenster: 2,4,6,8 Ohne Trennzeichen: Hallo, wie geht es?,Ach, eigentlich ganz gut Mit Trennzeichen: Hallo, wie geht es?#Ach, eigentlich ganz gut Verwenden wir lediglich die toString()-Methode, enthält der resultierende String das Komma sowohl als Trennzeichen innerhalb eines Satzes (nach „Hallo“ und „Ach“) wie zwischen den Elementen des ursprünglichen Arrays (Zwischen „?“ und „Ach“). Programmiertechnisch können wir so nicht entscheiden, was denn nun konkret damit gemeint ist. Die join()Methode dagegen setzt nur an den Stellen, an denen eine Trennung zwischen Array-Elementen markiert werden soll, das angegebene Sonderzeichen, während das Komma als Satzelement erhalten bleibt. Aus sSatz 2 ließe sich daher problemlos der ursprüngliche Inhalt des Arrays wieder herstellen bzw. ein verständlicher Text erstellen, aus sSatz 1 dagegen nicht (oder nur mit erheblichem Mehraufwand). Logischerweise wählen Sie als Trennzeichen für die join()-Methode ein Zeichen, das in dem angesprochenen Array nicht vorkommt.
trace(aZahl);
Erwartungsgemäß gibt Flash jetzt die Zahlen „2,4,6,8“ aus. Sie liegen in aZahl nicht mehr als kommaseparierte Elemente, wie wir es vom Array aZahlen gewohnt sind, vor, sondern bilden einen einzigen Wert. Sie lassen sich jedoch sehr einfach aufgrund des Trennzeichens wieder als einzelne Elemente auslesen, falls Bedarf bestehen sollte. Wenn die Elemente im Array bereits als Strings vorliegen, die ihrerseits Kommas enthalten (z. B. im Fall ganzer Sätze), so können wir das Trennzeichnen explizit definieren, müssen dazu aber eine andere Methode verwenden. 4. Erweitern Sie den Code um folgende Zeilen: var aSaetze:Array = [„Hallo, wie geht es?“, „Ach, eigentlich ganz gut“]; var sSatz 1:String = aSaetze.to String(); var sSatz 2:String = aSaetze. join(„#“);
trace(„Ohne Trennzeichen: „+sSatz 1); trace(„Mit Trennzeichen: „+sSatz 2);
9.8 Mehrdimensionale Arrays Die in den bisherigen Beispielen verwendeten Arrays sind prinzipiell einfach aufgebaut, da sie lediglich einzelne, durch Trennzeichen direkt identifizierbare Elemente enthalten. Doch manchmal ist es notwendig, in einem Array weitere Arrays zu erfassen. In diesem Fall spricht man von einem mehrdimensionalen Array. Damit lässt sich beispielsweise der Aufbau eines gekachelten Spielfelds, die Positionen eines Objekts oder eine aus verschiedenen Zeilen und Spalten bestehenden Fläche beschreiben, wie weiter unten in einzelnen Workshops zu sehen ist. Ein derartiges Array lässt sich prinzipiell so einrichten, wie wir es bereits kennen gelernt haben. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var aPositionen:Array = [[0,0],[5,0]]; trace(aPositionen);
82
Kapitel 9 Arrays
Im Nachrichtenfenster erscheinen die vier bei der Initialisierung übergebenen Werte. Alternativ hätte man auch schreiben können:
einen String als Index zu definieren. In diesem Fall sprechen wir von assoziativen Arrays oder Hashes. Sie lassen sich auf mehrere Arten anlegen.
var aPositionen:Array = new Array ([0,0],[5,0]);
1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu:
Und ganz besonders korrekt geht auch das: var aPositionen:Array = new Array(new Array(0,0),new Array(5,0));
Um für Flash kenntlich zu machen, dass wir in ein Array weitere Arrays füllen, verwenden wir den Array-Operator, d. h. die eckigen Klammern []. In beredter Prosa formuliert besteht aPositionen aus einem Array mit dem Inhalt 0,0, das dem ersten Element entspricht, und aus einem Array mit dem Inhalt 5,0, das dem zweiten Element entspricht. Der einfache trace()-Befehl gibt die Elemente ohne Arrayzugehörigkeit aus, gerade so, als handle es sich um gewöhnliche kommaseparierte Elemente, wie wir es von anderen Arrays her kennen. Der Zugriff auf einzelne Elemente erfolgt in gewohnter Weise. 3. Erweitern Sie den Code um folgendes trace(): trace(aPositionen[1]);
Im Nachrichtenfenster wird der Inhalt des zweiten Arrays in aPositionen, also 5,0 angezeigt. Wollen wir innerhalb dieses Arrays auf eine einzelne Zahl zugreifen, ergänzen wir einfach den benötigten Index. 4. Ändern Sie die trace()-Anweisung (Fettdruck): trace(aPositionen[1][0]);
Nun taucht nur noch die Zahl 5 auf, also im zweiten Element (5,0) das erste Element (5). Prinzipiell können Sie die Arrays beliebig tief verschachteln. Allerdings verliert man dann schnell den Überblick, so dass hier weniger vielleicht mehr ist. Alle bisher behandelten Eigenschaften und Methoden treffen auf die in aPositionen enthaltenen Arrays zu. Lediglich die concat()-Methode macht insofern eine Ausnahme, als sie, wie erwähnt, beim Zusammenfügen nicht korrekt zwischen Array und kommaseparierten Elementen unterscheidet.
var aAuto:Array = new Array(); aAuto[„raeder“] = 4;
aAuto[„farbe“] = „rot“; aAuto[„preis“] = 26000;
Damit legen wir ein Array an, das über drei Elemente mit den Werten 4, „rot“ und 26000 verfügt. Sie sind jedoch nicht über eine Indexnummer erfasst, sondern über eigens definierte Strings. In einer alternativen Schreibweise kann man die Strings als Bezeichner übergeben. 3. Ersetzen Sie den Code nach der ersten Zeile durch: aAuto.raeder = 4;
aAuto.farbe = „rot“; aAuto.preis = 26000;
Anstelle der Strings verwenden wir jeweils einen Bezeichner, der per Punktsyntax dem Array zugeordnet wird. Eine weitere Möglichkeit besteht darin, auf einen anderen Datentyp zurückzugreifen. 4. Ändern Sie die erste Zeile wie folgt: var aAuto:Object = new Object();
Welche Variante Sie auch wählen, das Ergebnis ist immer dasselbe. Da es sich um keine gewöhnlichen Arrays handelt, gestaltet sich der Zugriff auf die Inhalte anders. 5. Ersetzen Sie die Objektdefinition wieder durch die Arraydefinition: var aAuto:Array = new Array();
6. Fügen Sie ganz am Ende ein: trace(„aAuto: „+aAuto);
trace(„aAuto[0]: „+aAuto[0]);
9.9 Assoziative Arrays
Ausgabe im Nachrichtenfenster: aAuto: aAuto[0]: undefined
Alle bisherigen Arrays verwenden eine Zahl als Indexzugriff. Daneben kennt Flash die Möglichkeit,
Flash kann aAuto nicht auslesen und die erste Indexposition gibt ein undefined zurück. Scheinbar noch
9.9 Assoziative Arrays
merkwürdiger wird es, wenn wir uns den Datentyp von aAuto anschauen. 7. Fügen Sie ganz am Ende ein: trace(„Datentyp aAuto: „ + typeof(aAuto));
Ausgabe im Nachrichtenfenster: aAuto: aAuto[0]: undefined Datentyp aAuto: object Entgegen unserer Erwartung meldet Flash (korrekt), bei unserem Array handle es sich um ein Object. Das gilt übrigens unabhängig davon, ob wir vor der Zuweisung von Inhalten über Bezeichner den trace()Befehl ausführen oder danach. Denn für Flash stellt ein Array nichts anderes dar als einen Sonderfall eines Objektes. Insofern schummelt das Programm ein wenig, wenn wir beim Deklarieren als Datentyp Array eingeben, aber ein Objekt erhalten. Wirklich tragisch ist das nicht, denn erst dadurch erlaubt uns Flash, Inhalte über Bezeichner statt über einen Index zu definieren. Der korrekte Zugriff erfolgt dann einfach über diese Bezeichner. 8. Kommentieren Sie die bisherigen trace()-Anweisungen aus oder löschen sie. 9. Fügen Sie am Ende einen neuen trace()-Befehl ein: trace(aAuto.farbe);
Das Nachrichtenfenster zeigt nun den zugehörigen Wert „rot“ an. Werte können also nur über die betreffenden Bezeichner abgefragt werden. 10. Fügen Sie ein weiteres trace() ein:
trace(aAuto[„preis“]);
Neben der Farbe „rot“ gibt Flash den Preis „26000“ an. Wie bei der Deklaration können wir beim Zugriff beide Schreibweisen, also mit Bezeichner oder mit String, verwenden. Kennen wir die Bezeichner nicht, bleibt der Zugriff per Schleife. Hier können wir allerdings nur die for in-Schleife verwenden, die ohne Indexzahl auskommt. 11. Ersetzen Sie beide trace()-Befehle durch: for(var e:String in aAuto){
trace(„Element „+e+“: „+aAuto[e]);
}
83
Flash zählt alle Elemente in der umgekehrten Reihenfolge ihrer Zuweisung auf. Die Zählvariable e übernimmt dabei keinen Index, sondern den Bezeichner, dessen konkreter Wert über aAuto[e] bestimmt wird. Sind die Bezeichner nicht namentlich bekannt, kann man sie durch direkten Zugriff auf die Variable auslesen. Übrigens kann man die for in-Schleife auch dann verwenden, wenn ein Array über gültige Indexwerte verfügt. In dem Fall wandelt Flash die Indexpositionen einfach in Bezeichner um, es findet also eine temporäre Typumwandlung von Number in String statt. Aus einem „normalen“ Array wird dann temporär ein Hash. Bei Hashes versagen leider die gängigen Methoden der Bearbeitung, d. h. wir können weder die Eigenschaft length abfragen noch eine der oben behandelten und lieb gewonnenen Methoden wie concat(), splice(), push() einsetzen. Änderungen sind nur über die Bezeichner bzw. Strings möglich, die wir beim Einrichten oder zu einem späteren Zeitpunkt setzen. Wir haben oben im Zusammenhang mit verschiedenen Datentypen (Vorname: String, Alter: Number) darauf verwiesen, solche Elemente in einem Array assoziativer Arrays verwalten zu können. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var aPersonen:Array = [];
aPersonen. push({namen:„Peter“,alter:17}); aPersonen. push({namen:„Ulla“,alter:12});
aPersonen.push({namen:„Severin“,alter :22}); aPersonen.push({namen:„Johannes“,alte r:31}); trace(aPersonen);
Ausgabe im Nachrichtenfenster: [object Object],[object Object],[object Object],[object Object] Innerhalb des übergeordneten Arrays aPersonen haben wir jeweils Objekte angelegt mit den Attributen „Namen“ und „Alter“ sowie zugehörigen Werten. Der trace()-Befehl gibt uns die entsprechenden Objekte aus. Um auf die Inhalte zugreifen zu können, müssen wir allerdings eine konkretere Angabe machen. 3. Ändern Sie den trace()-Befehl (Fettdruck): trace(aPersonen[1].namen);
84
Ausgabe im Nachrichtenfenster: Ulla Wir übergeben trace() das Objekt an zweiter Stelle und greifen dort auf den Bezeichner namen zu. Als Ergebnis erhalten wir „Ulla“. Wie wir gesehen haben, kann der Zugriff alternativ über einen String anstelle eines Bezeichners erfolgen. 4. Fügen Sie ein weiteres trace() hinzu: trace(aPersonen[1][„alter“]);
Ausgabe im Nachrichtenfenster: Ulla 12 Nach dem Namen erhalten wir nun auch das zugehörige Alter. Obwohl beide Varianten des Zugriffs möglich sind, sollte man sich auf eine festlegen, da der Code ansonsten eher verwirrt. Um alle Elemente auszulesen, verwenden wir wie oben gezeigt einfach eine for in-Schleife. Sollen Werte geändert werden, müssen Sie lediglich den Zugriff auf ein Element mit einer Zuweisung verknüpfen. 5. Fügen Sie am Ende ein: aPersonen[1].alter = 14;
trace(aPersonen[1].alter);
Ausgabe im Nachrichtenfenster: Ulla 12 14 Sprunghaft altert Ulla um 2 Jahre, was man sich in der Jugend gerne gefallen lässt, später dagegen eher nicht mehr. Interessanterweise verwaltet Flash alle Objekte, die von AS-Klassen erstellt werden, als assoziative Arrays. Wir können damit auf ihre Elemente und Eigenschaften zugreifen, indem wir eine for inSchleife verwenden. Das wollen wir uns am Beispiel eines Textfeldes anschauen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var tfTxt:TextField = this. createTextField(„textfeld“, 1, 0, 0, 100, 28); for (var e:String in tfTxt) {
trace(„Eigenschaft „+e+“: „+tfTxt[e]); }
Kapitel 9 Arrays
Im Nachrichtenfenster erhalten Sie eine lange Auflistung der diversen Textfeldeigenschaften und zugeordneter Standardwerte sowie der von Ihnen bei der Initialisierung gegebenen Werte. Durchaus nützlich ist das, wenn wir als Behälter einen leeren MovieClip verwenden und in ihm alle Objekte speichern, die sich gleich oder ähnlich verhalten sollen, z. B. in einem Spiel die gegnerischen Geschosse. Dann können wir mit einer for inSchleife darauf zugreifen, und ihnen die benötigten Aktionen dynamisch zuweisen, ohne ihre Namen zu kennen. In einzelnen Workshops werden wir darauf zurückkommen. Zur Illustration hier ein kleines Beispiel: 1. Erstellen Sie eine Standarddatei. 2. Auf der Ebene objects zeichnen Sie an beliebiger Stelle ein Quadrat (40 × 40, orange). 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcQuadrat, kein Instanzname, Registrierungspunkt mittig). 4. Duplizieren Sie das Quadrat mehrfach und setzen die Kopien auf verschiedene Positionen. 5. Markieren Sie alle MovieClips der Ebene und erstellen daraus einen weiteren MovieClip (Bibliotheksname mcBehaelter, Instanzname behael ter, Registrierungspunkt beliebig). 6. Weisen Sie actions folgendes Bildskript zu: behaelter.onEnterFrame = function() { for (var e in this) {
if (typeof (this[e] == „movie clip“)) { this[e]._rotation += 5; } } };
Beim Testen werden sich alle MovieClips um ihre eigene Achse drehen. Wir definieren für den MC be haelter ein onEnterFrame–Ereignis, das ihn anweist, alle in ihm enthaltenen Objekte zu animieren. Die if-Bedingung sorgt dafür, dass auch wirklich nur MovieClips angesprochen werden. Denn in behael ter können neben den von uns händisch angelegten MovieClips weitere Elemente enthalten sein, z. B. eine type function, also das zugewiesene on EnterFrame-Ereignis. Mit diesem Objekt würde ein Rotationsversuch keinen Sinn machen.
10
Funktionen
Während wir in Arrays und Variablen Daten erfassen können, bietet eine moderne Skriptsprache auch die Möglichkeit, ganze Befehlsfolgen zu speichern. Dazu verwendet man sogenannte Funktionen, die in ActionScript mit dem Schlüsselwort function deklariert werden. Der Vorteil einer Funktion besteht darin, Befehlsfolgen bzw. Codeblöcke, die man mehrfach benötigt, auf eine einfache Art zur Verfügung zu stellen. Anstatt immer wieder die benötigten Befehle aufzurufen, kann man sie bequem über eine Funktion ausführen. Darüber hinaus erhöht das die Übersichtlichkeit des Codes und strukturiert ihn. Last not least können ausgelagerte Funktionen in anderen Projekten wieder verwendet werden, so dass der benötigte Code nicht jedes mal komplett neu geschrieben werden muss. Angenommen, der gesamte textliche Inhalt Ihrer Website wird extern geladen und in spezifischer Weise formatiert. Nach unserer bisherigen Vorgehensweise müssten Sie den betreffenden Code per copyand-paste an allen Stellen einfügen, an denen er ausgeführt werden soll. Das ist nicht nur mühsam und unübersichtlich, sondern auch fehleranfällig. Denn was geschieht, wenn Sie plötzlich die Formatierung ändern wollen? Dann müssen Sie in einer zeitaufwendigen Suche alle Stellen ausfindig machen, an denen der betreffende Code steht, und ihn ändern. Wie schnell schleicht sich da ein Fehler ein, nur weil man eine Zeile übersehen hat. Der gesamte Prozess vereinfacht sich erheblich, wenn Sie statt dessen mit Funktionen arbeiten. Der benötigte Code wird ein einziges Mal in die Funktion geschrieben, die Sie in Ihrer Anwendung immer dann aufrufen, wenn externe Texte zu laden und zu formatieren sind. Änderungen lassen sich schnell an einer zentralen Stelle durchführen, nämlich in der Funktionsdefinition bzw. -deklaration.
10.1 Funktionsdeklaration und -aufruf Allgemein sieht eine Funktionsdeklaration oder -definition folgendermaßen aus: function Funktionsname():Void{ Anweisung1; Anweisung2; }
Der Schlüsselbegriff function leitet die Funktionsdefinition ein. Ihm folgt der Name, der den gleichen Konventionen unterliegt wie ein Variablen-Name, also: Keine Zahl als erstes Zeichen. • Keine Leerzeichen. • Keine Sonderzeichen (ausgenommen $ und _). • Keine deutschen Umlaute. • Groß- und Kleinschreibung beachten. • Keine reservierten Begriffe. • Eindeutiger Name. Auch hier gilt natürlich die Regel, dass man einen möglichst aussagekräftigen, dennoch nicht zu langen Namen wählen soll. Nach dem Namen folgen eine runde Klammer, die bei einfachen Funktionen leer bleibt, sowie die Angabe des Datentyps, falls Ihre Funktion einen Wert zurück liefert. Da das hier nicht zutrifft, legen wir diesen Datentyp mit Void fest. Der eigentliche Inhalt der Funktion, d. h. die auszuführenden Anweisungen, wird immer von geschweiften Klammern umschlossen, so dass Flash Beginn und Ende des zur Funktion gehörigen Befehlsblocks erkennen kann. Etwas ähnliches haben Sie bereits bei if-Anweisungen und Schleifen kennen gelernt, bei denen der abhängige Code ebenfalls in derartige Klammern eingeschlossen wird. Als Minimum enthält
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
85
86
eine Funktion mindestens eine Anweisung. Formal gesehen gibt sich Flash allerdings auch mit einer leeren Funktion zufrieden, also dem völligen Fehlen von irgendwelchen Anweisungen zwischen den Klammern Da prinzipiell keine Obergrenze existiert, kann eine Funktion problemlos einige Hundert Zeilen Anweisungen enthalten – Sinn macht das jedoch keinen, denn sie würde durch ihren enormen Umfang völlig unübersichtlich. Der Code in der Funktion kann beliebiger Art sein, es spielt also keine Rolle, ob Sie dort den Wert einer Variablen ändern, eine Schleife verwenden, mit einer verschachtelten if-Abfrage arbeiten, eine andere Funktion aufrufen oder gar eine neue Funktion definieren. Für die Lesbarkeit Ihres Codes können sich jedoch deutliche Konsequenzen ergeben, je nachdem, welche Aktion Sie in Ihren Funktionen ausführen. Wenn Sie beispielsweise innerhalb einer Funktion ständig neue Funktionen definieren, weil diese erst jetzt benötigt werden, dann mag das dem logischen Ablauf Ihrer Anwendung zwar entsprechen, Sie laufen jedoch Gefahr, den berühmt-berüchtigten Spaghetti-Code zu produzieren, weil der Überblick verloren geht. In vielen Fällen ist es daher sinnvoller, alle benötigten Funktionen in einem eigenen Block innerhalb des Sourcecodes zu definieren, selbst wenn die Funktionen erst später, u. U. sogar überhaupt nicht (nämlich wenn sie von einer Useraktion abhängig sind) benötigt werden. Um mit einer Funktion konkret arbeiten zu können, reicht eine Deklaration nicht aus. Denn dort steht nur der Inhalt der Funktion. Die Deklaration entspricht lediglich einer Beschreibung. Ausgeführt wird sie erst, wenn wir sie aufrufen. Ein derartiger Aufruf sieht allgemein so aus: Funktionsname();
Um die in einer Funktion enthaltenen Befehle auszuführen, reicht es also vollkommen aus, den Namen zuzüglich der geschweiften Klammern zu schreiben. Fehlen die Klammern, erhalten Sie zwar keine Fehlermeldung von Flash, der Aufruf wird aber auch nicht ausgeführt. Wir wollen uns ein konkretes Beispiel einer Funktionsdeklaration inklusive Aufruf anschauen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: function ausgabe():Void{
Kapitel 10 Funktionen
trace(„Hallo Flasher!“); }
ausgabe();
Ausgabe im Nachrichtenfenster: Hallo Flasher! Anfangs deklarieren wir eine Funktion namens aus gabe(), deren einzige Aufgabe darin besteht, im Nachrichtenfenster eine Information auszugeben. Anschließend rufen wir die Funktion über ihren Namen auf, so dass Flash uns entsprechend freundlich begrüßt. Zwar spielt bei Flash die Reihenfolge keine Rolle, so dass Sie getrost zuerst eine Funktion aufrufen dürfen und sie danach erst deklarieren. Das widerspricht aber dem Programmablauf und ist zudem schlechter Stil. Daher wollen wir uns immer an die Reihenfolge Deklaration – Aufruf halten.
10.2 Funktionen ohne Bezeichner Alternativ zur oben vorgestellten Syntax können Funktionen auch ohne Bezeichner bzw. als anonyme Funktionen definiert werden. Unter Bezeichner versteht man nichts anderes als den Funktionsnamen. In der allgemeinen Form sieht eine derartige anonyme Funktion so aus: var ausgabe:Function = function(){ //Anweisungen; }
Bei der Übergabe an eine andere Funktion als Argument: Funktionsname(function(){Anweisungen});
Im Zusammenhang mit Ereignissen schreiben wir sie so: Objekt.Ereignis = function(){ //Anweisungen; }
Beachten Sie, dass im letztgenannten Fall keine Typisierung erfolgen kann. Diese Variante verwenden wir häufig, da sie es auf eine recht einfache Art ermöglicht, Interaktionen zu programmieren. Wir können unser erstes Beispiel auch in der anonymisierten Form realisieren.
10.3 Gültigkeit
3. Fügen Sie vor der bestehenden Funktionsdeklaration folgende Zeilen hinzu: var ausgabe:Function = function(){
trace(„Nochmal: Hallo Flasher!“); }
4. Fügen Sie nach dem bestehenden Funktionsaufruf folgende Zeile hinzu: ausgabe();
Ausgabe im Nachrichtenfenster: Hallo Flasher! Nochmal: Hallo Flasher! Wir legen zunächst eine Variable mit dem Datentyp Function an und weisen ihr eine Funktionsdeklaration zu, die eine trace()-Anweisung enthält. Der Aufruf erfolgt analog zu derjenigen einer Funktion mit Bezeichner, so dass im Nachrichtenfenster die Texte beider trace()-Aufrufe erscheinen. 5. Zeichnen Sie auf objects an beliebiger Stelle einen Kreis (50 × 50). 6. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Instanzname kreis, Registrierungspunkt beliebig). 7. Fügen Sie nach der zweiten Funktionsdeklaration und vor dem ersten Funktionsaufruf folgende Zeilen ein: kreis.onPress = function(){ trace(„Mausklick“); }
Wir weisen der Instanz kreis eine Funktion ohne Bezeichner zu, die dafür sorgt, dass bei jedem Mausklick auf dieses Objekt eine Meldung im Nachrichtenfenster ausgegeben wird. Fügen Sie folgenden Aufruf ein: setInterval(function(){trace(„Ich bin trotz Großer Lauschangriffe völlig anonym unterwegs!“)},100)
Im Abstand von 100 Millisekunden meldet sich trotzig das Nachrichtenfenster und beharrt auf anonymen, aber keinesfalls ungesetzlichen Bewegungen. Welchen Sinn machen diese verschiedenen Vorgehensweisen? Die Funktionsdefinition mit Bezeichner hat u. a. den Vorteil, jederzeit verwendet werden zu können. Die anonymisierte Form mit der Variablen
87
findet insbesondere bei der Erstellung von Methoden, also an Objekte gebundene Funktionen, Verwendung. Wie wir bei der Beschreibung der einzelnen Klassen weiter unten sehen werden, macht Flash sehr viel Gebrauch von solchen Methoden. Ein konkretes Beispiel haben wir bereits im Kapitel Variablen kennen gelernt, wo wir eine Methode namens kreis.aus gabe() definierten. Die vorletzte Variante mit den Ereignissen erweist sich dann als nützlich, wenn Programmaktionen zu bestimmten Zeitpunkten ausgelöst oder Interaktionen des Anwenders definiert werden sollen. Das letzte Beispiel dagegen funktioniert zwar, macht aber insofern wenig Sinn, als der Code dadurch ziemlich unübersichtlich wird. Eine derartige Schreibweise sollte also vermieden werden. Übrigens lassen sich Funktionen, die einem Ereignis zugewiesen wurden, explizit aufrufen. Wenn wir beispielsweise in unserem Code nach der schließenden Klammer des onPress-Ereignisses kreis. onPress(); eintragen, dann entspricht dies dem Eintreten des Mausereignisses und Flash gibt im Nachrichtenfenster die betreffende Meldung aus, ohne dass wir auf den Kreis geklickt haben.
10.3 Gültigkeit Im Zusammenhang mit Variablen haben wir das Konzept der Gültigkeit oder Reichweite kennen gelernt. Was dort gesagt wurde, gilt auch hier. Ein Funktionsaufruf wie oben in Schritt 2 bzw. Schritt 4 kann nur gelingen, wenn sich die Funktionsdeklaration in derselben Zeitleiste befindet, von der aus wir aufrufen. Da wir hier in der Hauptzeitleiste sowohl deklarieren wie auch aufrufen, stellt das kein Problem dar. 8. Ändern Sie den Anweisungsblock innerhalb der Funktionsdefinition von ausgabe() wie folgt (Fettdruck): trace(„Ich bin eine in der Hauptzeitleiste definierte Funktion“); 9. Ersetzen Sie den Anweisungsblock innerhalb des onPress-Ereignisses durch: ausgabe(); Bei Mausklick auf kreis rufen wir die in derselben Zeitleiste wie das onPress-Ereignis definierte Funktion ausgabe() auf, was zum Anzeigen des ge-
88
Kapitel 10 Funktionen
wünschten Textes führt. Anders sieht es jedoch aus, wenn wir das onPress in die Zeitleiste des Movie Clips verlagern. 10. Löschen Sie das onPress-Ereignis vollständig. 11. Öffnen Sie den Symbolbearbeitungsmodus von mcKreis. 12. Fügen Sie dort folgendes Bildskript ein:
this.onPress = function() {
trace(„Mausklick“); ausgabe();
};
Wenn Sie testen und auf den Kreis klicken, wird nur die trace()-Anweisung ausgeführt, also „Mausklick“ im Nachrichtenfenster ausgegeben. Der Funktionsaufruf von ausgabe() dagegen schlägt fehl, da Flash die entsprechende Deklaration nicht finden kann. Leider schweigt sich Flash an dieser Stelle höflich aus anstatt uns eine entsprechende Fehlermeldung anzuzeigen. 13. Ändern Sie im Symbolbearbeitungsmodus den Aufruf von ausgabe() (Fettdruck):
_root.ausgabe();
Bei Mausklick ist Flash nun in der Lage, die Funktion ausgabe() auszuführen, denn wir haben mitgeteilt, in welcher Zeitleiste sich die zugehörige Definition befindet. Der Bezeichner _root bezieht sich auf die Hauptzeitleiste. Wie bereits an früherer Stelle erwähnt, sollten Skripte möglichst zentral in einem oder einigen wenigen Bildskripten derselben Zeitleiste definiert werden, um Unübersichtlichkeit zu vermeiden. Daher empfiehlt es sich, soweit möglich, auf Bildskripte in Symbolen bzw. auf Objektskripte bei Instanzen zu verzichten und statt dessen ein Bildskript auf der Hauptzeitleiste anzulegen. 14. Schneiden Sie das komplette Bildskript im Symbolbearbeitungsmodus von mcKreis aus und fügen es in actions auf der Hauptzeitleiste wieder ein. 15. Ändern Sie dort die Ereigniszuweisung von on Press (Fettdruck):
kreis.onPress = function() {
16. Löschen Sie innerhalb der Anweisungen des on Press-Ereignisses den Bezug auf _root, so dass
die Funktion ausgabe() wie zuvor aufgerufen wird:
ausgabe();
Unser Skript besitzt die gleiche Funktionalität wie zuvor, jedoch liegt nun der gesamte Code wieder übersichtlich in einem einzigen Frame vor. Wie bei Variablen sind globale Funktionsdefinitionen möglich, so dass das eben demonstrierte Problem des Zeitleistenbezugs nicht besteht. Allerdings gilt hier dasselbe, was auch schon dort angemerkt wurde: Wenn der gesamte Code in der Hauptzeitleiste definiert und Module per loadMovie() geladen werden, können wir getrost auf _global verzichten.
10.4 Verschachtelung von Funktionen Ähnlich der Verschachtelung von MovieClips können Funktionen ihrerseits Funktionen enthalten bzw. aufrufen. Das haben wir in dem vorhergehenden Beispiel mehrfach gesehen. Zunächst weisen wir dem on Press-Ereignis eine anonyme Funktion zu, die ihrerseits die Funktion trace() aufruft. Einige Schritte danach lagern wir das trace() in eine eigene Funktion namens ausgabe() aus und rufen diese im on Press–Ereignis auf. Die verwendete Schreibweise lässt sich noch etwas vereinfachen, denn in solchen Fällen ist es möglich, die selbst definierte Funktion unmittelbar dem on Press-Ereignis zuzuweisen. Allerdings ändert sich dann der Selbstbezug bzw. der Inhalt des Schlüsselworts this. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects an beliebiger Stelle einen Kreis (50 × 50). 3. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Instanzname kreis, Registrierungspunkt beliebig). 4. Zeichnen Sie auf objects auf der Position 0,0 ein Rechteck (50 × 50). Sie brauchen es nicht in einen MovieClip zu verwandeln, da es uns lediglich zu Referenzzwecken dient. 5. Weisen Sie actions folgendes Bildskript zu: function klick(){
trace(„Sie haben auf „+this+“ ge klickt“);
10.5 Parameter
this._x += 20; }
kreis.onPress = function(){ klick(); }
Wenn Sie auf den Kreis klicken, erscheint im Nachrichtenfenster die Meldung „Sie haben auf _level0 geklickt“ und alle Objekte hüpfen um 20 Pixel nach rechts. Wir richten am Anfang die Funktion klick() ein, die eine Information ausgibt, wobei sie auf den Schlüsselbegriff this zugreift. Dieser Selbstbezeichner bezieht sich jeweils auf diejenige Zeitleiste, in der sich das Skript mit dem Begriff befindet. In unserem Fall handelt es sich um _root bzw. _le vel0, was mit der Hauptzeitleiste identisch ist. Danach bewegen wir das in this ermittelte Objekt um einen bestimmten Betrag nach rechts. Da sich this momentan auf die Hauptzeitleiste bezieht, muss das Ausführen des Skriptes die komplette Bühne mit allen sichtbaren Objekten verschieben, was auch tatsächlich geschieht. Danach weisen wir der Instanz kreis ein on Press-Ereignis zu, das bei Auftreten die Funktion klick() aufruft. Diese Schreibweise entspricht formal gesehen derjenigen, die wir in der vorhergehenden Übung kennen gelernt haben. 6. Ersetzen Sie das bestehende onPress-Ereignis komplett durch: kreis.onPress = klick;
Wenn Sie diesmal klicken, hüpft nur noch die Instanz kreis nach rechts und die Ausgabe im Nachrichtenfenster lautet „Sie haben auf _level0.kreis geklickt“. Flash behandelt die Funktion klick() nun so, als würde sie direkt als anonyme Funktion im onPressEreignis von kreis deklariert. Daher ändert sich der Selbstbezug von this, das nun nicht mehr auf die Hauptzeitleiste, sondern auf kreis verweist. Da nur noch kreis angesprochen wird, bleibt das Rechteck bei einem Mausklick auf den Kreis auf seiner Position. Bei dieser Schreibweise ist unbedingt darauf zu achten, dass Sie nicht aus Versehen die Zuweisung mit runden Klammern abschließen: kreis.onPress = klick();
Dies würde Flash korrekterweise als Funktionsauf-
89
ruf interpretieren, d. h. ohne dass wir auf den Kreis klicken, springt er wie von Geisterhand bewegt unmittelbar nach Start der Anwendung nach rechts und das Nachrichtenfenster gibt eine Meldung aus. Der Bezug von this würde wieder dem vorhergehenden entsprechen und die Hauptzeitleiste referenzieren. In manchen Fällen stellt das eine nützliche Möglichkeit dar, User-Ereignisse zu simulieren, ohne dass der Anwender tatsächlich agierte; in unserem konkreten Beispiel allerdings wäre das ein Fehler.
10.5 Parameter Wie wir gesehen haben, stellen Funktionen eine bedeutende Möglichkeit dar, Code zu vereinfachen und mehrfach verwendbar zu machen. Im Hinblick auf den letztgenannten Punkt erweist es sich als besonders wichtig, eine Funktion so zu definieren, dass sie nicht nur für einen ganz speziellen Fall gilt, sondern in möglichst vielen Situationen Anwendung findet. Nehmen wir das einfache Beispiel einer Textausgabe, die erfolgen soll, wenn wir auf bestimmte Objekte klicken. Abhängig davon, welchem Objekt wir unsere Zuwendung per Klick bezeugt haben, möchten wir einen anderen Text anzeigen und eine zugehörige Infografik einblenden. Wenn wir dafür eine einzige Funktion schreiben, ergibt sich das Problem der verschiedenen Texte. Da sie ja jeweils wechseln, können wir sie nicht direkt in die Funktionsdefinition aufnehmen. Die Lösung besteht in der Verwendung von Parametern, die eine etwas geänderte Funktionsdefinition erfordern: function Funktionsname(Parameter1:Daten typ):Void{ Anweisung1; Anweisung2; }
In die runden Klammern tragen wir die Parameter mit dem zugehörigen Datentyp ein. Dabei handelt es sich um eine besondere Art von Variablen: Sie besitzen nur in einem einzigen Augenblick einen konkreten Inhalt, nämlich zu dem Zeitpunkt des Funktionsaufrufs. Der große Vorteil besteht darin, dass wir eine Funktion mit wechselnden Parametern ausführen können. Was konkret dahinter steckt, wollen wir an dem angesprochenen Beispiel der Textausgabe anschauen.
90
Kapitel 10 Funktionen
1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie an beliebiger Position auf objects zwei verschiedenfarbige Kugeln (z. B. blau und orange). 3. Wandeln Sie die Objekte jeweils in MovieClips um (Bibliotheksname mcBlau und mcOrange, Instanznamen blau und orange, Registrierungspunkt jeweils beliebig). Achten Sie darauf, dass sich die Instanzen nicht überlappen. 4. Weisen Sie actions folgendes Bildskript zu:
textAusgabe(„blauer“);
function textAusgabe(psText:String):Void {
orange.onPress = function() {
trace(„Ich bin ein „+psText+“ Kreis“); }
blau.onPress = function() { textAusgabe(); };
Klicken Sie auf den Kreis blau, öffnet sich das Nachrichtenfenster und behauptet: „Ich bin ein undefined Kreis“. Der Aufruf der Funktion textAusgabe() sorgt für die Ausführung des trace()-Befehls. Dort steht nicht nur ein String („Ich bin ein “ sowie „ Kreis“), sondern auch ein Parameter bzw. eine Variable, die aber nirgends definiert wird. Daher enthält sie den Wert undefined. 5. Ändern Sie den Funktionsaufruf bei Mausklick wie folgt (Fettdruck):
};
Nun können alle Objekte angeklickt werden, wobei sie jeweils eine Textinformation ausgeben. Allerdings behaupten alle Kreise fälschlicherweise, ein blauer Kreis zu sein. 7. Korrigieren Sie die Übergabe von Argumenten an den Funktionsaufruf von textAusgabe() wie folgt (Fettdruck): textAusgabe(„orangener“); };
Jetzt gibt jedes Objekt bei einem Mausklick glaubhaft Auskunft über sich selbst. Beim Aufruf von text Ausgabe() wird jeweils ein anderes Argument als Parameter übergeben wie Abbildung 13 visualisiert. Die Instanz orange speichert in psText den String „orangener“, blau dagegen den String „blauer“. Auf diese Weise rufen zwar alle Objekte die gleiche Funktion auf, können Sie jedoch an eigene Bedürfnisse anpassen. Wir werden im Laufe zahlreicher Workshops mit parametrisierten Funktionen arbeiten, da sie eine ausgesprochen große Flexibilität bieten. Das vorliegende Skript kann man noch mit Hilfe von Arrays optimieren, für unsere Zwecke reicht es jedoch vollkommen aus.
textAusgabe(„blauer“);
Wenn Sie jetzt auf den Kreis klicken, bleibt er nicht mehr anonym, sondern meldet wahrheitsgemäß, er sei ein blauer Kreis. Beim Aufruf der Funktion text Ausgabe() wird in den Klammern der String „blauer“ mitgegeben. Er repräsentiert den konkreten Inhalt von psText, d. h. überall dort, wo innerhalb des Anweisungsblocks von textAusgabe() der Parameter psText verwendet wird, steht nun der konkrete String. Betrachten wir den Aufruf der parametrisierten Funktion, bezeichnet man den konkreten Klammerinhalt als Argument, in der entsprechenden Funktionsdeklaration dagegen verwendet man, wie oben gesehen, die Bezeichnung Parameter. 6. Weisen Sie der zweiten Kugel ebenfalls ein on Press-Ereignis zu: orange.onPress = function() {
Abbildung 13: Verwendung einer parametrisierten Funktion
10.6 Funktionen mit Rückgabewert (return)
Wir haben hier als Argument einen String übergeben, aber prinzipiell ist jeder Datentyp möglich, ob nun Number, Boolean, Array oder gar MovieClip. Selbst Funktionen können als Argument verwendet werden. Das wichtigste Beispiel wäre die vorgefertigte Funktion setInterval(), der man als Argument mitteilen muss, welche Funktion in welchem Zeitinterval ausgeführt werden soll. Obwohl wir jeweils nur einen Parameter in die Funktionsdeklaration aufgenommen haben, gibt es prinzipiell keine Obergrenze. Wir können also Funktionen mit beliebig vielen Parametern deklarieren – einmal abgesehen von der Tatsache, dass sich einem ungeschriebenen Naturgesetz der Programmierung zufolge die Lesbarkeit einer solchen Definition umgekehrt proportional zur Anzahl der Parameter verhält. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: function berechnung(pnOperand1: Number, pnOperand2:Number, pbAddition:Boolean) { var nErgebnis:Number; if (pbAddition) {
nErgebnis = pnOperand1 + pnOper and2; } else {
nErgebnis = pnOperand1 – pnOper and2; }
trace(„Die Berechnung ergibt: „+nErgebnis); }
berechnung(20,100,true);
berechnung(20,100,false);
Ausgabe im Nachrichtenfenster: Die Berechnung ergibt: 120 Die Berechnung ergibt: -80 Wir deklarieren die Funktion berechnung(), die über drei Parameter verfügt: pnOperand1 und pnOperand2 als Zahlen, mit denen gerechnet wird, sowie pbAddi tion, ein Bool’scher Wert, der festlegt, ob eine Addition oder eine Subtraktion erfolgen soll. Anschließend richten wir eine lokale Variable ein, die noch keinen Wert erhält. Sie dient dazu, das Ergebnis der Berechnung aufzunehmen. Abhängig vom Wert des Parameters
91
pbAddition erfolgt eine Addition (wenn true) oder eine Subtraktion (wenn false). Das Ergebnis wird zum Schluss im Nachrichtenfenster per trace() ausgege-
ben. Die Funktion wird zweimal aufgerufen, wobei die beiden Zahlenwerte identisch sind, aber einmal true und einmal false als Argument mitgegeben werden. Dadurch erreichen wir, dass beim ersten Aufruf eine Addition, beim zweiten eine Subtraktion erfolgt. Woher weiß Flash, welches Argument zu welchem Parameter gehört? Flash findet dies simpel über die Position innerhalb der Funktionsdeklaration sowie des Aufrufs heraus. Der Inhalt, der beim Aufruf beispielsweise an erster Stelle übergeben wird, entspricht auch dem Inhalt des ersten Parameters in der Deklaration. Wenn in der Deklaration der erste Parameter eine Zahl ist, muss beim Aufruf an dieser Stelle ebenfalls eine Zahl übergeben werden. Geschieht das nicht oder stimmt die Anzahl der Argumente nicht mit der Anzahl der Parameter überein, kommt es notwendigerweise zu einem Fehler. Fehlt ein Argument, ohne das es zu einer Typdiskrepanz mit den definierten Parametern kommt, erhalten wir leider wie so oft keine Fehlermeldung, sondern Flash, sich stumm in sein Schicksal fügend, führt die Funktion fehlerhaft aus. Die im Abschnitt „Verschachtelung von Funktionen“ behandelte Schreibweise läst sich mit parametrisierten Funktionen leider nicht anwenden. Denn Flash geht, wie Sie schon gesehen haben, automatisch von einem Funktionsaufruf aus, sobald einem Ereignis ein Ausdruck zugewiesen wird, der mit Klammern abschließt – und genau das wäre notwendig, weil wir ein Argument ja in eine Klammer schreiben müssen.
10.6 Funktionen mit Rückgabewert (return) Die bisherigen Funktionen verrichten mehr oder minder schweigend ihr Werk. Es gibt darüber hinaus eine besondere Art von Funktionen, die nicht nur irgendwelche Aktionen ausführen, sondern an die aufrufende Stelle einen Wert zurück liefern: function Funktionsname():Datentyp{ Anweisung1; Anweisung2;
return Wert; }
92
Nehmen wir an, wir müssen öfters eine Multiplikation durchführen. Dann könnten wir entweder eine parametrisierte Funktion schreiben, die das Ergebnis in eine Variable speichert, und anschließend diese Variable auslesen. Oder wir können einfacher eine Funktion mit einem Rückgabewert einsetzen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var nMulti:Number = 5;
Kapitel 10 Funktionen
trace(„Dies wird nach return igno riert!“);
Bei jedem Mausklick führt Flash wie bisher die Berechnung durch und ignoriert geflissentlich die eben eingefügte return-Anweisung. 4. Machen Sie die Gegenprobe, indem Sie den re turn-Befehl auskommentieren (Fettdruck): //return nErgebnis;
return nErgebnis;
Zwar berechnet Flash bei einem Mausklick wie bisher, doch die return-Anweisung existiert im Programm ablauf nicht mehr. Das Ergebnis der Berechnung wird daher auch nicht mehr ausgegeben. Statt dessen führt Flash den in Schritt 3 eingefügten trace()-Befehl aus.
}
this.onMouseDown = function() {
5. Heben Sie den im vorigen Schritt eingefügten Kommentar wieder auf.
nZahl++;
Das können wir uns im Fall der Fälle zunutze machen, indem wir den return-Befehl verwenden, ohne ihm Daten zwecks Rückgabe zuzuweisen. Auf diese Weise lässt sich eine Funktion an einer beliebigen Stelle beenden.
var nZahl:Number = 2;
function multiplikation(pOperand1) { var nErgebnis:Number = pOperand1*nMulti;
trace(multiplikation(nZahl)); };
Klicken Sie mehrfach mit der Maus auf die Bühne, erhalten Sie im Nachrichtenfenster Zahlen, die ab der Zahl 15 jeweils in Fünferschritten erhöht werden. Beim ersten Klick wird die Variable nZahl inkrementiert, beträgt also 1. Dieser Wert wird an die Funktion multiplikation() als Argument übergeben und dort mit 5 multipliziert. Das Ergebnis speichern wir in einer lokalen Variable, die anschließend an die aufrufende Stelle, in unserem Fall in den trace()Befehl, zurückgeschrieben wird. Damit enthält die Klammer von trace() direkt das Ergebnis der Berechnung. Der Zwischenschritt über die Variable nErgebnis ist zwar nicht zwingend notwendig, er erhöht aber die Lesbarkeit des Codes. Falls komplexere Berechnungen durchgeführt werden müssen, als deren Ergebnis nicht nur ein, sondern mehrere Werte vorliegen, können wir weder mit mehreren return noch mit einer kommaseparierten Aufzählung die betreffenden Daten zurückgeben. In einem solchen Fall speichern Sie Ihre Daten in einem Array und geben dieses zurück. Der return-Befehl beendet recht radikal die Funktion, so dass Befehle, die danach im Skript stehen, vollständig ignoriert werden. 3. Erweitern Sie die Funktionsdeklaration von mul tiplikation(), indem Sie unmittelbar nach dem return-Befehl folgende Zeile einfügen:
10.7 Von Flash zur Verfügung gestellte Funktionen Neben den von den Entwicklern selbst definierten Funktionen existieren zahlreiche vorgegebene Funktionen, die wir direkt verwendet können, ohne dass wir sie vorher deklarieren müssen. Denn die Deklaration wird ja bereits von Flash vorweg genommen. Als Entwickler haben wir darauf zunächst keinen Einfluss, was auch gut ist, da wir ansonsten u. U. Gefahr liefen, die Skriptsprache durch fehlerhafte Deklarationen funktionsunfähig zu machen. Wir haben bereits häufig mit solchen Funktionen gearbeitet – mittlerweile in Fleisch und Blut übergegangen sein müsste das Paradebeispiel, nämlich der trace()-Befehl. Wir können ihn unmittelbar verwenden, ohne Flash mitteilen zu müssen, was er mit diesem Befehl anstellen soll. Andere besonders bei Frame-basierten Animationen eingesetzte Beispiele sind gotoAndPlay(), stop(), play() etc. Einen Überblick über solche Funktionen bietet Ihnen die Flash-Hilfe sowie das Hilfspanel im Editor unter der Kategorie Globale Funktionen sowie jeweils un-
10.8 Rekursion
93 Abbildung 14: Vorgegebene Funktionen
ter den spezifischen Klassennamen. In letzterem Fall handelt es sich genau genommen um Methoden, die aber nichts anderes darstellen als Objekten zugeordnete Funktionen. Die meisten vorgefertigten Funktionen und Methoden verwenden Parameter, was ihnen größere Flexibilität verleiht. Manchmal allerdings meint es Macromedia/Adobe einfach zu gut mit uns und bietet schon mal Methoden an, die mit einem Dutzend oder mehr Parametern arbeiten. Wir werden solche Monster u. a. in den Kapiteln Text sowie Filter kennen und lieben lernen. Es spricht für Flash, dass diese vorgefertigten Funktionen keinesfalls in Stein gemeißelt sind. Sie lassen sich nämlich vom Entwickler anpassen, so dass wir im Prinzip in der Lage sind, eine ganz individuelle Flash-Version zu stricken. In der einfachsten Variante können wir per Prototyping auf die einzelnen Klassen und ihre Methoden zugreifen, um sie zu modifizieren und zu erweitern. Diese Vorgehensweise wurde viel in Flash MX und 2004 genutzt. Mit dem recht ausge-
feilten Klassenkonzept von Flash 8 und insbesondere ab CS3 bietet sich noch weit darüber hinaus gehende Möglichkeiten, die aber nicht mehr Gegenstand dieser Einführung sind.
10.8 Rekursion Eine Besonderheit stellen rekursive Funktionen dar, denn sie rufen sich ständig selbst auf. Das ist etwa so, als wollten Sie mit sich selbst kommunizieren, indem Sie beispielsweise andauernd Ihren eigenen Namen rufen – ein zugegebenermaßen etwas schizophrener Zeitvertreib. Für die Programmierung dagegen macht dies in bestimmten Situationen durchaus Sinn (natürlich nicht bezüglich Ihres Namens!). Wir können auf diese Weise etwa eine Endlosschleife für einen Hintergrundsound erstellen, Fraktale visualisieren oder per Programmierung organische Objekte wie Pflanzen zeichnen.
Kontrollstrukturen
In den meisten bisherigen Beispielen haben wir es mit einem sehr einfachen Programmablauf zu tun gehabt, denn die einzelnen Code-Zeilen wurden sukzessive ohne Unterbrechung abgearbeitet. Wie im richtigen Leben kommt es jedoch in der Programmierung oft vor, dass man vor eine Entscheidung gestellt wird oder dass eine Aktion mehrfach hintereinander ausgeführt werden soll. Programmiertechnisch handelt es sich dabei um Kontrollstrukturen in Form einer bedingten Ausführung bzw. einer wiederholten Ausführung, für die Flash jeweils spezielle Elemente zur Verfügung stellt.
11.1 Bedingungen Die Bedingung macht das Ausführen von Code abhängig von bestimmten Voraussetzungen, die der Entwickler selbst definiert. Wenn beispielsweise Ihre Website passwortgeschützt ist, dann kann der Anwender sie nur mit einem korrekten Passwort besuchen. Oder Sie erstellen eine Animation, bei der ein Objekt sich permanent zwischen dem rechten und linken Bühnenrand hin- und herpendelt. Wenn das Objekt einen Rand erreicht, muss es sich umdrehen und in die andere Richtung gehen. All diese Aktionen setzen voraus, dass eine bestimmte Bedingung erfüllt ist, bevor sie ausgeführt werden.
11
if(Bedingung){ //Anweisungen; }
Das Schlüsselwort if leitet die Anweisung ein. Ihr folgt als Argument in runden Klammern die Bedingung, die erfüllt sein muss, damit der in geschweiften Klammern eingeschlossene Code-Block ausgeführt wird. Trifft die Bedingung nicht zu, wird der zugehörige Code-Block ignoriert. Die Auswertung der Bedingung ergibt immer ein true oder false, je nachdem, ob das, was in der runden Klammer angegeben wurde, wahr ist oder nicht. Unter Verwendung obiger Beispiele würden unsere if-Anweisungen lauten: if(Passwort == richtig){ Zugang; }
oder: if(Objekt erreicht Rand){ Objekt umdrehen; Objekt bewegen; }
Ein konkretes Beispiel sähe dann so aus: 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: if(true){
11.1.1 Die if-Anweisung
trace(„Wahr“);
Solche Bedingungen lassen sich mit einer if-Anweisung formulieren, die in allgemeiner Form so aussieht:
Ausgabe im Nachrichtenfenster: Wahr
}
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
95
96
Beim Ausführen kontrolliert Flash, ob die Bedingung wahr ist. Da true immer wahr ist, denn als Konstante kann sie nie einen anderen Wert als den bereits enthaltenen Wert annehmen, und der entspricht nun mal true, führt Flash den trace()-Befehl aus. 3. Ersetzen Sie in der Bedingung den Wert true durch false (Fettdruck): if(false){
Jetzt erfolgt keine Ausgabe im Nachrichtenfenster, denn die angefragte Konstante enthält den Wert false. In dieser Form macht die if-Anweisung nicht allzu viel Sinn. Denn dass true immer true und false immer false entspricht, dürfte kaum eine Überraschung darstellen. Da die Bedingung jeden beliebigen Ausdruck enthalten darf, kontrolliert man oft Eigenschaften oder Inhalte von Variablen mit Hilfe von Vergleichsoperatoren. Dabei können die Variablen jeden Datentyp, und daher, bezogen auf obiges Beispiel, natürlich auch Boolsche Werte enthalten.
Kapitel 11 Kontrollstrukturen
if (bStart){
Ausgabe im Nachrichtenfenster: Das Spiel kann beginnen! Die Ausgabe ändert sich nicht gegenüber vorher, denn Flash führt automatisch einen Vergleich gegen true aus, selbst wenn wir die entsprechende Anweisung streichen. Anders formuliert liest Flash unsere Bedingung genauso, wie sie vor der Streichung in Schritt 4 formuliert war. Der Vollständigkeit halber führen Sie den Test mit dem Umkehrwert von bStart aus. 6. Ändern Sie die Initialisierung von bStart (Fettdruck): var bStart:Boolean = false;
Jetzt erfolgt keine Ausgabe im Nachrichtenfenster, denn der betreffende Code hängt ja davon ab, dass die Bedingung true ergibt. Das ist jedoch aufgrund der Initialisierung der Variablen mit false nicht mehr der Fall.
4. Fügen Sie folgende Codezeilen hinzu:
7. Ändern Sie die Bedingung wie folgt (Fettdruck):
var bStart:Boolean = true;
if (bStart == false){
if (bStart == true){
trace(„Das Spiel kann beginnen!“);
Ausgabe im Nachrichtenfenster: Das Spiel kann beginnen!
}
Nun erscheint wieder die Meldung im Nachrichtenfenster (wobei man durchaus darüber diskutieren kann, ob ein Spiel ausgerechnet dann beginnen soll, wenn die Startvariable auf false gesetzt wird). Obwohl der Inhalt der Variablen bStart false ist, gibt der Ausdruck in der Bedingung true zurück. Denn dort steht, um es in beredter Prosa zu formulieren: „Der Inhalt von bStart beträgt false“, und das trifft bekanntermaßen aufgrund der Initialisierung zu. Analog zur oben vorgestellten Kurzform lässt sich diese Bedingung abkürzen.
Ausgabe im Nachrichtenfenster: Das Spiel kann beginnen! Die mit true initialisierte Variable bStart wird in der if-Anweisung evaluiert. Da sie wahr ist, gibt die Anweisung true zurück und führt den trace()-Befehl aus. Anders als zuvor kann die Variable bStart zu verschiedenen Zeitpunkten verschiedene Werte (true oder false) enthalten. Der von der if-Anweisung abhängige Code-Block wird also nicht in jedem Fall ausgeführt bzw. nicht in jedem Fall ignoriert. Wenn Sie Boolsche Werte abfragen wollen, dann macht es dementsprechend keinen Sinn, direkt mit der betreffenden Konstante zu arbeiten. Statt dessen verwendet man wie in diesem Beispiel eine Variable. Die Bedingung lässt sich in verkürzter Form schreiben, wenn getestet werden soll, ob der Variableninhalt true ist. 5. Ändern Sie die Bedingung, indem Sie den Vergleichsoperator und den nachfolgenden Vergleichswert streichen:
8. Ändern Sie die Bedingung wie folgt (Fettdruck): if (!bStart){
Ausgabe im Nachrichtenfenster: Das Spiel kann beginnen! Die Bedingung lautet nun: „Der Inhalt von bStart beträgt nicht true“, was in unserem Fall der Aussage „Der Inhalt von bStart beträgt false“ entspricht. Wie Sie sich sicher erinnern, kehren wir durch den Operator ! etwas in sein Gegenteil um.
11.1 Bedingungen
97
Selbstverständlich ist die Bedingung nicht auf Variablen mit Bool’schen Werten beschränkt, sondern andere Datentypen sind ebenso möglich wie die Verwendung komplexer Ausdrücke.
Umfasst der von der Bedingung abhängige CodeBlock nur eine einzige Zeile wie in vorliegendem Fall, kann die gesamte Anweisung in verkürzter Form geschrieben werden.
9. Ergänzen Sie das Skript:
12. Fügen Sie nach dem bisherigen Code folgende Zeile ein:
var sName:String = „Sven Regener“; if(sName == „Sven Regener“){
if(sName == sGesucht) trace(„Der Autor lautet: „+ sName);
}
Ausgabe im Nachrichtenfenster: Der Autor lautet: Sven Regener
trace(„Der Autor lautet: ”+ sName);
Ausgabe im Nachrichtenfenster: Der Autor lautet: Sven Regener Der Variablen sName weisen wir den String „Sven Regener“ zu. Anschließend kontrollieren wir, ob der Variableninhalt mit einem String innerhalb der Bedingung übereinstimmt, was hier zutrifft. 10. Ändern Sie das Skript (Fettdruck):
var sName:String = „Sven Regener“;
if(sName == sGesucht){
var sGesucht:String;
trace(„Der Autor lautet: ”+ sGesucht);
}
In der Bedingung tauschen wir den Klarnamen, gegen den wir testen, mit einer weiteren Variablen aus. Nun wird Variableninhalt mit Variableninhalt verglichen. Stimmen beide überein, gibt die Bedingung true zurück und der eingeschlossene Code wird wie gehabt ausgeführt. In unserem konkreten Beispiel allerdings erfolgt keine Ausgabe im Nachrichtenfenster. Denn die Variable sGesucht wurde zwar deklariert, aber wir haben ihr keinen Wert zugewiesen. Damit erhält sie automatisch den Wert undefined. Für Flash lautet die Bedingung dann: if(„Sven Regener“ == undefined){
Das trifft eindeutig nicht zu, ergibt also false.
Wir erhalten im Nachrichtenfenster zweimal dieselbe Ausgabe, nämlich einmal von der vorhergehenden, dreizeiligen if-Anweisung, und einmal von der aktuellen Kurzschreibweise. Welche Schreibweise Sie konkret wählen, hängt letzten Endes von Ihren Vorlieben ab. Die dreizeilige hat den Vorteil der besseren Lesbarkeit in längerem Code, da Sie durch die automatische Einrückung leichter erkennen können, dass hier eine if-Anweisung vorliegt. Zudem ist eine Erweiterung einfacher möglich, da man ohne Änderung des bisherigen Codes für weitere Anweisungen nur neue Zeilen einfügen muss. Andererseits spart die einzeilige Anweisung natürlich Schreibarbeit und macht den professionelleren Eindruck (doch, doch, das Auge isst mit). Wir verwenden im Nachfolgenden durchgehend die mehrzeilige Schreibweise. Das Element, das wir in einer Bedingung auswerten, kann sich im Laufe der Anwendung ändern, was insbesondere bei Animationen, bei denen spezifische Objekteigenschaften abgefragt werden, der Fall ist. Wir werden im Rahmen zahlreicher Workshops darauf zurückkommen. Dazu ein einfaches Beispiel (hier ohne Animation): 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var nKlick:Number = 0;
this.onMouseDown = function(){
11. Ändern Sie das Skript (Fettdruck):
nKlick++;
var sGesucht:String = „Sven Regener“;
if(nKlick == 5){
Ausgabe im Nachrichtenfenster: Der Autor lautet: Sven Regener Jetzt stimmen wie beim ersten Beispiel beide Strings wieder miteinander überein, so dass der abhängige Code ausgeführt werden kann.
trace(„nKlick: „+nKlick); trace(„Sie haben fünfmal die Maustaste gedrückt!“); delete this.onMouseDown; } }
98
Testen Sie, indem Sie mehrfach auf die Bühne klicken. Achten Sie dabei darauf, nicht in das geöffnete Nachrichtenfenster zu klicken, denn diese Klicks werden nicht von der Bühne des Flashfilms ausgewertet. Bei jedem Mausklick erscheint im Nachrichtenfenster: nKlick: 1 beziehungsweise: nKlick: 2 usw., bis der Wert 5 erreicht. Dann erfolgt abschließend die Ausgabe: Sie haben fünfmal die Maustaste gedrückt! Danach hüllt sich Flash in intensives Schweigen. Jeder Klick erhöht die Variable nKlick um eins. Gleichzeitig kontrollieren wir, ob bereits der Wert 5 erreicht wurde. Ist das der Fall, geben wir eine entsprechende Meldung aus und löschen den Mausklick. Die Bedingung in der if-Anweisung trifft anfangs nicht zu. Erst nach mehrfachen Änderungen der abgefragten Variablen wird der definierte Grenzwert erreicht, und Flash führt anschließend den nur für diesen konkreten Fall vorgesehenen Code aus.
Kapitel 11 Kontrollstrukturen
Wir initialisieren nZahl mit dem Wert 100 und fragen zunächst ab, ob diese Zahl größer 10 ist. Da dies zutrifft, führt Flash die zugehörige trace()-Anweisung aus und fragt nach, ob sie zugleich kleiner 100 ist. Auch diese Bedingung gibt true zurück, so dass Flash die entsprechende trace()-Anweisung aufruft. Beachten Sie, dass die innere if-Anweisung von der äußeren if-Anweisung abhängt. Falls die äußere ein false ergibt (z. B. mit dem Wert 60 anstatt 10), wird die innere nicht mehr getestet. Der umgekehrte Fall, nämlich das Zutreffen nur der äußeren Bedingung, ist problemlos möglich. Dazu müssen Sie lediglich die innere Bedingung ändern (z. B. 40 anstatt 100 verwenden), so dass Flash nur noch die erste trace()Anweisung ausführt. Eine Kurzschreibweise ermöglicht die Verwendung des logischen und-Operators in Form von &&. Sie ist jedoch nicht vollständig identisch mit der verschachtelten if-Anweisung. 3. Fügen Sie nach der letzten schließenden Klammer außerhalb der if-Anweisung folgende Zeilen ein: if (nZahl>10 && nZahl<100) {
trace(„nZahl ist größer 10“);
11.1.2 Verschachtelte if-Anweisungen, logisches und, logisches oder Eine if-Anweisung lässt sich beliebig tief verschachteln. Sie kann also ihrerseits eine oder mehrere ifAnweisungen enthalten. Falls wir beispielsweise wissen wollen, ob der Wert einer Variablen innerhalb eines gewissen Bereichs liegt, könnte man mit einer derartigen Verschachtelung arbeiten. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var nZahl:Number = 50; if(nZahl > 10){
trace(„nZahl ist kleiner 100“); }
Ausgabe im Nachrichtenfenster: nZahl ist größer 10 nZahl ist kleiner 100 Zunächst ergibt sich ein gleicher Ablauf wie zuvor: Flash testet, ob die angegebene Variable größer 10 und gleichzeitig kleiner 100 ist. Da der Test true ergibt, erfolgt die gewohnte Ausgabe im Nachrichtenfenster. Allerdings verknüpft der und-Operator beide Ausdrücke miteinander, so dass beide true ergeben müssen, ehe die nachfolgende Anweisung beachtet wird. Trifft auch nur eine der Bedingungen nicht zu, ignoriert Flash die trace()-Anweisung.
trace(„nZahl ist größer 10“)
4. Ändern Sie in dieser Bedingung testweise den Wert 100 auf 40 (Fettdruck):
trace(„nZahl ist kleiner 100“);
if (nZahl>10 && nZahl<40) {
if(nZahl < 100){ } }
Ausgabe im Nachrichtenfenster: nZahl ist größer 10 nZahl ist kleiner 100
Die erste Bedingung trifft zwar noch zu, die zweite dagegen nicht mehr. Daher ergibt der gesamte Ausdruck in den runden Klammern false, so dass die trace()-Anweisung nicht zur Ausführung kommt. Welchen Wert die durch den und-Operator verknüpf-
11.1 Bedingungen
ten Bedingungen ergeben, lässt sich einfach mit einem trace() kontrollieren. 5. Erweitern Sie den Code um diese Zeile: trace(nZahl>10 && nZahl<40);
Ausgabe im Nachrichtenfenster: false Flash wertet den kompletten Ausdruck, den wir trace() als Argument übergeben, aus und stellt fest, dass der zweite Teilausdruck, nämlich nZahl<40, nicht zutrifft. Damit ergibt sich für den gesamten Ausdruck false, wie im Nachrichtenfenster angezeigt wird. 6. Ändern Sie sowohl in der if-Anweisung wie in der trace()-Anweisung die 40 in eine 100 (Fettdruck): if (nZahl>10 && nZahl<100) {
und: trace(nZahl>10 && nZahl<100);
Ausgabe im Nachrichtenfenster: nZahl ist größer 10 nZahl ist kleiner 100 true Da beide Bedingungen zutreffen, ergibt der gesamte Ausdruck true und die Welt ist wieder in Ordnung. Mit einem logischen oder bzw. || können wir festlegen, dass es ausreicht, wenn nur eine der Bedingungen zutrifft. Dabei spielt es, anders als bei der zuvor behandelten verschachtelten if-Anweisung keine Rolle, an welcher Stelle sich diese Bedingung befindet. 7. Erweitern Sie den Code um diese Zeilen: if (nZahl>10 || nZahl<100) { trace(„Logisches oder:“);
trace(„nZahl ist größer 10“);
trace(„nZahl ist kleiner 100“); }
trace(nZahl>10 || nZahl<100);
Ausgabe im Nachrichtenfenster: Logisches oder: nZahl ist größer 10 nZahl ist kleiner 100 true
99
Wenig überraschend meldet Flash wie zuvor, die getestete Zahl sei größer 10 und kleiner 100. Unsere ifAnweisung unterscheidet sich so prinzipiell nicht von der vorhergehenden mit dem logischen und. Anders sieht es jedoch aus, wenn wir eine der Bedingungen so wählen, dass sie nicht zutrifft. Ändern Sie den ersten Wert innerhalb der if-Anweisung sowie bei der letzten trace()-Anweisung (Fettdruck): if (nZahl>60 || nZahl<100) {
und: trace(nZahl>60 || nZahl<100);
Ausgabe im Nachrichtenfenster: Logisches oder: nZahl ist größer 10 nZahl ist kleiner 100 true Die Ausgabe ändert sich nicht. Während beim logischen und alle Bedingungen zutreffen müssen, reicht beim logischen oder eine korrekte Bedingung aus. Der Code wird also in jedem Fall vollständig ausgeführt, solange auch nur eine einzige der Bedingungen den Wert true zurückgibt.
11.1.3 if else Bei manchen Aktionen reicht es nicht aus, nur eine Bedingung ohne Alternative zu verwenden. Denken Sie zurück an das Beispiel der passwortgeschützten Site. Gibt der Anwender das korrekte Passwort ein, gewinnt er Zugang zur Site. Doch wie verhält es sich für den Fall einer falschen Passworteingabe? Auch dann muss eine Reaktion erfolgen, da der Anwender sonst nicht weiß, was geschehen ist. Eine derartige Alternative lässt sich in einer if-Bedingung mit dem Schlüsselwort else definieren. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf der Ebene objects an beliebiger Stelle der Bühne einen Kreis (50 × 50); 3. Wandeln Sie den Kreis in einen MovieClip um (Bibliotheksname beliebig, Instanzname kreis, Registrierungspunkt mittig). 4. Weisen Sie actions folgendes Bildskript zu: if(kreis._x < Stage.width/2){
100
trace(„Linke Seite“); }else{
trace(„Rechte Seite“); }
Je nachdem wo sich der Kreis auf Ihrer Bühne befindet, erhalten Sie einen der beiden Strings als Ausgabe im Nachrichtenfenster. Das Schlüsselwort else definiert einen alternativen Anweisungsblock, der dann automatisch auszuführen ist, wenn die ifBedingung nicht zutrifft. Nehmen wir an, Ihr MovieClip befindet sich auf der x-Position 111. Dann gibt Flash die Meldung „Linke Seite“ aus. Liegt er dagegen beispielsweise auf 666, erhalten Sie die Ausgabe „Rechte Seite“. Denn die if-Bedingung gibt in dem Fall false zurück und Flash ruft den Code im elseFall auf. Auch für diese Form existiert eine Kurzschreibweise. 5. Ergänzen Sie den Code um folgende Zeile: (kreis._x < Stage.width/2)? trace(„Linke Seite“):trace(„Rechte Seite“);
Diese Schreibweise ähnelt stark der bereits vorgestellten einzeiligen Schreibweise für die einfache if-Anweisung. In allgemeiner Form sieht sie so aus: Bedingung ? Anweisung1 : Anweisung2;
Dabei entspricht Anweisung1 dem Code, der bei Zutreffen der if-Bedingung ausgeführt wird, während Anweisung2 für den else-Fall steht. Das Fragezeichen oder, genauer, der Bedingungsoperator trennt die Bedingung von den Anweisungsblöcken. Hier gilt das gleiche wie das, was für die erwähnte Kurzschreibweise der einfachen if-Anweisung gesagt wurde. Insbesondere aus Gründen der leichteren Lesbarkeit verwenden wir im Nachfolgenden die mehrzeilige Form.
11.1.4 else if Eigentlich haben wir in dem vorhergehenden Beispiel mit dem Kreis ein bisschen gemogelt – Asche auf unser Haupt! Denn was geschieht, wenn sich der Kreis exakt in der Bühnenmitte befindet? Unserer Abfrage zufolge würde er dann den rechtsseitigen
Kapitel 11 Kontrollstrukturen
Kreisen zugeschlagen, da wir nur die zwei Fälle „links“ und „alle anderen“ unterscheiden, d. h. wir können nur einen von zwei Code-Blöcken ausführen. Damit würde ein Fall, der durchaus eintreten kann, in die falsche Kategorie eingeordnet. Mit den Schlüsselwörtern else if dagegen können wir eine beliebige Anzahl an Anweisungen aufrufen und damit auch einen dritten Fall für obiges Beispiel definieren. 6. Ändern Sie das Skript aus dem Kreisbeispiel wie folgt (Fettdruck): if(kreis._x < Stage.width/2){
trace(„Der Kreis befindet sich auf der linken Seite“); }else if(kreis._x > Stage.width){
trace(„Der Kreis befindet sich auf der rechten Seite“); }else{
t race(„Der Kreis befindet sich exakt in der Mitte“) }
Unsere if-Anweisung besteht aus drei verschiedenen abhängigen Code-Blöcken, die sich in beredter Prosa folgendermaßen formulieren lassen:
• if: „Wenn die x-Position des Kreises kleiner als die halbe Bühnenbreite ist“ if: „Oder wenn die x-Position des Kreises größer als die halbe Bühnenbreite ist“ • else: „Oder wenn keine der vorgenannten Bedingungen zutrifft“
• else
Damit sind alle Möglichkeiten in unserem konkreten Fall abgedeckt und Flash weiß genau, welcher CodeBlock ausgeführt werden muss. Es gibt keine Obergrenze für die Anzahl der else if-Anweisungen, wir könnten also theoretisch Dutzende hintereinanderschalten (falls es tatsächlich so viele Fälle zu unterscheiden gäbe). Wenn wir uns den Code anschauen, ähnelt er stark einer Aneinanderreihung von einfachen if-Anweisungen. Warum verwendet man dann nicht drei derartige if-Anweisungen anstelle von if, if else und else? Der grundlegende Unterschied besteht darin, dass ein else immer nur einen eindeutigen Fall zulässt, also ausschließlich ist, während die mehrfache Verwendung von einfachen if-Anweisungen in bestimmten Fällen mehrfach true ergeben kann.
11.1 Bedingungen
7. Positionieren Sie die Instanz kreis auf x = 150, y = beliebiger Wert. 8. Ersetzen Sie den Code vollständig durch: var nPos:Number = kreis._x; if (nPos >50) {
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 <= 10; i++){ trace(i); }
Ausgabe im Nachrichtenfenster: 1 2 3 4 5 6 7 8 9 10 Als Startwert legen wir in der Variablen i den Wert 1 fest. Unsere Bedingung lautet, dass alle Zahlen, die kleiner oder gleich 10 sind, als zulässig gelten.
Kapitel 11 Kontrollstrukturen
Abschließend definieren wir, dass wir per Inkrement vom Startwert 1 zum Endewert 10 gelangen. Solange die Bedingung zutrifft, also i kleiner oder gleich 10 ist, führt Flash die trace()-Anweisung aus. Die Variable, die für Initialisierung und Bedingung verwendet wird, heißt Zählvariable. Es hat sich eingebürgert, dafür die Kleinbuchstaben i, j etc. zu verwenden und auf eine Deklaration außerhalb der Schleife, wie wir sie bei anderen Variablen kennen gelernt haben, zu verzichten. Selbstverständlich können Sie andere Variablennamen verwenden; wir wollen uns im Nachfolgenden allerdings an dieser Konvention orientieren. Achten Sie darauf, entgegen sonstiger Gewohnheit die Argumente in der Klammer per Semikolon anstelle eines Kommas zu trennen. Wie führt Flash den Code aus? Zunächst wird eine Variable mit dem Wert i initialisiert. Das geschieht genau ein einziges Mal, nämlich zu Beginn des Schleifenaufrufs. Dann kontrolliert Flash, ob der Initialwert der Bedingung entspricht. Das trifft in unserem Fall zu. Anschließend wird die trace()-Anweisung ausgeführt. Gelangt Flash ans Ende der Anweisungen – der Code-Block kann ja mehr als nur einen einzigen Ausdruck enthalten wie in unserem Beispiel –, wird i inkrementiert. Die Addition erfolgt erst zum Schluss, da wir ein Post-Inkrement verwenden. Bei einem PräInkrement würde i erst hochgezählt, anschließend käme der Anweisungsblock mit dem trace() zum Zug. Nun springt Flash wieder an den Schleifenbeginn und kontrolliert, ob der neue Wert von i noch der Bedingung entspricht. Ist das der Fall, wird derselbe Prozess, d. h. Abarbeiten aller Anweisungen, wieder durchgeführt. Nach dem nächsten Inkrement erfolgt eine erneute Kontrolle. Das wiederholt sich solange, bis i den in der Bedingung genannten Wert übersteigt. Dann springt Flash sofort ans Ende der Schleife und beendet sie, ohne die darin enthaltenen Anweisungen erneut auszuführen. Da wir uns i jeweils ausgeben lassen, können wir genau erkennen, welchen Wert die Zählvariable innerhalb der Schleife besitzt. Beachten Sie, dass i, obwohl innerhalb der Schleife als lokale Variable definiert, nach Ablauf der Schleife weiterhin existiert und den ersten Wert enthält, der nicht mehr die Schleifenbedingung erfüllt. 3. Fügen Sie nach der schließenden Klammer der Schleife folgende Zeile ein: trace(„Nach der Schleife beträgt i: “+i);
11.2 Schleifen
105
Ausgabe im Nachrichtenfenster: Nach der Schleife beträgt i: 11
die Schleife einfach solange aus, bis keine der Bedingungen mehr gilt.
Wie Sie erkennen, zählt Flash bis zum ersten nicht mehr gültigen Wert, hier 11, hoch. Dieser Wert wird nicht mehr innerhalb der Schleife, sondern nach ihrem Ende ausgegeben. Daraus ergibt sich gerade für Anfänger eine Fehlerquelle: Außerhalb der Schleife besteht keine Möglichkeit, auf die Schleifenwerte von i zuzugreifen. Statt dessen werden Sie immer den ersten nicht mehr gültigen Wert erhalten, in unserem konkreten Beispiel die Zahl 11. Wie wir in einigen Workshops sehen werden, stellt das mitunter ein Problem dar, so dass wir dort nach Möglichkeiten suchen müssen, die einzelnen Zählwerte permanent zu speichern. Der Schleifenkopf, d. h. die Stelle, an der die Schleife definiert wird, kann mehrere Initialisierungen enthalten.
5. Ändern Sie den Schleifenkopf wie folgt (Fettdruck):
4. Ändern Sie den bisherigen Code wie folgt (Fettdruck): for(var i:Number = 1, j:Number = 1; i <= 10; i++, j--){ trace(„i: „+i+“, j: „+j); }
Ausgabe im Nachrichtenfenster: i: 1, j: 1 i: 2, j: 0 i: 3, j: -1 i: 4, j: -2 i: 5, j: -3 i: 6, j: -4 i: 7, j: -5 i: 8, j: -6 i: 9, j: -7 i: 10, j: -8 Der Ablauf entspricht demjenigen von zuvor, lediglich die Variable j tritt hinzu. Sie wird mit dem gleichen Wert wie i initialisiert, aber schrittweise dekrementiert, also jeweils um 1 vermindert. Die Anzahl der Schritte wird weiterhin durch i vorgegeben. Die Zählvariable j wird nur solange dekrementiert, wie wir i inkrementieren. Dadurch erhalten wir wiederum 10 verschiedene Ausgaben im Nachrichtenfenster. Stimmt die Anzahl der Schleifendurchläufe, die sich aus Bedingung und Änderung ergibt, für verschiedene Zählvariablen nicht miteinander überein, führt Flash
for(var i:Number = 1, j:Number = 1; i <= 10, j<=12; i++, j++){
Ausgabe im Nachrichtenfenster: i: 1, j: 1 i: 2, j: 2 i: 3, j: 3 i: 4, j: 4 i: 5, j: 5 i: 6, j: 6 i: 7, j: 7 i: 8, j: 8 i: 9, j: 9 i: 10, j: 10 i: 11, j: 11 i: 12, j: 12 Über Bedingung und Änderung der i-Variablen legen wir 10, im Fall der j-Variable aber 12 Schleifendurchläufe fest. Da eine Schleife in jedem Fall solange ausgeführt wird, wie eine Bedingung zutrifft, bestimmt in unserem Beispiel j die Gesamtanzahl der Durchläufe. Das führt allerdings automatisch dazu, dass i trotz Erreichen des Grenzwerts von 10 weiter inkrementiert wird. Daher müssen Sie bei Schleifen dieser Art darauf achten, nicht durch logisch falsche Argumente zu fehlerhaften Werten zu gelangen. Davon abgesehen erweisen sich solche Schleifen als schwer lesbar und sollten daher möglichst selten verwendet werden. Wie im Fall der if-Anweisung erlaubt die Schleife eine beliebige Verschachtelung. Das erscheint vielleicht unlogisch, denn wozu sollten wir einen Anweisungsblock, der einzelne Aktionen mehrfach ausführt, seinerseits mehrfach aufrufen? Eine solche Schleife in der Schleife stellt beispielsweise eine einfache Lösungsmöglichkeit für eine gekachelte Fläche dar, also eine Fläche, die wie eine Tabelle aus Spalten und Zeilen besteht, deren Inhalte Grafiken bilden. Sie kann für grafische Effekte ebenso eingesetzt werden wie als Spielfeld. In verschiedenen Workshops werden wir ausführlichere Einsatzbeispiele kennen lernen. Hier wollen wir uns mit einem grundlegenden Beispiel begnügen. 1. Erstellen Sie eine Standarddatei.
106
2. Weisen Sie actions folgendes Bildskript zu: for (var i:Number = 1; i<=3; i++) {
Kapitel 11 Kontrollstrukturen
3. Fügen Sie vor Beginn der Schleife folgende Zeile ein:
for (var j:Number = 1; j<=3; j++) {
trace(getTimer() + „ Millisekunden vor Schleifenaufruf“);
}
4. Fügen Sie nach Ende der Schleife folgende Zeile ein:
trace(„i: „+i+“, j: „+j); }
Ausgabe im Nachrichtenfenster: i: 1, j: 1 i: 1, j: 2 i: 1, j: 3 i: 2, j: 1 i: 2, j: 2 i: 2, j: 3 i: 3, j: 1 i: 3, j: 2 i: 3, j: 3 In der ersten Zeile, also der äußeren Schleife, legen wir fest, dass ein Anweisungsblock insgesamt dreimal auszuführen ist. Besagter Anweisungsblock wird in der zweiten Zeile definiert. Dabei handelt es sich ebenfalls um eine Schleife, die einen trace()-Befehl dreimal ausführt. Dadurch erhalten wir insgesamt 3 × 3 = 9 Aufrufe von trace(). Im Einzelnen geschieht folgendes: Wenn i mit 1 initialisiert wird, beginnt die zweite Schleife mit der Initialisierung von j, dem ebenfalls 1 zugewiesen wird. Dann gelangt Flash ans Ende der inneren Schleife, setzt j auf 2, kehrt zurück zum Schleifenbeginn und kontrolliert, ob die Bedingung noch gilt. Da dies zutrifft (2 ist kleiner 3), wird erneut der trace()-Befehl ausgeführt, addiert und kontrolliert. Auch der j-Wert von 3 erfüllt noch die Bedingung und derselbe Prozess folgt noch mal. Da j abschließend den Wert 4 besitzt, gilt die Bedingung nicht mehr, und der weitere Ablauf wird nun wieder von der äußeren Schleife gesteuert: Erhöhung von i auf 2, Kontrolle der Bedingung, Ausführung der inneren Schleife mit der gleichen Prozedur wie eben beschrieben. Dieser Kreislauf läuft solange ab, bis die Bedingung in der äußeren Schleife nicht mehr erfüllt ist und Flash unmittelbar ans Schleifenende springt. So komplex das auch aussehen mag, die Ausführung erfolgt im Bruchteil einer Sekunde, da die Anweisungen alles andere als rechenintensiv sind. Das können wir kontrollieren, indem wir die abgelaufene Zeit messen.
trace(getTimer() + „ Millisekunden nach Schleifenaufruf“);
Ausgabe im Nachrichtenfenster: 10 Millisekunden vor Schleifenaufruf //Ausgabe von i und j 10 Millisekunden nach Schleifenaufruf Falls Sie nicht gerade über einen jener museumsreifen, noch dampfbetriebenen Computer verfügen, erhalten Sie eine recht niedrige Zahl, die in beiden Fällen identisch ist. Sie entspricht der Anzahl der Millisekunden, die seit erstmaligem Aufruf des Flash-Players vergangen sind. Die Schleifenausführung dauerte noch nicht einmal eine Millisekunde. Da wir viel mit visuell wahrnehmbaren Objekten arbeiten werden, bilden Schleifen ein nützliches Hilfsmittel, um dynamisch solche Objekte einzublenden und zu benennen. Nehmen wir an, auf der Bühne befinden sich fünf Instanzen kreis1 bis kreis5 an beliebiger Stelle, die wir nebeneinander in einem Abstand von 60 Pixeln anordnen wollen. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf der Ebene objects an beliebiger Stelle einen Kreis (50 × 50). 3. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Instanzname kreis0, Registrierungspunkt links oben). 4. Duplizieren Sie die Instanz viermal und weisen jeweils folgende Instanznamen zu: kreis1 bis kreis4. 5. Weisen Sie actions folgendes Bildskript zu: kreis0._x = 0;
kreis0._y = 100; kreis1._x = 60;
kreis1._y = 100; kreis2._x = 120; kreis2._y = 100; kreis3._x = 180; kreis3._y = 100;
11.2 Schleifen
kreis4._x = 240; kreis4._y = 100;
6. Nachdem Sie das Ergebnis bewundert haben, duplizieren Sie die Kreisinstanz weitere 10-mal, erweitern dementsprechend das Bildskript und ärgern sich über den enormen Mehraufwand, der sich selbstverständlich mit Hilfe einer Schleife, wie sie im nächsten Schritt definiert wird, einsparen lässt. 7. Ersetzen Sie den vollständigen Code durch: var nAnzahl:Number = 5;
var nAbstand:Number = 60;
for (var i:Number = 0; i
this[„kreis“+i]._x = i*nAbstand; this[„kreis“+i]._y = 100; }
Wie auf einer Schnur aufgereiht liegen Ihre fünf Kreise nebeneinander auf der Bühne. Wir initialisieren zunächst eine Variable für die Gesamtzahl der Kreise sowie für einen gleichmäßigen Abstand. Anschließend rufen wir eine Schleife auf, deren Anzahl an Durchläufen der Variablen nAnzahl entspricht. Lassen Sie sich nicht von der Bedingung i < nAnzahl anstatt von i <= nAnzahl irritieren. Denn da i mit 0 statt 1 beginnt, erhalten wir genauso viele Durchläufe als wenn unsere Schleife lauten würde: for (var i:Number = 1; i <= nAnzahl; i++) {
Innerhalb der Schleife setzen wir die benötigten In stanznamen dynamisch zusammen. Wir wissen, dass sie alle den String „kreis“ zuzüglich einer Zahl von 0 bis 4 enthalten. Daraus machen wir unter Zuhilfenahme der Zählvariablen i den Ausdruck „kreis“+i. Damit Flash darin einen Instanznamen erkennt, müssen wir den Bezug zu einer Zeitleiste herstellen, der hier mit Hilfe des Bezeichners this und den nachgestellten Klammern [] erfolgt. Wenn i beim erstmaligen Schleifendurchlauf 0 beträgt, lautet die Anweisung in der 4. Zeile im Klartext: this[„kreis“+0]._x = 0*60;
bzw.: this[„kreis0“]._x = 0*60;
was wiederum ergibt:
107
kreis0._x = 0;
Da 0 mit 60 multipliziert magere 0 ergibt, ist diese Zeile identisch mit der ersten Zeile aus Schritt 5. Sie setzt die Instanz kreis0 auf die x-Position 0, während die darauf folgende Zeile als y-Position 100 zuweist. Anschließend inkrementiert Flash i, so dass die Anweisung in der 4. Zeile konkret lautet: this[„kreis“+1]._x = 1*60;
Jetzt wird die nächste Instanz angesprochen und positioniert. Das geschieht solange, bis alle Instanzen auf der gewünschten Position liegen. Sie merken schon: Die Schleife nimmt uns die gesamte mühsame Handarbeit ab, die Sie in Schritt 6 leisten mussten (seien Sie froh, dass die Anweisung nicht lautete, 100-mal zu duplizieren!). Änderungen erweisen sich als ausgesprochen einfach. Wollen Sie beispielsweise 20 Objekte so positionieren, müssen Sie lediglich den Wert von nAnzahl dementsprechend anpassen, alles andere erfolgt automatisch. Im Kapitel über die MovieClip-Klasse werden Sie darüber hinaus erfahren, wie Sie per ActionScript Instanzen duplizieren bzw. MovieClips einfügen, so dass sogar das händische Kopieren vollständig entfällt. Unser Skript lässt sich etwas vereinfachen für den Fall, dass wir noch mehr Anweisungen pro Instanz ausführen wollen. 8. Fügen Sie vor der ersten Variablendeklaration folgende Zeile ein: var mKreis:MovieClip;
9. Ändern Sie den kompletten Anweisungsblock innerhalb der Schleife wie folgt: for (var i:Number = 0; i
Wir legen zunächst eine Variable mit dem Datentyp MovieClip an. Innerhalb der Schleife weisen wir ihr die jeweils ermittelte Instanz zu und können nun mit Hilfe der Variablen die x-und y-Position unserer Kreise definieren. Der Code gewinnt an Übersichtlichkeit und wird weniger fehleranfällig, da wir bei einer Änderung der Instanznamen nur an einer einzigen Stelle korrigieren müssen, nämlich dort, wo wir mKreis einen konkreten Inhalt zuweisen.
108
11.2.2 break, continue Wir haben bei switch mit dem break-Befehl eine Möglichkeit kennen gelernt, den Programmablauf zu unterbrechen. Das gleiche funktioniert auch bei Schleifen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: for (var i:Number = 1; i<=10; i++) { if (i == 5) { break; }
trace(i); }
Ausgabe im Nachrichtenfenster: 1 2 3 4 Obwohl wir im Schleifenkopf 10 Durchläufe festlegen, bricht die Schleife bereits nach dem vierten Durchlauf ab, da wir per if-Anweisung für den Fall, dass die Zählvariable i den Wert 5 enthält, den break-Befehl ausführen. Wollen wir statt dessen nicht vollständig abbrechen, sondern lediglich einen Durchlauf an dieser Stelle überspringen, verwenden wir die continue-Anweisung. 3. Ändern Sie die Anweisung innerhalb der if-Abfrage (Fettdruck): if (i == 5) { continue; }
Ausgabe im Nachrichtenfenster: 1 2 3 4 6 7 8 9 10
Kapitel 11 Kontrollstrukturen
Auch jetzt erscheint eine Lücke, sobald i den Wert erreicht. Aber anders als zuvor wird nur dieser eine Wert übersprungen und mit dem Wert i = 6 fortgefahren.
11.2.3 while-Schleife Etwas einfacher als die for-Schleife gestaltet sich die while-Schleife, die in allgemeiner Form folgendermaßen aussieht: while(Bedingung){ //Anweisungen }
In beredter Prosa formuliert bedeutet dies, dass Anweisungen solange ausgeführt werden, wie die eingangs definierte Bedingung wahr ist. Als Beispiel wollen wir die erste oben behandelte for-Schleife nachbilden, bei der wir von 1 bis 10 zählen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var nZahl:Number = 1; while (nZahl<=10) { trace(nZahl); nZahl++; }
Ausgabe im Nachrichtenfenster: 1 2 3 //usw. 10 Anders als bei der for-Schleife können wir am Schleifenbeginn keine Initialisierung vornehmen. Daher initialisieren wir vor Aufruf der Schleife die Zählvariable nZahl mit dem benötigten Startwert, hier die 1. Am Schleifenbeginn kontrolliert Flash, ob die Bedingung noch zutrifft, was unmittelbar nach der Deklaration von nZahl natürlich der Fall ist. Anschließend führt Flash den abhängigen Code-Block aus, was in unserem Beispiel schlicht bedeutet, dass er die Variable im Nachrichtenfenster ausgibt. Die letzte Zeile vor der schließenden Klammer der Schleife ist besonders wichtig, denn sie sorgt dafür, dass nZahl inkrementiert wird, so dass irgendwann der Grenzwert von 10
11.2 Schleifen
erreicht und die Ausführung abgeschlossen werden kann. Nach jedem Inkrementieren wird die Schleifenbedingung kontrolliert und abhängig vom Ergebnis der nächste Durchlauf gestartet oder der gesamte Vorgang beendet. Die meisten Vorgänge können Sie mit einer forSchleife abbilden, so dass Sie getrost weitgehend auf die while-Schleife verzichten können, zumal diese zu Fehlern verleitet. In manchen Fällen jedoch erweist sie sich als unverzichtbar, etwa wenn sie ein Objekt solange auf einer Zufallsposition einblenden wollen, bis es nicht mehr mit anderen bereits vorhandenen Objekten kollidiert. Oder Sie ermitteln eine Zufallszahl aus einem größeren Zahlenbereich, wollen dabei jedoch bestimmte Werte ausschließen. Letzteres ist insbesondere interessant, wenn Sie eine Animation erstellen, bei der eine Variable positive und negative Werte enthalten darf, jedoch keine 0. 3. Fügen Sie folgende Deklaration vor der whileSchleife ein:
109
nZufall vor der Schleife: 0 nZufall in der Schleife: -1 nZufall nach der Schleife: -1 Wenn bei Mausklick eine 0 ermittelt wird, trifft die Schleifenbedingung nZufall == 0 zu. Flash führt die Schleife aus und ermittelt auf exakt die gleiche Weise wie zuvor erneut eine Zufallszahl. Diese wird wieder mit der Schleifenbedingung verglichen. Ist sie ungleich 0, verlässt Flash die Schleife und führt den unmittelbar folgenden Code aus. Falls es sich jedoch erneut um eine 0 handelt, bleibt Flash innerhalb der Schleife und gibt eine neue Zufallszahl aus. Das geschieht solange, bis die Zahl größer oder kleiner 0 ist und die Schleife beendet wird. Da die Ausführung der Anweisung extrem schnell erfolgt, merken wir als Anwender keine Verzögerung, selbst wenn Flash mehrfach hintereinander eine 0 ermittelt. Weitere Informationen erhalten Sie im Kapitel zur Math-Klasse.
this.onMouseDown = function() {
11.2.4 do while-Schleife
trace(„nZufall vor der Schleife: „+nZufall);
Ein enger Verwandter der while-Schleife ist die do while-Schleife. Sie funktioniert ähnlich, verlagert aber die Bedingung ans Ende bzw. an den Schleifenfuß:
var nZufall:Number = Math. floor(Math.random()*21)-10;
while (nZufall == 0) {
nZufall = Math.floor(Math.random()*11)-5;
trace(„nZufall in der Schleife: „+nZufall); }
trace(„nZufall nach der Schleife: „+nZufall); };
Sobald Sie auf die Bühne klicken, gibt Flash Zufallszahlen sowie den zugehörigen Text aus. Wenn die ermittelte Zufallszahl ungleich 0 ist, z. B. eine 4, dann erhalten Sie folgende Meldung im Nachrichtenfenster: nZufall vor der Schleife: 4 nZufall nach der Schleife: 4 Da die in der while-Schleife definierte Bedingung nicht zutrifft, gibt Flash nur das erste und dritte trace() aus, während die vollständige Anweisung in der Schleife ignoriert wird. Anders sieht es dagegen bei einer 0 aus. Dann könnte im Nachrichtenfenster beispielsweise stehen:
do{
//Anweisungen;
}while(Bedingung);
Auch hier wollen wir bis 10 zählen, um den direkten Vergleich mit den anderen Schleifen zu ermöglichen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var nZahl:Number = 1; do {
trace(nZahl); nZahl++;
} while (nZahl<=10);
Ausgabe im Nachrichtenfenster: 1 2 3 //usw. 10
110
Während die Ausgabe derjenigen der while-Schleife entspricht, unterscheidet sich der Ablauf in einem nicht unwesentlichen Punkt: Durch die Verlagerung der Bedingung ans Ende wird diese Schleife auf jeden Fall mindestens einmal ausgeführt, unabhängig davon, ob die Bedingung zutrifft oder nicht. Da Skripte bekanntermaßen von oben nach unten abgearbeitet werden, trifft Flash zuerst auf die Anweisung nach dem Schlüsselwort do, die direkt ausgeführt wird. Anschließend erfolgt die Kontrolle der Bedingung. Die while-Schleife dagegen enthält die Bedingung unmittelbar vor dem Aufruf der abhängigen Anweisungen, so dass diese tatsächlich nur ausgeführt werden können, wenn die Überprüfung der Bedingung true ergibt. 3. Ändern Sie testweise die Bedingung wie folgt (Fettdruck): } while (nZahl>10);
Ausgabe im Nachrichtenfenster: 1 Formal gesehen weisen wir Flash an, nZahl solange auszugeben, wie ihr Wert größer 10 ist. Diese Bedingung trifft allerdings nie zu, denn wir haben die Variable unmittelbar vor der Schleife mit 1 initialisiert. Trotzdem führt Flash ein einziges Mal die Anweisung innerhalb der Schleife aus. Denn zum Zeitpunkt des Aufrufs dieser Anweisung ist Flash die Bedingung noch nicht bekannt. Skeptische Geister werden sich nun fragen, wozu denn eine derartige Schleife überhaupt gut sein soll. Wir setzen sie immer dann ein, wenn wir möchten, dass eine bestimmte Aktion auf jeden Fall einmal und eventuell mehrfach ausgeführt werden soll. Sie stellt beispielsweise ähnlich der while-Schleife eine Möglichkeit dar, Zufallszahlen zu generieren und dabei sicher zu stellen, dass ein bestimmter Wert nicht erreicht wird. Nehmen wir an, wir wollen eine Zufallszahl ermitteln, die sowohl positiv wie negativ sein kann, aber nicht gleich 0 sein darf, da wir sie für die Bewegung von MovieClips verwenden wollen. 4. Ergänzen Sie den Code um folgende Zeilen: var nZuf:Number;
this.onMouseDown = function() { do {
nZuf = Math.round(Math.random()*4)-2;
Kapitel 11 Kontrollstrukturen
} while (nZuf == 0);
trace(„nZuf: „+nZuf); };
Bei jedem Mausklick gibt Flash eine Zufallszahl aus. Wir definieren zunächst die Variable nZuf, ohne ihr einen Wert zuzuweisen. Anschließend legen wir ein Mausklick-Ereignis fest, das jeweils eine Zufallszahl zwischen –2 und 2 ermittelt. Die genaue Funktionsweise der Math-Methoden entnehmen Sie bitte dem Kapitel Math-Klasse. Da in dem übergebenen Wertebereich auch die 0 enthalten ist, wir aber genau diese Zahl ausschließen wollen, führen wir die Schleife solange aus, wie für nZuf 0 ermittelt wird. Anschließend geben wir den Zufallswert aus. Nehmen wir an, wir klicken mit der Maus. Dann wird die Anweisung innerhalb der Schleife unabhängig von der konkreten Bedingung einmal ausgeführt und in nZuf eine Zufallszahl gespeichert, z. B. eine 2. Da diese ungleich 0 ist, trifft die Bedingung nicht zu und Flash gibt den entsprechenden Wert direkt aus. Klicken wir wieder, läuft der gesamte Prozess erneut ab. Wenn jetzt beispielsweise eine 0 als Zufallszahl ermittelt wird, dann trifft die im Fuß angegebene Bedingung zu und Flash wird angewiesen, erneut eine Zufallszahl zu wählen. Solange dabei 0 herauskommt, speichert Flash eine neue Zufallszahl in nZuf. Erst wenn diese ungleich 0 ist, kann die Schleife beendet und der Wert ausgegeben werden.
11.2.5 for in-Schleife Eher aus dem Rahmen fällt die letzte der Schleifen, die for in-Schleife. Sie wird eingesetzt, um den Inhalt von Objekten oder Hashes (assoziative Arrays) auszulesen. Sie verwendet keine Zählvariable, sondern einen String, der der auszulesenden Eigenschaft entspricht. Allgemein sieht sie folgendermaßen aus: for (Variable in Objekt){ //Anweisungen; }
Weitere Informationen finden Sie im Kapitel ArrayKlasse.
11.2 Schleifen
111 Abbildung 15: Fehlermeldung bei Endlosschleife
11.2.6 Endlosschleifen Schleifen bergen prinzipiell das Risiko in sich, nicht abgebrochen werden zu können. Insbesondere wenn die Abbruchbedingung falsch definiert wird oder gar völlig fehlt, liegt eine Endlosschleife vor. Während andere Sprachen wie etwa Lingo, die Skriptsprache von Director, in einem derartigen Fall abstürzten, hört Flash nach einer gewissen Anzahl an Schleifendurchläufen automatisch auf und weist höflich darauf hin, dass ein Skript zu viele Iterationen enthalte. Je nach Gemütsverfassung kann man die Ausführung abbrechen (empfehlenswert) oder fortsetzen. Da Sie zweifelsohne darauf brennen, eine Endlosschleife in Aktion zu erleben, speichern Sie bitte alle noch offenen Dateien, beenden vorsichtshalber alle laufenden Applikationen (mit Ausnahme von Flash natürlich) und machen folgende Übung nicht nach. 1. Erstellen Sie keine Standarddatei. 2. Weisen Sie actions unter keinen Umständen folgendes Bildskript zu: var nZahl:Number = 1; while(nZahl < 10){
trace(„nZahl: “+ nZahl); }
Sollten Sie alle Ratschläge ignoriert, das Skript wider besseres Wissen geschrieben und entgegen aller Vernunft ausgeführt haben, gehen Sie einen Kaffee trinken und begutachten anschließend die in Abbildung 15 gezeigte Warnung.
Zunächst initialisieren wir nZahl mit dem Wert 1. Anschließend weisen wir Flash an, diese Variable solange auszugeben, wie sie kleiner als 10 ist. Da aber innerhalb der Schleife an keiner Stelle nZahl geändert wird, kann sie nie den Grenzwert erreichen bzw. übersteigen, so dass die Ausführung theoretisch unendlich lange erfolgen müsste. Zum Glück erweist sich Flash als klug genug, nach einer gewissen Zeit die Ausführung zu unterbrechen. So banal das Beispiel erscheinen mag, so wichtig ist es doch, denn gerade eine while- bzw. eine do while-Schleife verleitet dazu, die Abbruchbedingung zu vergessen. Etwas größere Sicherheit bietet zumindest in dieser Hinsicht die for-Schleife, denn dort muss ja direkt am Schleifenbeginn die Abbruchbedingung definiert werden. Vergessen wir sie, erhalten wir unmittelbar beim Kompilieren eine Fehlermeldung, so dass die Schleife erst gar nicht zur Ausführung kommt. Daher empfiehlt es sich zumindest anfangs, möglichst auf while-Schleifen zu verzichten. Meistens kann man an ihrer Stelle bequem mit einer for-Schleife arbeiten. Es gibt, wie wir in den Workshops sehen werden, nur einige wenige Fälle, in denen die while-Schleife unverzichtbar ist. Die einzige wirkliche Fehlerquelle einer forSchleife liegt in einer erfahrungsgemäß sehr selten auftretenden fehlerhaften Festlegung, wie man vom Startwert der Zählvariablen zu ihrem Endewert gelangt. Solange Sie, was in der Mehrzahl der Fall sein wird, mit einem simplen Inkrement arbeiten, stellt das nicht wirklich ein Problem dar.
MovieClip-Klasse
Wie wir gesehen haben, verfolgt Flash einen objektbasierten Ansatz. Das wichtigste Objekt, das visuell in Erscheinung tritt, aber durchaus auch in einer unsichtbaren Variante wertvolle Dienste leisten kann, stellt der MovieClip dar. Sie werden ihn sicher ohne Skripting schon intensiv kennen gelernt haben. Eine noch viel wichtigere Rolle spielt er aber gerade für ActionScript 2. Denn nahezu der komplette visuell wahrnehmbare Content Ihrer Anwendung wird, wenn er per Skripting ansprechbar sein soll, als MovieClip vorliegen. Demgegenüber bieten andere Symbole entweder erheblich weniger Funktionalität (Schaltfläche) oder lassen sich nicht skripten (Grafik). Im Grunde genommen können wir also völlig auf Grafik- und Schaltflächensymbole zugunsten von MovieClips verzichten. Ganz so extrem wollen wir nicht vorgehen, aber Schaltflächen werden im Nachfolgenden und insbesondere in den Workshops bestenfalls eine marginale Rolle spielen, deren Funktionalität übernimmt in den allermeisten Fällen ein MovieClip. Wie das geht, sehen wir im weiteren Verlauf dieses Kapitels. Die enorme Bedeutung des MovieClips unterstreicht zudem die Tatsache, dass kein anderes Objekt über einen derartig reichhaltigen Fundus an Eigenschaften, Methoden und Ereignissen verfügt. Wenn Sie einen Blick in die unter Entwicklern zwar aus prinzipiellen Gründen verpönte (jeder hat seinen Stolz), aber gerade für Anfänger absolut notwendige Hilfe werfen und auf den Eintrag MovieClip klicken, schleudert Ihnen Flash auf einer einzigen Seite eine Aufzählung von sage und schreibe 41 Eigenschaften, 18 Ereignissen und 43 Methoden entgegen! Was zunächst nach verwirrend viel aussieht, wird sich im Laufe der Programmierung tatsächlich als oft notwendig erweisen. Wir werden an dieser Stelle nur die am häufigsten verwendeten Eigenschaften, Ereignisse
12
und Methoden behandeln, greifen aber im Rahmen der Workshops das eine oder andere darüber hinausgehende Thema auf; Exoten bzw. Elemente, die nur in Ausnahmefällen zum Tragen kommen, wollen wir völlig ignorieren.
12.1 Eigenschaften von MovieClips 12.1.1 Adressierung von MovieClips Damit ein Objekt per Skript angesprochen werden kann, benötigt es eine Bezeichnung, einen Namen, und dieser Name muss – anders als im richtigen Leben – eindeutig sowie einmalig sein. Nachdem Sie ein Objekt in einen MovieClip verwandelt haben, sehen Sie in dessen Eigenschaftsfenster die Option , wie auf Abbildung 16 zu sehen.
Abbildung 16: Eigenschaftsfenster eines MovieClips auf der Bühne
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
113
114
Dort wird der Name eingetragen. Fehlt er, kann das Objekt theoretisch nicht per Skripting angesprochen werden, da wir Flash nicht mitteilen können, auf wen sich unsere Anweisungen beziehen sollen. In Ausnahmefällen ist das trotzdem möglich, wie wir noch unten sehen werden; die dort gezeigte Methode ist vor allem dann sinnvoll und hilfreich, wenn wir beispielsweise nicht konkret wissen, wie die Objekte heißen, die wir verändern wollen. Der Name folgt denselben Konventionen, die wir bereits bei Variablen kennen gelernt haben: Keine Schlüsselwörter, an erster Stelle keine Zahlen, keine Leerzeichen sowie keine Sonderzeichen mit Ausnahme von $ und _. Selbstverständlich verzichten Sie auch auf deutsche Umlaute, obwohl Flash diese mittlerweile klaglos akzeptiert. Wenn Sie also einem MovieClip den Instanznamen bücher zuweisen, können Sie zwar problemlos damit arbeiten, aber Sie gewöhnen sich eine Schreibweise an, die in anderen Skript- und Programmiersprachen nicht funktioniert. Um sich späteren Ärger zu sparen und um sich nicht unnötig Ausnahmen merken zu müssen, wo was akzeptiert wird, verzichten Sie einfach auf solche Umlaute. Ebenfalls zu beachten ist die Groß- und Kleinschreibung. Wir haben Variablen durch ein Präfix gekennzeichnet. Ähnliches lässt sich bei Instanznamen anwenden. So ist es z. B. sinnvoll, einen MovieClip, der als Button fungiert, mit dem Kürzel bt zu kennzeichnen, also z. B. btMenue, btService etc. Enthält er eine Zeitleistenanimation, kann man mit an darauf hinweisen, anAuto oder anFlugzeug wären entsprechende Beispiele. Da jedoch ein MovieClip enorm viele Funktionen übernehmen wird, ist es nicht möglich, alles mit einem Präfix zu charakterisieren. Eine weitere Möglichkeit bestünde in der zusätzlichen oder alternativen Verwendung eines Suffix’, das auf den Datentyp des Objektes, d. h. auf den MovieClip verweist. Dann könnten unsere MovieClip-Buttons die Instanznamen bMenue_mc und bService_mc bzw. menue_mc und service_mc tragen. Das hat während des Skriptens den Vorteil, dass in einem Auswahlfeld alle zur Verfügung stehenden Möglichkeiten angezeigt werden, sobald nach dem Namen ein Punkt folgt. Flash erkennt nämlich aufgrund des nachgestellten _mc, dass ein MovieClip vorliegt. Da es sich aber um eine, wie bereits oben angemerkte, proprietäre Flash-Lösung handelt, wird davon abgeraten
Kapitel 12 MovieClip-Klasse
Über denselben Vorteil verfügt, wer die verwendeten MovieClips in einer Variablen referenziert, deren Datentyp man als MovieClip festgelegt hat. Eine derartige Vorgehensweise besitzt zudem den Vorteil, dass man an einer zentralen Stelle die Variablen einrichtet und schon dort erkennen kann, welche Objekte verwendet werden. Wir wählen in den meisten Workshops diesen Weg. Wir wollen an einem kleinen Beispiel zeigen, wie wir auf den Namen zugreifen können. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects an beliebiger Position einen roten Kreis mit beliebigem Durchmesser. 3. Wandeln Sie den Kreis in einen MovieClip um (Bibliotheksname mcKreis, Instanzname kreis1, Registrierungspunkt mittig). 4. Weisen Sie der Ebene actions ein Bildskript zu: _root.onMouseDown = function(){ trace(kreis1._name); }
Testen Sie den Film und klicken an eine beliebige Stelle. Flash gibt im Nachrichtenfenster kreis1 aus. Das Skript weist Flash an, bei einem Klick mit der Maus auf eine beliebige Stelle Ihrer Bühne auf die Namenseigenschaft der Instanz von mcKreis zuzugreifen. Da wir im Eigenschaftsfenster dieser Eigenschaft den String kreis1 zugewiesen haben, erscheint die im Nachrichtenfenster angezeigte Information. Die Bezeichnung _root bezieht sich hier auf die Hauptzeitleiste, die ebenfalls einen MovieClip darstellt. Sie verfügt automatisch über einen eigenen Namen, auf den wir keinen Einfluss haben. Um mit MovieClips zu arbeiten, ist es wichtig, auf ihre Namen zugreifen zu können. Das ist einfach, solange sie nicht verschachtelt sind. Wenn Sie auf der Bühne 10 verschiedene Kreise mit den Instanznamen kreis1 bis kreis10 liegen haben, lassen sie sich ansprechen, indem wir eben diese Namen verwenden, z. B. kreis1._x = 100, was die horizontale Position des MovieClips mit dem Instanznamen kreis1 auf 100 setzt. Häufig wird es jedoch so sein, dass ihre MovieClips weitere MovieClips enthalten, wir es also mit einer Verschachtelung wie bei der berühmten russischen Matroschka, der Puppe in der Puppe, zu tun haben. Stellen Sie sich einen MovieClip mit dem Instanznamen auto vor, der über eine Karosserie, drehbare
12.1 Eigenschaften von MovieClips
115
Pfad, wie wir ihn vom Betriebssystem her gewohnt sind, wenn wir auf eine bestimmte Datei zugreifen wollen. Auch dort müssen wir den genauen Ort inklusive Ordner angeben. Da eine korrekte Adressierung, eingedenk Konfuzius, der schon vor Jahrtausenden in weiser Voraussicht gefordert hat: „Stellt die Namen richtig!“, unverzichtbar ist, wollen wir sie uns an dem erwähnten Beispiel der Autoanimation näher anschauen.
Abbildung 17: Verschachtelter MovieClip auto
Räder, einen qualmenden Auspuff etc. verfügt. Da all diese Elemente untrennbare Bestandteile eines Autos sind, werden sie logischerweise im MovieClip auto zusammengefasst. Wenn wir nun die Räder per Skripting ansprechen wollen, um sie etwa zu stoppen, dann benötigen die betreffenden Objekte einen Instanznamen, z. B. anRadV und anRadH für das Vorder- und das Hinterrad. Wollten wir bei einem beliebigen Mausklick die Radanimationen stoppen, müsste das entsprechende Skript lauten: _root.onMouseDown = function(){ auto.anRadH.stop(); auto.anRadV.stop(); }
Da sich anRadH und anRadV in auto befinden, kann Flash die Radanimationen nur finden, wenn wir genau diesen Ort angeben. Die Angabe auto.anRadH und auto.anRadV gleicht im Grunde genommen einem
5. Löschen Sie dort den Kreis sowie alle bisherigen Skripte. 6. Erstellen Sie einen leeren MovieClip (Bibliotheksname mcAuto). 7. Benennen Sie die bestehende Ebene um in rad. 8. Fügen Sie die Ebene karosserie oberhalb von rad ein. 9. Zeichnen Sie auf rad das Vorderrad. Ihre Grafik muss so gestaltet werden, dass wir optisch in der Lage sind, eine Drehung wahrnehmen zu können. Ein simpler Kreis, so attraktiv er auch sein mag, reicht also nicht aus. 10. Wandeln Sie das Rad in einen MovieClip um (Bibliotheksname mcAnRad, Registrierungspunkt mittig). 11. Zeichnen Sie auf karosserie eine Karosserie (Fahrzeugausrichtung nach rechts). Bedenken Sie, dass das Fahrzeug idealerweise nicht den ganzen Screen ausfüllt, so dass später noch Platz für Buttons bleibt. Ordnen Sie die Grafik so an, dass sich der Koordinatenursprung des MovieClips mcAuto in der Mitte befindet. Damit haben wir alle statischen Elemente erstellt, die wir benötigen. Es fehlt noch die Animation der Räder. 12. Öffnen Sie mcAnRad im Symbolbearbeitungsmodus. 13. Wandeln Sie dort die bestehende Grafik in einen MovieClip um (Bibliotheksname mcRad, Registrierungspunkt mittig). 14. Fügen Sie in mcAnRad in Frame 8 ein Schlüsselbild ein. 15. Weisen Sie Frame 1 ein Bewegungstween zu (Drehen: nach rechts, 1 mal). Achten Sie darauf, dass Frame 8 kein Tween erhält. Es ist nicht nur eine relativ weit verbreitete Unsitte, sondern schlicht falsch, dem letzten Schlüsselbild einer Animation ebenfalls ein Tween zuzuweisen.
116
Kapitel 12 MovieClip-Klasse
Ein auf diese Weise erstelltes Tweening hat den Nachteil, dass erstes und letztes Bild miteinander übereinstimmen. Infolgedessen stockt die Animation, weil eine einzige Position doppelt angezeigt wird.
16. Wandeln Sie alle Zwischenbilder von mcAnRad in Schlüsselbilder um. 17. Entfernen Sie aus allen Bildern das Tweening. 18. Löschen Sie das letzte Schlüsselbild.
Indem wir aus allen getweenten Bildern Schlüsselbilder machen, werden sie unabhängig vom ersten Schlüsselbild. Wir können sie dann nach Belieben verändern, was aber in diesem konkreten Beispiel nicht nötig ist. Da durch die Umwandlung alle Bilder (mit Ausnahme des letzten) ein Tween besitzen, es aber überflüssig geworden ist, löschen wir es. Die von Flash errechnete Drehung bleibt trotzdem erhalten. Nun können wir auch das letzte Bild mit der gleichen Position wie das erste entfernen, ohne das es sich auf die Animation auswirken würde. Hätten wir dagegen direkt nach Schritt 15 das letzte Bild gelöscht, wäre gleichzeitig das Tween zerstört worden. 19. Öffnen Sie mcAuto im Symbolbearbeitungsmodus. 20. Weisen Sie dem bestehenden Vorderrad den Instanznamen anRadV zu. 21. Fügen Sie aus der Bibliothek mcAnRad als Hinterrad ein (Instanznamen anRadH). 22. Kehren Sie zur Hauptzeitleiste zurück. 23. Fügen Sie auf objects an beliebiger Position mcAuto ein (Instanzname auto). 24. Weisen Sie actions folgendes Bildskript zu:
_root.onMouseDown = function(){
}
auto._x += 5;
Sobald Sie irgendwo auf der Bühne klicken, springt das Auto respektvoll um 5 Pixel zur Seite. Wir weisen der Hauptzeitleiste ein onMouseDown-Ereignis zu, das bei jedem Aufruf zur aktuellen x-Position des Objektes mit dem Instanznamen auto 5 Pixel addiert. Das ist zwar noch nicht besonders aufregend, aber wir sehen, wie wir die Objekte ansprechen: Solange sie direkt auf der Bühne liegen, reicht der simple Instanzname. 25. Wollen wir darüber hinaus die Radanimationen stoppen, muss das onMouseDown-Ereignis wie folgt ergänzt werden (Fettdruck):
_root.onMouseDown = function(){
auto._x += 5;
auto.anRadH.stop(); auto.anRadV.stop(); }
Der Mausklick weist Flash an, im Objekt auto nach Instanzen namens anRadH und anRadV zu suchen und ihre Animationen zu stoppen. Das funktioniert selbstverständlich mit jedem beliebigen anderen Objekt innerhalb von auto oder anderen MovieClips auf der Bühne auch. Die Verschachtelung darf beliebig tief sein. Die Pfade lassen sich wie eine Art Hierarchie betrachten, wie Abbildung 18 zeigt. Wenn die Aktion nicht von _root, sondern vom MovieClip auto ausgelöst werden soll, kann der Pfad in der jetzigen Version verwendet werden. 26. Ändern Sie das bestehende Skript wie folgt (Fettdruck):
auto.onPress = function(){
auto._x += 5;
auto.anRadH.stop(); auto.anRadV.stop();
}
Klicken Sie beim Testen auf das Auto, bleiben wie zuvor die Animationen der Räder stehen. Beachten Sie, dass wir ein onPress anstelle eines onMouseDown verwenden müssen, wenn wir einem von _root verschiedenen Objekt einen Mausklick zuweisen wollen. Der Unterschied zwischen beiden Ereignissen wird weiter unten genauer erläutert. Natürlich können wir den Mausklick auch auf die Räder verlagern. 27. Ersetzen Sie auto.onPress durch: auto.anRadH.onPress = auto.anRadV. onPress = function(){
Abbildung 18: Pfadhierarchie
12.1 Eigenschaften von MovieClips
Ein einzelner Klick auf eines der Räder stoppt jetzt alle Radanimationen. Aufgrund der Mehrfachzuweisung verfügt jedes der Räder über die gleiche Funktionalität wie zuvor die Instanz auto. Daher reicht ein Klick auf ein Rad aus. Zu Kapitelbeginn wurde erwähnt, dass _root einen MoviClip darstellt. Insofern befindet sich unser autoMovieClip im _root-MovieClip und müsste mit einem entsprechenden Pfad ansprechbar sein. 28. Testen Sie folgende Schreibweise (Änderungen fett): _root.auto.anRadH.onPress = _root. auto.anRadV.onPress = function(){ _root.auto._x += 5;
_root.auto.anRadH.stop(); _root.auto.anRadV.stop();
}
Tatsächlich existiert in der Ausführung kein Unterschied, beide verwendeten Schreibweisen sind in diesem Fall miteinander identisch. Sie können also theoretisch nach Belieben die eine oder andere Varianten wählen. Allerdings gilt das nicht generell, denn in einer bestimmten Situation führen die Schreibweisen zu zwei völlig verschiedenen Ergebnissen. Die Angabe _root wird anders interpretiert, wenn Sie modulare Applikationen mit Hilfe der loadMovie()-Methode erstellen. Daher empfiehlt es sich unbedingt, auf die Verwendung von _root zu verzichten, um nicht ungewollt einen fehlerhaften weil von Flash anders verstandenen Pfad zu erhalten. Sollte es unverzichtbar sein, sich auf _root zu beziehen, hilft es, die _ root-Angabe in einer Variablen zu speichern oder die Eigenschaft _lockroot auf true zu setzen. Beide Möglichkeiten werden im Zusammenhang mit der erwähnten loadMovie()-Methode näher erläutert. Ein Pfad wie auto.anRadV stellt eine absolute Angaben dar, da wir uns unabhängig davon, von welcher Stelle aus das zugehörige Skript aufgerufen wird, immer auf auto bzw. _root (s. o.) beziehen, also auf das oberste Objekt innerhalb unserer Hierarchie. Diese Vorgehensweise hat zwar den Vorteil, eher simpel zu sein, birgt aber zugleich den Nachteil, dass jede Änderung der Objekthierarchie eine entsprechende Korrektur im Code erfordert. Als Alternative bietet Flash eine relative Adressierung, die als Ausgangspunkt diejenige Stelle wählt, von der aus das Skript ausgeführt wird. Wenn wir bei-
117
spielsweise auto.anRadH ein onPress zuweisen, gilt dieser MovieClip als Ausgangsort. Wollen wir dessen eigene Animation stoppen, können wir anstelle des vorherigen Pfadbandwurms mit dem erheblich kürzeren Schlüsselwort this arbeiten. 29. Ändern Sie das Skript wie folgt: auto.anRadH.onPress = auto.anRadV. onPress = function(){ this.stop();
}
Ein Klick auf das Vorderrad oder das Hinterrad stoppt die betreffende Animation. Der Methode stop() müssen wir nicht mehr wie zuvor eine Instanz im Klarnamen zuordnen, sondern Flash ermittelt das gemeinte Objekt über das Schlüsselwort this. Wenn wir auf auto.anRadH klicken, steckt in this der Instanzname des Hinterrads, klicken wir dagegen auf auto.anRadV, verweist this auf das Vorderrad. Das können wir noch etwas genauer testen. 30. Schreiben Sie nach der Stop-Methode innerhalb der geschweiften Klammern: trace(„Angeklicktes Objekt: “+this._ name);
Bei einem Mausklick öffnet sich das Nachrichtenfenster, in dem der Name des angetroffenen Movie Clips ausgegeben wird. Wie Sie sehen, ermittelt Flash automatisch jeweils den richtigen Namen, ohne das wir ihn explizit eintragen müssen. Diese Variante besitzt erheblich mehr Flexibilität als die vorhergehende, denn wenn wir den Pfad oder den Instanznamen ändern, wird this in jedem Fall das korrekte Objekt finden. Würden wir mit einem absoluten Pfad arbeiten, wäre es notwendig, nicht nur die Angabe beim onPress-Ereignis zu korrigieren, sondern auch die stop()-Methode ausdrücklich dem neuen Objekt zuzuweisen. Mit this lässt sich übrigens auch der Pfad ablesen, wobei Flash immer einen absoluten Pfad zurückgibt. 31. Fügen Sie nach dem bestehenden ein weiteres trace ein: trace(„Pfad angeklicktes Objekt: “+this);
Als neue Ausgabe erscheint _level0.auto.anRadH bzw. _level0.auto.anRadV. Dies entspricht dem gesamten Pfad von der Hauptzeitleiste bis zum ange-
118
Kapitel 12 MovieClip-Klasse
klickten Objekt. Lassen Sie sich nicht von _level0 verwirren, an dessen Stelle Sie wahrscheinlich ein _ root erwartet haben. Für Flash sind in diesem Fall beide Bezeichnungen identisch, aber in der Ausgabe wird nur _level0 verwendet. Wie bereits erwähnt, verhält es sich anders, wenn wir mit der loadMovie()Methode arbeiten. Wir haben oben eine Version kennen gelernt, bei der ein Klick auf das Auto die Radanimationen stoppt. Dasselbe geht natürlich auch, wenn wir den absoluten Pfad durch einen relativen ersetzen. 32. Löschen Sie das aktuelle Skript und schreiben Sie folgendes:
auto.onPress = function(){
this.anRadH.stop(); this.anRadV.stop();
}
Da sich anRadH und anRadV in auto befinden, genügt es, bei Klick auf das Auto über this das aktivierte Objekt zu ermitteln. Wir erhalten automatisch auto und daran angehängt anRadH, was auto.anRadH ergibt. Jedes benannte Objekt, das sich in einem MovieClip befindet, lässt sich über this.InstanznameDesObjektes ansprechen, vorausgesetzt, das ausführende Skript ist dem Objekt zugewiesen, auf das this verweist. Wie der absolute lässt der relative Pfad eine beliebige Verschachtelung zu. Beachten Sie, dass this in jedem Fall an erster Stelle angeführt wird. Ein Pfad wie auto.this.beliebigerInstanzname wäre also unsinnig, weil Sie mit auto bereits einen absoluten Pfad angegeben haben und so eine relative Adressierung überflüssig machen. In einem derartigen Fall erhalten Sie eine Fehlermeldung. Eine Animation wie etwa diejenige der Räder muss nicht notwendigerweise über Klick auf das Auto oder eines seiner Bestandteile gesteuert werden. Statt dessen könnte man eigene Buttons (in Form von MovieClips) verwenden. 33. Fügen Sie in der Hauptzeitleiste eine neue Ebene namens steuerung ein. 34. Erstellen Sie in dieser Ebene an beliebiger Stelle einen MovieClip (Bibliotheksname btnStart, Instanzname btStart, Registrierungspunkt links oben) mit der Beschriftung „Start“ (statisches Textfeld).
35. Erstellen Sie in derselben Ebene neben diesem MovieClip einen weiteren MovieClip (Bibliotheksname btnStop, Instanzname btStop, Registrierungspunkt links oben) mit der Beschriftung „Stop“ (statisches Textfeld). 36. Markieren Sie die beiden neu erstellten Movie Clips sowie das Auto auf der Hauptzeitleiste und wandeln sie in einen gemeinsamen MovieClip (Bibliotheksname mcFahrzeug, Instanzname fahrzeug, Registrierungspunkt unten mittig) um. Wir verfahren hier wie beim Auto: Was von der Logik her zusammen gehört, packen wir in einen eigenen Behälter. Da unsere Steuerung sich nur auf das Auto bezieht, stecken wir sie zusammen mit ihm in einen übergeordneten MovieClip. Alternativ hätte man auch einen vom Auto unabhängigen MC einrichten können, der nur die Steuerung aufnimmt. 37. Schreiben Sie nach dem bisherigen Skript folgenden Code:
fahrzeug.btStop.onPress = function(){
fahrzeug.auto.anRadH.stop(); fahrzeug.auto.anRadV.stop();
}
Die Radanimation lässt sich nun über einen Button steuern. Momentan verwenden wir eine absolute Adressierung, ausgehend von der auf _root befindlichen Instanz fahrzeug, in der sich sowohl die Buttons wie auch das Auto befinden. 38. Ändern Sie das gerade eingefügte Skript wie folgt (Fettdruck):
fahrzeug.btStop.onPress = function(){
this._parent.auto.anRadH.stop(); this._parent.auto.anRadV.stop();
}
Gegenüber vorher erkennen wir beim Testen keinen Unterschied. Auch hier verwenden wir eine relative Adressierung, bewegen uns aber in der Objekthierarchie nicht direkt nach unten, sondern zuerst nach oben. Ein Mausklick weist Flash an, zu dem unserem Stopp-Button übergeordneten Element, also seinem _parent zu gehen, und von dort aus auf den in auto befindlichen MovieClip anRadH bzw. anRadV zuzugreifen. Der Vorteil dieser Schreibweise besteht darin, dass das Skript in jedem Fall funktioniert, solange sich
12.1 Eigenschaften von MovieClips
Abbildung 19: Relative Adressierung mit _parent
innerhalb der Objekthierarchie der Bezug zwischen dem Stop-Button und den Rädern nicht ändert. Dabei ist es völlig unerheblich, welchen Namen der Behälter trägt, in den diese Elemente eingebettet sind. Würden wir den Instanznamen von fahrzeug in fahrzeug1 ändern, weil beispielsweise mehrere Fahrzeug-Movie Clips verwendet werden, würde die stop()-Anweisungen trotzdem funktionieren, vorausgesetzt, das onPress-Ereignis würde dementsprechend fahrzeug1 zugewiesen. Anders sähe das bei einer absoluten Adressierung aus, denn dann müssten wir den Pfad, ausgehend von _root, explizit angeben. Eine Namensänderung von fahrzeug müsste dann auch in den stop()-Anweisungen berücksichtigt werden. Warum können wir vom Stop-Button aus nicht direkt via auto.anRadH.stop() bzw. auto.anRadV.stop() auf auto zugreifen, immerhin befinden sich beide ja innerhalb von fahrzeug? In dem Fall würde Flash unsere Angabe als absoluten Pfad lesen und direkt auf _root nach einer Instanz namens auto suchen. Da sie dort nicht (mehr) existiert, geschieht nichts. Eine falsche Pfadangabe ist eine zumindest anfangs ausgesprochen häufig anzutreffende Fehlerquelle, auf die Flash leider nicht wie man es in anderen Programmiersprachen gewohnt ist, mit einer entsprechenden Meldung reagiert.
119
nen nur ausgelesen, die meisten auch gesetzt werden. Viele beziehen sich auf die visuelle Erscheinung und sind vom Namen her selbsterklärend wie beispielsweise _width (Breite), _height (Höhe), _alpha (Deckkraft) etc. Leider ist, historisch bedingt, die Schreibweise der Eigenschaften bei MovieClips inkonsistent: Die in älteren Flash-Versionen eingeführten wie _height etc. werden mit einem vorangestellten Unterstrich, neuere wie cacheAsBitmap oder filters dagegen ohne Unterstrich geschrieben. Das Auslesen und Setzen von Eigenschaften gehört zu den zentralen Aktionen jeder Applikation, da sie die visuelle Erscheinung von Objekten definieren und ihre interaktive Änderung erlauben. Eine wichtige Rolle spielen dabei die erwähnten Ausmaße, aber auch Position und aktueller Frame eines MovieClips. Bevor wir uns damit näher befassen, ist ein Blick auf das Koordinatensystem von Flash notwendig. Wie weiß Flash, an welcher Stelle ein Objekt darzustellen ist? Allgemein ermöglicht es ein Koordinatensystem, einem Objekt in einem Raum eine genau definierte Position zuzuweisen. Ein zweidimensionales Koordinatensystem benötigt dazu zwei, ein dreidimensionales drei Angaben. Als absoluter Bezugspunkt wird ein sogenannter Koordinatenursprung definiert, in dem die erwähnten Angaben den Wert 0 besitzen. Flash verwendet ein kartesisches Koordinatensystem. Generell liegt in einem derartigen Koordinatensystem der Koordinatenursprung in der Mitte mit nach rechts aufsteigenden und nach links absteigenden
12.1.2 _x, _y, _xmouse, _ymouse, _width, _height Ist der korrekte Pfad zu einem MovieClip bekannt, sind wir in der Lage, beispielsweise auf dessen Eigenschaften zuzugreifen. Manche Eigenschaften kön-
Abbildung 20: Kartesisches Koordinatensystem
120
Abbildung 21: Kartesisches Koordinatensystem in Flash (Hauptzeitleiste)
Werten auf der x-Achse sowie nach oben aufsteigenden und nach unten absteigenden Werten auf der yAchse. Die x-Achse definiert dabei die horizontale, die y-Achse die vertikale Position. In Flash befindet sich der Koordinatenursprung, wenn wir uns auf die Hauptzeitleiste beziehen, in der linken oberen Ecke. Von diesem Punkt ausgehend steigt der x-Wert, wenn wir uns nach rechts bewegen, und er wird negativ, wenn wir uns nach links bewegen. Der y-Wert steigt im positiven Bereich bei einer Bewegung nach unten, nach oben sinkt er unter 0. Abbildung 21 verdeutlicht das Kartesische Koordinatensystem in Flash. Wird ein Objekt auf der Hauptzeitleiste auf einen negativen Wert gesetzt, liegt es außerhalb der Bühne und ist i. d. R. nicht mehr sichtbar. Maßgeblich für die Positionierung innerhalb dieses Koordinatensystems ist der Registrierungspunkt eines Objektes. Davon weicht das Koordinatensystem eines beliebigen MovieClips und einer Schaltfläche insofern ab, als dessen Ursprung in der Mitte liegt. Das können Sie leicht testen, indem Sie per <Strg> einen leeren MovieClip erstellen. Dann öffnet Flash den Symbolbearbeitungsmodus und zeigt den Koordinatenursprung genau in der Screenmitte an. Wenn Sie dort ein Objekt zeichnen, es markieren und per Eigenschaftsfenster auf 0,0 positionieren, erhalten Sie ein Koordinatensystem, das demjenigen der Hauptzeitleiste gleicht, da nun der komplette sichtbare Inhalt auf positiven x- und y-Werten liegt. Ein derartiger
Kapitel 12 MovieClip-Klasse
MovieClip verfügt dann über einen links oben angeordneten Registrierungspunkt. Diese Vorgehensweise ist generell üblicher als ein mittig angeordneter Registrierungspunkt. Die Hauptzeitleiste stellt also eine Art globales Koordinatensystem dar, während jedes andere Objekt mit einer eigenen Zeitleiste dementsprechend über ein eigenes lokales Koordinatensystem verfügt. Zum Glück kann man bei Symbolen im Gegensatz zur Hauptzeitleiste den Registrierungspunkt, der identisch ist mit dem Koordinatenursprung, setzen. Diejenige Eigenschaft, für die das Koordinatensystem die größte Bedeutung besitzt, ist natürlich die Position, ausgedrückt als MovieClip._x (horizontaler Abstand zum Koordinatenursprung) und MovieClip._y (vertikaler Abstand zum Koordinatenursprung). Die Abstände werden jeweils in Pixeln gemessen, wobei nicht nur Ganzzahlen, sondern auch Fließkommawerte gültig sind. Allerdings sollte man, wo immer möglich, mit Ganzzahlen arbeiten, was nicht nur das Erstellen eines sauberen Layouts erleichtert, sondern auch gelegentliche Darstellungsfehler verhindert. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects einen roten Kreis (50 × 50, Linienstärke 1) an beliebiger Position. 3. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Instanzname kreis, Registrierungspunkt mittig). Sollte sich der Registrierungspunkt aus welchem Grund auch immer nicht an der gewünschten Stelle, nämlich exakt in der Mitte, befinden, so lässt er sich nachträglich sehr leicht korrigieren. Dazu müssen Sie den MovieClip lediglich im Symbolbearbeitungsmodus öffnen, die zu positionierenden Elemente markieren und im Eigenschaftsfenster unter x sowie y jeweils minus halbe Breite und halbe Höhe eintragen. Wird ein Objekt im Symbolbearbeitungsmodus um seine halbe Breite nach links und um seine halbe Höhe nach oben verschoben, liegt der Registrierungspunkt automatisch exakt in der Mitte. 4. Weisen Sie der Ebene actions folgendes Bildskript zu: trace(„x-Position: “ + kreis._x + “; y-Position : “ + kreis._y);
Im Nachrichtenfenster werden die genannten Eigenschaften angezeigt. Flash liest die x- und y-Position
12.1 Eigenschaften von MovieClips
aus und zeigt die betreffenden Pixelwerte an. Spannender ist das Ganze, wenn wir die Pixelwerte setzen, denn dann können wir unabhängig davon, wo sich das Objekt bei Start der Anwendung befand, zu jedem beliebigen Zeitpunkt eine Positionsänderung vornehmen. 5. Fügen Sie nach dem trace()-Befehl folgende Zeile ein: kreis._x = 0; kreis._y = 0;
Beim Testen befindet sich der Kreis nicht mehr an der händisch während des Erstellens zugewiesenen Position, sondern er klebt in der linken oberen Ecke und ragt nur noch zu einem Viertel sichtbar in die Bühne hinein. Da sein Registrierungspunkt in der Mitte liegt, befinden sich seine linke und seine obere Hälfte, betrachtet vom Koordinatensystem der Hauptzeitleiste, im negativen Bereich und damit außerhalb der Bühne. Wollten wir ihn ganz sichtbar in derselben Ecke positionieren, müssten wir eine kleine Berechnung durchführen. 6. Ändern Sie die Positionszuweisungen:
121
der Durchmesser des Kreises 50 beträgt). Nicht immer ist ein derartiges Verhalten erwünscht. Gerade bei Animationen möchte man eine Positionsänderung ausgehend von der aktuellen Position vornehmen. Das geschieht mit Hilfe einer relativen Wertezuweisung. 7. Schreiben Sie nach den bestehenden Wertezuweisungen: kreis._x += 200;
Der Kreis behält seine y-Position bei, wird aber horizontal um 200 Pixel nach rechts verschoben. Wohlgemerkt: Anders als zuvor wird er nicht auf den Pixelwert gesetzt, der nach dem Zuweisungszeichen genannt wird, sondern zu seiner aktuellen Position wird dieser Wert addiert, so dass er im Ergebnis auf 225,25 gesetzt wird. 8. Schreiben Sie nach der letzten Wertezuweisung: trace(„x-Position: “ + kreis._x);
Im Nachrichtenfenster erscheint der o. g. Wert von 225. Wenn wir die relative Zuweisung ein weiteres Mal ausführen, ergibt sich als neue x-Position 425.
kreis._x = kreis._width/2;
kreis._y = kreis._height/2;
9. Kopieren Sie die in Schritt 7 und 8 geschriebenen Zeilen und fügen sie nach der letzten trace()Anweisung erneut ein.
Jetzt ist er in der linken oberen Ecke vollständig sichtbar. Wie wir gesehen haben, wird er aufgrund des mittig angeordneten Registrierungspunktes horizontal und vertikal jeweils halb außerhalb der Bühne angezeigt, wenn wir ihm die Position 0,0 zuweisen. Um ihn ganz sehen zu können, müssen wir ihn also um die nicht sichtbare Hälfte nach rechts und nach unten verschieben. Diese Hälfte ist, in beredter Prosa formuliert, nichts anderes als die halbe Breite und die halbe Höhe. Wir erhalten Sie einfach, indem wir die vorhandenen MovieClip-Eigenschaften _width und _height durch 2 dividieren. Derartige Berechnungen werden sich im Verlaufe vieler Workshops als wichtig erweisen, da sie es ermöglichen, dynamisch die korrekten Positionen zuzuweisen, ohne mit fest vorgegebenen Zahlenwerten arbeiten zu müssen. In diesen Fällen handelt es sich um absolute Wertezuweisungen, d. h., unabhängig von dem vorher gültigen Wert wird ein neuer Wert eingesetzt. Egal, ob sich kreis vorher auf der Position 0,75 auf 221,333 oder auf irgend einem anderen beliebigen Wertepaar befand, nachher sitzt er auf jeden Fall auf 25,25 (falls
Die beiden letzten Ausgaben im Nachrichtenfenster lauten nun 225 und 425, wodurch wir im Grunde genommen eine sehr einfache Animation programmiert haben. Vielleicht erscheint es Ihnen merkwürdig, dass wir trotz mehrfacher Änderung der x-Position den Kreis nur an einer einzigen Stelle, nämlich an der durch die letzte Zuweisung definierten Position sehen können. Der Grund liegt in der schnellen Abarbeitung unserer doch sehr wenigen Codezeilen. Diese werden im Bruchteil einer Sekunde ausgeführt, anschließend zeichnet der Flash-Player den Frame und als Ergebnis bekommen wir den letzten Zustand zu sehen. Selbst wenn wir Flash mit einem updateAfterEvent() nach jeder Positionsänderung dazu zwingen würden, direkt den Screen neu zu zeichnen, wären wir nicht in der Lage, die einzelnen Positionsänderungen wahrzunehmen. Wie gezeigt, verwendet Flash für die Positionierung das Koordinatensystem derjenigen Zeitleiste, in der sich das betreffende Objekt befindet. Solange unsere Objekte nicht verschachtelt sind, stellt das kein Problem dar. Oft werden wir jedoch MovieClips
122
Kapitel 12 MovieClip-Klasse
einsetzen, die ihrerseits MovieClips beinhalten und u. U. sehr tief verschachtelt sind. Nehmen wir das Beispiel des MovieClips auto, der seinerseits animierte MovieClips enthält. Deren Position wird nicht über die Hauptzeitleiste, sondern über diejenige von auto definiert. Denn sie befinden sich nicht direkt in der Hauptzeitleiste, sondern in auto. 10. Öffnen Sie die Datei mit dem MovieClip auto. 11. Ersetzen Sie das bestehende Bildskript der Ebene actions durch:
auto._x = auto._y = 100;
trace(„Horizontale Position auto: ” + auto._x); trace(„Horizontale Position anRadH: “ + auto.anRadH._x) ;
Im Nachrichtenfenster gibt Flash die x-Position des Autos erwartungsgemäß mit 100 an, die x-Position des animierten Hinterrades dagegen hat einen negativen Wert. Würde sich der letztgenannte Wert auf die Hauptzeitleiste beziehen, wäre das entsprechende Objekt gar nicht mehr sichtbar, denn es läge links außerhalb der Bühne. Da sich anRadH in auto befindet, wird dessen Koordinatensystem herangezogen. Die Radanimation liegt dort links vom Koordinatenursprung, was eine negative Zahl ergibt. Manchmal besteht die Notwendigkeit, Werte aus einem Koordinatensystem in ein anderes umzurechnen. So könnte es z. B. wichtig sein, welchen Koordinaten auf der Hauptzeitleiste die Position von anRadH in auto entspricht. Dazu stellt Flash zwei Methoden (localToGlobal() und globalToLocal()) zur Verfügung, die wir unten im Block zu den MovieClipMethoden vorstellen. Auswirkungen haben die verschiedenen Koordinatensysteme auch auf die Mausposition, je nachdem, ob das globale oder ein lokales System verwendet wird. 12. Ergänzen Sie das Skript um:
auto.onPress = function(){
trace(„lokale Koordinaten: „ + this._xmouse + „, „ + this._ ymouse); trace(„globale Koordinaten: „ + _xmouse + „, „ + _ymouse);
}
Sie erhalten im Nachrichtenfenster zwei verschiedene Wertepaare. Das erste, kleinere Paar misst die
Mausposition in Bezug auf das Koordinatensystem des MovieClips auto und wird in keinem Fall größer sein als die halbe Breite bzw. halbe Höhe des MovieClips, da der Registrierungspunkt mittig angeordnet ist. Das zweite Paar wird um den Wert 100 schwanken und geht von der Hauptzeitleiste aus, in der wir auto auf x = 100 positioniert haben. Die Eigenschaften _xmouse und _ymouse gehören also zu den MovieClip-Eigenschaften und nicht zu denjenigen der Maus, wie man vielleicht vermuten könnte. Sie geben die horizontale und vertikale Position der Spitze des Cursor in Bezug auf die angegebene Zeitleiste wieder. Natürlich lassen sich, falls erforderlich, diese Werte wie die Positionsangaben von Movie Clips in Werte anderer Zeitleisten umrechnen (s. Abschnitt Ausdehnung und Koordinaten). Die Eigenschaften _width und _height haben wir bereits kennen gelernt. Sie definieren die Breite und Höhe eines MovieClips in Pixeln. Wenn wir sie explizit setzen, wird der betreffende MovieClip mehr oder minder skaliert. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects ein Quadrat (50 × 50, Linienstärke 0, Farbe beliebig, Position beliebig). 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcQuadrat, Instanzname quadrat, Registrierungspunkt mittig). 4. Weisen Sie actions folgendes Bildskript zu: trace(„Breite: “ + quadrat._width + „; Höhe: “ + quadrat._height);
Ausgabe im Nachrichtenfenster: Breite: 50, Höhe: 50 Flash greift auf die betreffenden Eigenschaften zu und gibt sie uns so aus, wie wir sie im Eigenschaftsfenster eingestellt haben. Das ist jedoch nicht immer so, denn Flash hält eine kleine Überraschung bereit, wenn man mit einem Rahmen arbeitet. 5. Weisen Sie mcQuadrat im Symbolbearbeitungsmodus eine schwarze Linie der Stärke 1 zu. 6. Markieren Sie das komplette Objekt und vergewissern Sie sich im Eigenschaftsfenster, dass dort als Höhe und Breite jeweils 50 angezeigt wird. 7. Kehren Sie zur Hauptzeitleiste zurück und testen Ihre Anwendung. Ausgabe im Nachrichtenfenster: Breite: 51, Höhe: 51
12.1 Eigenschaften von MovieClips
Als Ausgabe würden wir 50 erwarten, der Wert, den wir bei der Erstellung des Quadrats zugewiesen haben. Tatsächlich gibt das Nachrichtenfenster Breite und Höhe mit jeweils 51 an. Das liegt an der Linienstärke, die wir anfangs auf 1 Pixel gesetzt haben. Horizontal und vertikal erhöhen sich automatisch die Dimensionen eines Objektes jeweils um die Linienstärke, was sehr verwirrend sein kann. Wenn Sie diesen Effekt vermeiden wollen, verwenden Sie entweder eine Haarlinie (bzw. Linienstärke 0.25) oder setzen explizit Breite und Höhe per Skripting auf den gewünschten Wert. Eine derartige Eigenschaftsänderung erfolgt analog zu derjenigen der x- und y-Position, d. h. durch eine simple Zuweisung. 8. Fügen Sie nach dem bisherigen Code ein: quadrat._width = quadrat._height = 50; trace(„Breite: “ + quadrat._width + „; Höhe: “ + quadrat._height);
Jetzt betragen Breite und Höhe unabhängig von der Linienstärke exakt 50 Pixel. 9. Fügen Sie nach dem bisherigen Code ein: quadrat._width = 200;
Das Quadrat wird von seinem Registrierungspunkt aus horizontal gestreckt, während wir vertikal keine Änderung vornehmen. Optisch entspricht das einem Skalieren, wie Sie an der nun dicker dargestellten Linie des Objekts erkennen können. Mitunter ist dieser Effekt unerwünscht. 10. Um das Skalieren der Linie zu unterbinden, öffnen Sie den betreffenden MovieClip im Symbolbearbeitungsmodus und wählen unter der Option
123
Skalieren Ohne. Alternativ dazu können Sie auch eine Haarlinie als Linientyp auswählen. Allerdings verringert sich dann die Linienstärke auch im nicht skalierten Objekt. Genau so wie die Position lassen sich Höhe und Breite nicht nur auslesen, sondern auch setzen. Lediglich die Mausposition kann nur ausgelesen werden, was Sinn macht, denn die Maus wird ja bekanntlich vom Anwender gesteuert und sollte sich daher auch genau dort befinden, wo er sie hin bewegt.
12.1.3 _xscale, _yscale Wollen Sie skalieren, müssen Sie nicht unbedingt wie zuvor auf _width bzw. _height zugreifen, denn Flash stellt zu diesem Zweck jeweils eine eigene Eigenschaft zur Verfügung, nämlich _xscale und _yscale. Beide Eigenschaften arbeiten mit Prozent angaben. Weist man 100 Prozent (nur als Zahl, ohne Maßangabe) zu, erhält man die Originalgröße, höhere Werte vergrößern, niedrigere Werte verkleinern das Objekt. Ein Wert von 200 entspricht also der doppelten Größe, bei 50 liegt eine Halbierung vor. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects ein Quadrat (50 × 50, Linienstärke Haarlinie, Farbe beliebig, Position beliebig, Option Skalieren: Kein). 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcQuadrat, Instanzname quadrat, Registrierungspunkt mittig). 4. Weisen Sie actions folgendes Bildskript zu: trace(„Breite quadrat vorher: “ + quadrat._width); quadrat._xscale = 400;
trace(„Breite quadrat nach “ + quadrat._width);
Abbildung 22: Skalieren eines MovieClips
Im Nachrichtenfenster erhalten wir die Angaben 51 für die Breite vor und 204 für die Breite nach der Skalierung. Da Sie in einem der vorhergehenden Schritte die Skalierungseigenschaft auf Kein gestellt haben, bleibt die Linienstärke beim duplizierten und skalierten Quadrat mit derjenigen des nicht skalierten Quadrats identisch. Denselben Effekt können wir mit einer relativen Zuweisung erzielen.
124
Kapitel 12 MovieClip-Klasse
5. Ersetzen Sie die Zuweisung der Eigenschaft _ xscale durch: quadrat._xscale *= 4;
Optisch ergibt sich beim Testen kein Unterschied. Wir nehmen die aktuelle Skalierung in horizontaler Richtung und multiplizieren sie mit 4, d. h. wir vergrößern das Objekt in x-Richtung auf das Vierfache. Ob Sie mit der Zuweisung von 400 oder der Multiplikation mit 4 arbeiten, macht prinzipiell Fall keinen Unterschied. Leider fehlt in AS 2.0 die Möglichkeit, in x- und yRichtung über eine einzelne Eigenschaft eine identische Skalierung zu erreichen. Statt dessen müssen Sie mit den getrennten Eigenschaften für eine vertikale und eine horizontale Skalierung arbeiten. 6. Schreiben Sie am Beginn des Codes: var nScale:Number = 4;
7. Ersetzen Sie die Zuweisung für _xscale durch: quadrat._xscale *= nScale; quadrat._yscale *= nScale;
Ihr Quadrat hat sich jetzt auf das Vierfache seiner Größe aufgeblasen. Um die Zuweisung desselben Wertes zu vereinfachen, richten wir zunächst eine Variable ein, in der wir die gewünschte Zahl speichern. Anschließend weisen wir sowohl der _xscale- wie der _yscale-Eigenschaft diese Variable zu. Diese zweizeilige Zuweisung lässt sich vereinfachen. 8. Ersetzen Sie die im vorhergehenden Schritt geschriebenen Anweisungen durch: quadrat._xscale = quadrat._yscale *= nScale;
}
quadrat.onRelease = quadrat.onReleaseOutside = function(){
this._xscale = 100; }
Klicken Sie auf quadrat, wird es in horizontaler Richtung auf das Doppelte vergrößert. Sobald Sie die Maus loslassen, erhält es wieder seine Originalgröße zurück, was einer Skalierung auf 100 Prozent entspricht. Mit einem kleinen Trick ermöglichen es diese Eigenschaften, Objekte zu spiegeln. Da das bei einem Quadrat eher schwer nachprüfbar sein dürfte, benötigen wir ein neues Objekt. 11. Erstellen Sie auf der Ebene objects eine beliebige, nicht-symmetrische Grafik, z. B. eine kleine Figur. 12. Wandeln Sie die Figur in einen MovieClip um (Bibliotheksname mcFigur, Instanzname figur, Registrierungspunkt mittig). 13. Erweitern Sie Ihren Code folgendermaßen:
figur.onPress = function(){
}
this._xscale = -100;
Klicken Sie auf die Figur, dreht sie sich um, wird also in horizontaler Richtung gespiegelt. Die Spiegelachse entspricht einer vertikalen Linie durch den Registrierungspunkt des MovieClips wie in Abbildung 23 zu sehen. Verschieben Sie den Registrierungspunkt, ändert sich natürlich die Spiegelachse, was eventuell zu einem ungewollten Effekt führt.
Da eine Zuweisung von rechts nach links erfolgt, skaliert Flash zuerst in vertikaler Richtung auf das Vierfache, anschließend weist es der horizontalen Skalierung die vertikale Skalierung zu. Sollten Sie lieber mit der handlicheren Originalgröße des Quadrats vorliebnehmen wollen, so lässt sich diese jederzeit wiederherstellen. Das funktioniert auch dann, wenn Sie mehrfache Skalierung vorgenommen haben. 9. Löschen Sie alle bisherigen Skripte. 10. Schreiben Sie statt dessen:
quadrat.onPress = function(){ this._xscale *= 2;
Abbildung 23: Spiegelung mit _xscale
12.1 Eigenschaften von MovieClips
14. Öffnen Sie mcFigur im Symbolbearbeitungsmodus und verschieben den Registrierungspunkt an den linken Rand. Das erreichen Sie einfach, indem Sie alles markieren und im Eigenschaftsfenster als x-Position 0 eintragen. Wenn Sie jetzt klicken, spiegelt der MovieClip entlang seines linken Randes, was einen völlig anderen visuellen Eindruck ergibt als zuvor. Diese Lösung ermöglicht nur ein einmaliges Spiegeln. Manchmal kann es jedoch sinnvoll sein, permanent zu spiegeln, etwa bei einer Figur, die sich hinund herbewegt. Erreicht sie den Rand, dreht sie sich um und bewegt sich in die andere Richtung. Oder, um es an dieser Stelle einfacher zu halten, bei jedem Klick soll die Figur gespiegelt werden. 15. Verschieben Sie den Registrierungspunkt wieder im Symbolbearbeitungsmodus in die Mitte, indem Sie der Grafik als Position ihre halbe Breite sowie halbe Höhe im Eigenschaftsfenster zuweisen. 16. Ändern Sie das onPress-Ereignis von figur wie folgt (Fettdruck):
figur.onPress = function(){
}
this._xscale *= -1;
Zeigt die Figur anfangs nach rechts, dreht sie sich bei einem Klick nach links. Ein erneuter Klick spiegelt sie, so dass sie wieder in die ursprüngliche Richtung schaut etc. Dahinter steckt nichts anderes als das Ändern des Vorzeichens der Eigenschaft _xscale. Wie wir wissen, besitzt diese Eigenschaft bei einer Grafik, die nicht geändert wurde, den Wert 100 (misstrauische Naturen können dies mit einem trace()-Befehl kontrollieren). Setzen wir diesen Wert auf –100, ändern also ihr Vorzeichen, spiegeln wir die Grafik, wie wir vorhin gesehen haben. Die negative Zahl erhalten wir, indem wir die positive Zahl mit –1 multiplizieren (100 * –1 = –100, wie uns einst die gute alte Schulmathematik lehrte). Multiplizieren wir den negativen Wert erneut mit –1, entsteht eine positive Zahl, was dem ursprünglichen, unveränderten Wert von 100 entspricht. Das Prinzip der Vorzeichenumkehr wird uns noch öfters begegnen, wenn wir Animationen skripten, die zwischen zwei definierten Zuständen hin- und herpendeln.
125
12.1.4 _rotation, _alpha, _visible Drei weitere wichtige Eigenschaften beziehen sich ebenfalls auf die visuelle Darstellung, nämlich _rotation, _alpha und _visible. Die Drehung definiert Flash über die Eigenschaft _rotation, die eine Gradangabe erwartet. Standardmäßig besitzt diese Eigenschaft den Wert 0. Eine Drehung nach rechts entspricht einer Erhöhung, eine Drehung nach links einer Reduktion dieses Wertes. Ein Wert von 360 ergibt eine Volldrehung, die wir aber nicht sehen können, da sie identisch mit einer Drehung um 0 Grad ist. Klaglos nimmt Flash auch höhere Werte an, aus denen sich mittels einer Division durch 360 der richtige Drehwinkel errechnen lässt. Würden wir _rotation auf 450 setzen, entspräche das einer Drehung um 90 (450/360 ergibt einen Rest von 90). 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf der Ebene objects in der Screenmitte einen Smiley (80 × 80), der uns freundlich entgegen lächelt. 3. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcSmiley, Instanzname smiley, Registrierungspunkt mittig). 4. Weisen Sie der Ebene actions folgendes Bildskript zu: smiley.onPress = function(){ this._rotation = 90; }
Ein Mausklick dreht ihn um 90 Grad im Uhrzeigersinn. Entgegen dem Uhrzeigersinn dreht er sich, wenn Sie statt 90 die –90 übergeben. Optisch entspricht dieser Wert einer Dreivierteldrehung, also einer Drehung im Uhrzeigersinn um 270 Grad. Wie bei anderen Eigenschaften ist hier eine relative Zuweisung möglich. 5. Ändern Sie die Zuweisung (Fettdruck): smiley.onPress = function(){ this._rotation += 20; }
Jeder Mausklick erhöht den Drehwinkel um 20, so dass, genügend Geduld und Zeit vorausgesetzt, bei 18 Klicks eine Volldrehung vorliegt. Beim Auslesen ist zu beachten, dass die Eigenschaft _rotation ne-
126
gative Werte annehmen kann, selbst dann, wenn Sie ausschließlich positive Werte zuweisen. 6. Ändern Sie die Zuweisung (Fettdruck): smiley.onPress = function(){ this._rotation += 45;
trace(this._rotation); }
Klicken Sie beim Testen wiederholt auf den Smiley, gibt Flash die betreffenden Rotationswerte im Nachrichtenfenster aus. Bis zum vierten Mausklick einschließlich entspricht das Ergebnis unseren Erwartungen. Die Eigenschaft _rotation enthält sukzessive die Werte 45, 90, 135 und 180. Der fünfte Wert jedoch springt mit –135 aus dem Rahmen. Alle nachfolgenden Werte sind negativ bis zu einer Drehung von 0 Grad, dann beginnt der ganze Prozess wieder von vorne. Der Grund liegt in einer für uns nicht intuitiv nachvollziehbaren Art, wie Flash intern Drehwinkel verrechnet. Alle Drehungen im Uhrzeigersinn entsprechen bis zu einer Halbdrehung einem positiven Wert, alle darüber hinausgehenden Drehungen im Uhrzeigersinn werden intern als Drehung entgegen dem Uhrzeigersinn bis zu einer Halbdrehung interpretiert und ergeben einen negativen Wert. Flash zählt also intern bei _rotation nicht über 180 hinaus, 181 entspricht daher –179. Und eine Dreivierteldrehung mit 270 wird flashintern als Vierteldrehung um –90 behandelt. Optisch können wir diese etwas ungewöhnliche Herangehensweise nicht wahrnehmen, aber sie spielt programmiertechnisch u. U. eine große Rolle, etwa, wenn Sie bestimmte Aktionen vom Drehwinkel eines Objektes abhängig machen wollen. Unabhängig von Positionierung und Größe lässt sich die Sichtbarkeit eines MovieClips mit den Eigenschaften _alpha und _visible gezielt steuern.
Kapitel 12 MovieClip-Klasse
verschwindet. Mit Hilfe einer relativen Wertzuweisung sorgen wir dafür, dass die Eigenschaft _alpha, also die Deckkraft des MovieClips, um 10 reduziert wird. Sie beginnt bei 100 Prozent. Nach 10 Klicks sind wir bei 0 angelangt, so dass der MovieClip nicht mehr zu sehen ist. Aufgrund der reduzierten Deckkraft scheint der Hintergrund immer stärker durch. Selbstverständlich wäre auch eine absolute Wertezuweisung, z. B. this._alpha = 50, möglich. Wer Flashseiten kennt, weiß, wie beliebt Effekte sind, die auf einer Änderung der Deckkraft beruhen. Sie werden unter dem denglischen Begriff des Fading subsummiert. Wir wollen uns das Grundprinzip, das in mehreren unserer Workshops zum Tragen kommt und ausführlicher unten im Kapitel zum Thema Geskriptete Animation behandelt wird, hier anschauen. Denn wie im Falle der Rotation gibt es eine Eigenheit, die wir in der Programmierung beachten müssen. 8. Ersetzen Sie das vollständige onPress-Ereignis durch ein onEnterFrame: smiley.onEnterFrame = function(){ this._alpha –= 5; }
Unser Smiley blendet ohne unser Zutun stetig aus, bis er nicht mehr zu sehen ist. Das onEnterFrame-Ereignis, auf das wir noch zu sprechen kommen, sorgt für die ständige Ausführung unseres Befehls, der vom aktuellen Alphawert jeweils 5 abzieht. Wenn wir bei 100 starten, hat Smiley im nächsten Durchlauf eine Deckkraft von 95 %, dann von 90 % usw. Übrigens wenn wir das onEnterFrame nicht nach einer gewissen Zeit aufheben, zählt Flash im negativen Bereich weiter. Ein Wert von beispielsweise –100 wäre aus Flashsicht korrekt, auch wenn sich eine negative Prozentzahl visuell gar nicht darstellen lässt.
7. Ergänzen Sie das onPress-Ereignis wie folgt (Fettdruck):
9. Fügen Sie innerhalb des onEnterFrame–Ereignis nach der Reduktion des Alphawerts hinzu:
smiley.onPress = function(){
trace(this._alpha);
this._alpha –= 10;
this._rotation += 45;
trace(this._rotation); }
Bei jedem Mausklick hinterlässt unser Smiley einen flüchtigeren Eindruck. bis er (scheinbar) vollständig
Die Zahlen, die Flash jetzt im Nachrichtenfenster präsentiert, entsprechen kaum unserer Erwartung: 94.921875, 89.84375, 84.765625, 79.6875 usw. Flash greift auf einen 8 Bit-Alphakanal zu, der insgesamt 256 verschiedene Abstufungen umfasst. Diese Werte werden jedoch in 100 verschiedenen Prozentwerten ausgedrückt. Flash muss einen Umrechnungsmodus
12.1 Eigenschaften von MovieClips
127
finden, um zwischen den verschiedenen Wertebereichen verrechnen zu können, und rundet anschließend, was in der Ausgabe zu den merkwürdig anmutenden Zahlen führt. Daher gilt hier wie bei _rotation, dass wir Acht geben müssen, wenn wir kontrollieren wollen, ob ein bestimmter Wert erreicht wurde. Beispielsweise würde eine if-Abfrage nach dem Erreichen von exakt 80 % nie true ergeben können. Statt dessen müsste man mit einem Vergleichsoperator wie >= bzw. <= arbeiten. Ein konkretes Beispiel dazu finden Sie im Kapitel Geskriptete Animationen. Wesentlich einfacher gestaltet sich die Verwendung von _visible, da es nur zwei zulässige Werte gibt, nämlich true, wenn ein Objekt dargestellt werden soll, und false, wenn es unsichtbar sein soll.
aktuellen Wert in sein Gegenteil umkehrt (zur Erinnerung: Das Zeichen „!“ bedeutet „nicht“). Das gleiche Prinzip wenn auch in anderer Form haben wir zuvor bei der Multiplikation mit –1 kennen gelernt. Konkret bedeutet das: Wenn die Eigenschaft _visible auf true gesetzt ist, weisen wir per Mausklick das Gegenteil, also false zu. Ist sie false, erhält sie bei erneutem Klick ebenfalls das Gegenteil, was dann true entspricht. Mitunter ist es notwendig, ein Objekt zeitweise unsichtbar zu schalten. Welchen der beiden vorgestellten Wege (_alpha, _visible) sollte man wählen? Obschon beide optisch das gleiche Ergebnis erbringen, reagieren sie völlig unterschiedlich auf Interaktionen des Users.
10. Erstellen Sie auf der Ebene objects an beliebiger Position ein blaues Rechteck (70 × 25). 11. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcRecht, Instanzname recht, Registrierungspunkt mittig). 12. Ersetzen Sie das bisherige Skript vollständig durch:
14. Löschen Sie das blaue Rechteck von der Bühne. 15. Duplizieren Sie den Smiley-MovieClip auf der Bühne. 16. Ändern Sie die Instanznamen beider Smileys in deckkraft bzw. sichtbar, so dass wir nachher im Code erkennen können, bei welchem Clip welche Eigenschaft geändert wurde. 17. Weisen Sie der Ebene actions folgendes Bildskript zu:
recht.onPress = function(){
}
smiley._visible = false;
Durch einen Mausklick können wir den Smiley unsichtbar schalten, optisch erreichen wir damit dasselbe wie bei der Reduktion der Deckkraft auf 0. Anzeigen lassen können wir unseren Smiley, indem wir seine Eigenschaft _visible explizit auf true setzen. Dazu verwenden wir der Einfachheit halber denselben MovieClip, der auch auf unsichtbar schaltet. Wenn ein Schalter zwei verschiedene Zustände annehmen und zwei verschiedene Aktionen auslösen kann, handelt es sich um einen Kippschalter. 13. Ändern Sie den Code (Fettdruck):
recht.onPress = function(){
}
smiley._visible = !smiley._visible;
Nun lässt sich der Smiley per Mausklick entweder ein- oder ausschalten, je nachdem, in welchem Zustand er sich bei Klick befindet. Wir machen uns die Tatsache zunutze, dass _visible nur zwei exakt definierte Boolsche Werte zulässt. Man kann sehr einfach zwischen ihnen umschalten, indem man den
deckkraft._y = sichtbar._y = deckkraft._height/2; deckkraft._x = deckkraft._width/2;
sichtbar._x = deckkraft._x + sichtbar._ width;
Wir positionieren beide MovieClips im linken oberen Bereich der Bühne. Da der Registrierungspunkt bei beiden in der Mitte liegt, entspricht ihre y-Position der halben Höhe der MCs. Beide sind gleich groß, so dass es ausreicht, für die Positionierung die Höhe von nur einem MovieClip heranzuziehen. Durch unsere Berechnung erhalten die MovieClips jeweils dieselbe y-Position. Ihre x-Position dagegen muss sich logischerweise voneinander unterscheiden, sonst liegen beide übereinander. Den ersten MovieClip positionieren wir direkt am linken Bühnenrand, was der halben Breite des Objektes entspricht. Der zweite muss dann nur noch um dessen gesamte Breite nach rechts verschoben werden, so dass beide exakt nebeneinander liegen. Es ist wichtig, die Objekte an einen für uns bekannten Ort zu setzen. Denn nur so können wir per Maus mit ihnen interagieren, wenn sie nicht mehr sichtbar sind.
128
Kapitel 12 MovieClip-Klasse
Abbildung 24: Positionierung der MovieClips
18. Fügen Sie an das Skript folgende Zeilen an:
deckkraft._alpha = 0;
sichtbar._visible = false;
sichtbar.onRollOver = deckkraft.onRollOver = function(){
erweist sich daher i. d. R. die Verwendung von _visible anstelle von _alpha als richtiger Weg. Alle Eigenschaften, die sich auf Position und Größe eines MovieClips beziehen, setzen voraus, dass sich sein Registrierungspunkt an der korrekten Stelle befindet, also i. d. R. links oben oder in der Mitte. Da _width, _x, _rotation etc. immer die konkreten Werte des Registrierungspunktes verwenden, führt ein falsch gesetzter Punkt auch zu unerwünschten Ergebnissen. Solange Sie Ihre MovieClips händisch auf der Bühne anordnen, lässt sich ein derartiger Fehler leicht korrigieren. Das geht jedoch nicht mehr, wenn ein Skript verwendet wird. Daher gewöhnen Sie sich unbedingt an, auf die korrekte Position des Registrierungspunktes zu achten.
trace(this._name);
}
Versuchen Sie, mit der Maus über die Objekte in der linken oberen Ecke zu fahren. Falls Sie nicht völlig daneben liegen, wird Flash im Nachrichtenfenster „deckkraft“ ausgeben, nicht jedoch „sichtbar“. Wir setzen die Eigenschaften _alpha und _visible auf 0, so dass beide MovieClips ausgeblendet werden. Anschließend weisen wir beiden das gleiche Ereignis zu, demzufolge bei einem onRollOver im Nachrichtenfenster der Name desjenigen Objekts ausgegeben wird, über dem sich die Maus gerade befindet. Tatsächlich reagiert nur die Instanz deckkraft, während sichtbar kein Mausereignis mehr wahrnimmt. Trotz visuell gleicher Darstellung behandelt Flash die Eigenschaften _alpha und _visible in Bezug auf Interaktion des Users unterschiedlich. Unabhängig von seinem Alphawert ist ein MovieClip jederzeit aktivierbar, selbst, wenn wir ihn nicht mehr sehen können. Besitzt er ein Mausereignis, wird er in jedem Fall darauf reagieren. Wenn wir das nicht wollen, müssen wir explizit das betreffende Ereignis deaktivieren bzw. löschen (s. u., Ereignisse). Wird dagegen die Eigenschaft _visible auf false gesetzt, gibt es keine aktive grafische Fläche für ein Mausereignis mehr – der MovieClip wird vom Flash-Player schlicht nicht gezeichnet. Daher kann natürlich auch kein Mausereignis mehr ausgelöst werden. Allerdings ist der MovieClip nach wie vor vorhanden, und Skripte, die ihm zugewiesen wurden, werden weiter abgearbeitet (soweit sie eben nicht von Mausereignissen abhängen). Wollen Sie ein Objekt unsichtbar schalten,
12.1.5 blendMode Von Photoshop oder anderen Grafik- und Illustrationsprogrammen her kennen Sie sicher die Möglichkeit, Ebenen verschiedene Modi zuzuweisen, um ausgefallene Effekte zu erzielen. Flash stellt dazu eine eigene Eigenschaft zur Verfügung, mit der man in gewohnter Weise sowohl per Eigenschaftsfenster wie per ActionScript arbeiten kann. 1. Erstellen Sie eine Standarddatei. 2. Setzen Sie die Hintergrundfarbe auf Schwarz. 3. Zeichnen Sie auf objects an beliebiger Stelle ein Quadrat (200 × 200, Linienstärke 2, schwarz) mit einem linearen, horizontal ausgerichteten Farbverlauf von Weiss nach Blau (#4E81F5). 4. Wandeln Sie das Quadrat in einen MovieClip um (Bibliotheksname mcVerlauf, Instanzname verlaufUnten, Registrierungspunkt mittig). 5. Duplizieren Sie die Instanz an beliebiger Stelle auf der Bühne. 6. Drehen Sie das Duplikat um 90 Grad im Uhrzeigersinn. 7. Weisen Sie dem Duplikat den Instanznamen verlaufOben zu. 8. Weisen Sie actions folgendes Bildskript zu: //-------------- vars –------------ var nMode:Number = 0;
//------------ functions –--------- function init() {
12.1 Eigenschaften von MovieClips
verlaufUnten._x = Stage.width/2;
verlaufUnten._y = Stage.height/2;
verlaufOben._x = verlaufUnten._x+50; verlaufOben._y = verlaufUnten._y; trace(verlaufOben.blendMode);
verlaufOben.onPress = function() { nMode++;
this.blendMode = nMode%14+1; trace(this.blendMode); }; }
//------------ aufruf –----------- init();
Beim Testen können Sie per Mausklick auf den oben liegenden MovieClip dessen Ebenenmodus verändern. Anfangs steht er auf normal, wie im Nachrichtenfenster angezeigt wird. Bei jedem Klick zeigt das Nachrichtenfenster den aktuell zugewiesenen Modus. Die meisten Modi führen zu einer Mischung zwischen den Farben des oberen und des unteren MovieClips sowie der Hintergrundfarbe. Flash kennt 14 verschiedene Ebenenmodi, die entweder als String oder als Number zugewiesen werden können. Eingedenk der Tatsache, dass Programmierer und solche, die es werden wollen, aus prinzipiellen Gründen schreibfaul sind, entscheiden wir uns für die kürzere zweite Variante. Dazu initialisieren wir im Variablen-Block nMode mit 0. Diese Variable dient später dazu, den gewünschten Modus zu errechnen. Innerhalb der Funktion init() setzen wir beide MovieClips in die Screenmitte. Um Ebeneneffekte in Bezug auf den Hintergrund erkennen zu können, versetzen wir den oben liegenden MovieClip horizontal um 50 Pixel nach rechts. Damit wird der linke Rand von verlaufUnten zu 50 Pixel frei sichtbar, den Rest verdeckt verlaufOben. Der trace()-Befehl gibt uns am Anfang, bevor wir klicken konnten, den aktuellen Ebenenmodus aus, der, wenn keine andere Einstellung vorgenommen wurde, standardmäßig normal lautet. Wir weisen verlaufOben ein Mausklick-Ereignis zu, das jedes Mal ausgeführt wird, wenn wir auf diesen MovieClip klicken (zum onPress-Ereignis s. u.). Dabei inkrementieren wir nMode und weisen der Eigenschaft blendMode das Ergebnis einer Modulo-
129
Berechnung zu. Wie Sie sich erinnern, gibt Modulo den Restwert einer Division wieder. Nehmen wir an, wir klicken zum ersten Mal auf den Clip. Dann beträgt nMode durch das Inkrement 1. Die Zahl 1 % 14 ergibt 1. Addieren wir 1 hinzu, erhalten wir 2. Diesen Wert weisen wir blendMode zu, so dass der nächste Ebenenmodus angezeigt wird. Im nächsten Durchgang beträgt nMode 2 und durch die Berechnung erhalten wir 3 usw. Wird nMode bis 13 hochgezählt, ergibt die Berechnung 14, den letzten gültigen Wert, da Flash genau 14 verschiedene Ebenenmodi kennt. Danach beträgt nMode 14, was mit Modulo 0 ergibt. Da es keinen blendMode 0 gibt, sorgen wir nach Modulo durch die Addition von 1 dafür, dass wir nie einen unzulässigen Wert erhalten. Wir gelangen nämlich zur 1. Auf diese Weise können wir nMode einfach hoch zählen, erhalten aber dank Modulo immer nur Zahlen zwischen 1 und 14. Im Einzelnen bedeuten die Ebenenmodi:
• normal (bzw. 1): Standardeinstellung, der Movie-
Clip wird unverändert angezeigt. • layer (2): Erstellt eine Bitmap-Kopie des MovieClips im Screenbuffer und wirkt sich nur aus, wenn Sie bestimmte Filter anwenden. • multiply (3): Multipliziert die Farben des Clips mit denjenigen des darunterliegenden Objekts, dividiert durch 255. Als Ergebnis erhält man i. d. R. dunklere Töne. • screen (4): Gewissermaßen das Gegenstück zu multiply, indem die Komplementärfarben miteinander verrechnet werden, wodurch zumeist eine Aufhellung erreicht wird. • lighten (5): Wählt die helleren Farbwerte des MovieClips und des darunter liegenden Objekts aus und erzeugt ein helleres Bild. • darken (6): Wählt die dunkleren Farbwerte des MovieClips und des darunter liegenden Objekts aus und erzeugt ein dunkleres Bild. • difference (7): Subtrahiert die dunklere von der helleren Farbe beider MovieClips. • add (8): Addiert die Farbwerte der direkt übereinander liegenden Bereiche. • subtract (9): Subtrahiert die Werte des MovieClips von denjenigen des darunter liegenden Elements. • invert (10): Verwirft die Farbwerte des oben liegenden Clips vollständig und kehrt die Werte des darunter liegenden Elements um.
130
• alpha
(11): Definiert die Farbpixel als Alphamaske. Dieser Modus und erase funktionieren nur, wenn dem übergeordneten MovieClip der blendMode layer zugewiesen wird. • erase (12): Löscht aus dem Hintergrund entsprechend dem Alphawert der Farbpixel des oben liegenden MovieClips. • overlay (13): Passt die Helligkeit an den Hintergrund an, erhält aber den Kontrast des Vordergrunds. • hardlight (14): Erhöht den Kontrast. Nicht jeder Modus funktioniert mit jedem Motiv gleich gut. Daher sollten Sie mit verschiedenen Motiven experimentieren.
12.1.6 _currentframe, _totalframes Zu den besonderen Stärken eines MovieClips zählt die Tatsache, dass er eine eigene, framebasierte Animationen ermöglichende Zeitleiste enthält (das gilt zwar für ein Grafiksymbol ebenfalls, doch können Sie das nicht per Skripting steuern). Um feststellen zu können, wo man sich innerhalb dieser Zeitleiste befindet, stellt Flash die Eigenschaften _currentframe, die sich auf den aktuell angezeigten Frame bezieht, und _totalframes, also die Gesamtlänge der Zeitleiste, zur Verfügung. Beachten Sie beim Einsatz dieser Eigenschaften, dass Flash in der Schreibweise von dem sonst gültigen Prinzip des Camel Case abweicht, also keine Binnenmajuskel verwendet. 1. Erstellen Sie eine Standarddatei. 2. Erstellen Sie in objects einen leeren MovieClip (Bibliotheksname mcAni). 3. Zeichen Sie im Symbolbearbeitungsmodus von mcAni einen Kreis (50 × 50) auf der Position 0,0. 4. Wandeln Sie den Kreis in einen MovieClip um (Bibliotheksname mcKreis). 5. Fügen Sie im Symbolbearbeitungsmodus von mcAni in Frame 20 eine Schlüsselbild ein, verschieben den Kreis an den rechten Bühnenrand und weisen Frame 1 ein Bewegungstween zu. 6. Kehren Sie zur Hauptzeitleiste zurück. 7. Fügen Sie in der Ebene objects aus der Bibliothek mcAni am linken Bühnenrand ein. 8. Weisen Sie dieser Instanz den namen ani zu.
Kapitel 12 MovieClip-Klasse
9. Weisen Sie der Ebene actions folgendes Bildskript zu: ani.onEnterFrame = function(){ trace(this._currentframe); }
Wenn Sie testen, bewegt sich der Kreis über die Bühne und zeigt im Nachrichtenfenster jeweils seinen aktuellen Frame an. Abhängig vom Erreichen einer bestimmten Framenummer kann man eine Aktion auslösen. 10. Erweitern Sie das Skript innerhalb des onEnterFrame-Ereignisses nach der trace()-Anweisung: if(this._currentframe == this._ total frames){
trace(„Fertig. Beginne wieder von vorne“);
}
Wenn die Animation zu Ende ist, meldet dies Flash und beginnt wie zuvor wieder von vorne. Flash vergleicht permanent die beiden Eigenschaften _currentframe und ._totalframes miteinander. Sobald ihre Werte übereinstimmen, ist das Animationsende erreicht und der Abspielkopf springt zurück zum ersten Frame. Gleichzeitig erfolgt die Ausgabe im Nachrichtenfenster. Weitergehende Interaktionsmöglichkeiten wie etwa die gezielte Navigation zu einem bestimmten Frame bieten sich durch die Verwendung bestimmter Methoden an, die wir unten kennen lernen.
12.2 Ereignisse In den vorangegangenen Beispielen haben wir bereits mehrfach auf Ereignisse zugegriffen, über die Movie Clips verfügen. Flash bietet mehrere system- und userabhängie Ereignisse, die es ermöglichen, gezielt mit MovieClips zu interagieren oder unabhängig von konkreten Aktionen des Anwenders Clips zu kontrollieren. Dazu zählen sowohl MovieClip-spezifische Ereignisse wie auch Schaltflächenereignisse. Wir wollen uns hier nur mit den unverzichtbaren Ereignissen befassen, die in nahezu jeder Applikation eingesetzt werden.
12.2 Ereignisse
12.2.1 onEnterFrame Zu den zweifelsohne am häufigsten verwendeten Ereignissen zählt das onEnterFrame. Dabei handelt es sich um ein permanentes Ereignis, das nach der Zuweisung automatisch von Flash ausgeführt wird. Es entspricht prinzipiell demselben Vorgang, den Flash ausführt, wenn der Abspielkopf in der Zeitleiste einen Frame betritt. Da Animationen oft mit onEnterFrame realisiert werden, kommen wir im Kapitel Geskriptete Animationen ausführlicher auf das Thema zurück. Wir schauen uns hier nur ein simples Beispiel an. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects einen Kreis (50 × 50, beliebige Farbe und Position). 3. Wandeln Sie ihn in einen MovieClip (Bibliotheksname mcKreis, Instanzname kreis) um. 4. Weisen Sie der Ebene actions folgendes Bildskript zu: kreis.onEnterFrame = function(){
trace(„Ich bin ein enterFrame-Ereignis“); }
Beim Testen gibt Flash beredt Auskunft über sein Innenleben, indem das Nachrichtenfenster permanent den an die trace()-Funktion übergebenen String anzeigt. Falls Sie gewohnt sind, das Nachrichtenfenster per Mausklick zu schließen, wird es unmittelbar danach wieder aufgehen und seine Ausgabe fortsetzen. Denn das onEnterFrame–Ereignis tritt ja permanent, d. h. solange, wie der Flashfilm ausgeführt wird, ein. Beachten Sie, dass ein derartiges Ereignis unabhängig von der tatsächlichen Anzahl der Frames in der Zeitleiste des MovieClips ausgeführt wird. Selbst wenn, wie in unserem Beispiel, die betreffende Zeitleiste nur einen einzigen Frame umfasst, tritt das onEnterFrame-Ereignis andauernd ein. Die Geschwindigkeit, mit der das Ereignis ausgeführt wird, entspricht der im Eigenschaftsfenster definierten Bildwiederholrate. Wenn Sie beispielsweise den Wert 18 BpS (Bilder pro Sekunde) eingetragen haben, führt Flash 18 mal pro Sekunde das Skript aus. Da die Bildwiederholrate global für den ganzen Film gilt, wird sich jedes onEnterFrame–Ereignis, egal, welchem Objekt Sie es zugewiesen haben, an diesem Tempo orientieren. Das kann insofern von Nachteil
131
sein, als sich nicht garantieren lässt, dass die von Ihnen gewählte Bildwiederholrate auf jedem Rechner gleich schnell ausgeführt wird. In manchen Fällen bietet es sich daher an, mit der setInterval()-Funktion zu arbeiten. Näheres zu setInterval() finden Sie im Kapitel über Geskriptete Animationen. An dem Verhalten des Nachrichtenfensters haben Sie sicher festgestellt, dass es schön wäre, irgendwann ein solches Ereignis wieder loszuwerden. Hier zeigt sich ActionScript ausgesprochen flexibel, denn wir können zur Laufzeit beliebig derartige Ereignisse zuweisen und wieder löschen. 5. Ändern Sie das bisherige Skript (Fettdruck): var nZahl:Number = 10;
kreis.onEnterFrame = function(){ nZahl –-;
trace(nZahl);
if(nZahl == 0){
delete this.onEnterFrame; } }
Flash zählt die Variable nZahl solange herunter, bis sie den Wert 0 erreicht. Dann wird das onEnterFrame–Ereignis gelöscht und dementsprechend erfolgt im Nachrichtenfenster keine weitere Ausgabe mehr. Der Befehl delete() ermöglicht also jederzeit das Löschen eines beliebigen Ereignisses. Anstatt es zu löschen, können Sie das Ereignis auch zur Laufzeit ändern. 6. Ändern Sie das bisherige Skript (Fettdruck): var nZahl:Number = 10;
kreis.onEnterFrame = function(){ nZahl –-;
trace(nZahl);
if(nZahl == 0){
this.onEnterFrame = function(){ this._x += 10; };
} }
Wie zuvor zählt Flash die Variable herunter. Sobald sie den definierten Grenzwert erreicht, bewegt sich der Kreis nach rechts. Die Bewegung erreichen Sie,
132
indem das onEnterFrame nicht gelöscht, sondern durch ein neues onEnterFrame-Ereignis mit einer geänderten Anweisung ersetzt wird.
12.2.2 Schaltflächenereignisse von MovieClips Das onEnterFrame–Ereignis läuft automatisch ab, was zwar oft erwünscht ist, aber mitunter spannender sind Applikationen, in die der Anwender eingreifen kann. Die bei weitem häufigsten Interaktionsmöglichkeit besteht im Mausklick oder Loslassen der Maustaste. Dem entsprechen in Flash die Ereignisse onPress und onRelease. Damit können wir die vorherige Übung interaktiv gestalten. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects einen Kreis (50 × 50, beliebige Farbe und Position). 3. Wandeln Sie ihn in einen MovieClip (Bibliotheksname mcKreis, Instanzname kreis) um. 4. Weisen Sie der Ebene actions folgendes Bildskript zu: var nZahl:Number = 0;
kreis.onEnterFrame = function(){ nZahl++;
trace(nZahl); }
kreis.onPress = function(){
delete this.onEnterFrame; }
Flash zählt die Variable nZahl solange hoch, bis Sie auf den Kreis klicken. Dann wird das onEnterFrameEreignis gelöscht und dementsprechend erfolgt im Nachrichtenfenster keine weitere Ausgabe mehr. 5. Erweitern Sie das Skript: kreis.onRelease = function() {
this.onEnterFrame = function() { nZahl ++;
trace(nZahl); }; };
Kapitel 12 MovieClip-Klasse
Wie zuvor wird das Hochzählen und Anzeigen der Variablen nZahl durch ein Klick gestoppt. Jetzt können Sie jedoch beim Loslassen der Maustaste den Prozess wieder an der Stelle fortsetzen, an der Sie ihn unterbrochen haben. Da das onEnterFrame-Ereignis in beiden Fällen identisch ist, würde man es in eine para metrisierte Funktion auslagern und diese bei Bedarf aufrufen. 6. Ersetzen Sie das Skript vollständig durch: // ------------- vars –------------–– var nZahl:Number = 0;
// ----------- functions –-------–––-
function zaehlen(pWer:MovieClip):Void { pWer.onEnterFrame = function() { nZahl ++;
trace(nZahl); }; }
kreis.onPress = function() { delete this.onEnterFrame; };
kreis.onRelease = function() { zaehlen(this); };
// ----------- aufrufe –----------––– zaehlen(kreis);
Der besseren Übersicht halber deklarieren wir unsere Variable in einem Variablen-Block zu Beginn des Skripts. Daran schließt sich die Definition einer para metrisierten Funktion namens zaehlen() an, die dem als Argument übergebenen MovieClip das on EnterFrame zuweist. Sobald sie einmal aufgerufen wird, funktioniert unser Skript wie zuvor. Die Struktur des Skripts entspricht derjenigen, die Sie in einem früheren Kapitel kennen gelernt haben. Bei längeren Skripten sollte man sich daran orientieren, da es den Code übersichtlicher gestaltet. User-Interaktionen mit MovieClips werden zumeist über Ereignisse gesteuert, die sich auf irgendwelche Eingabegeräte (insbesondere Maus und Tastatur) beziehen. Hier verfügen MovieClips nicht nur über eigene Ereignisse, sondern sie können, wie gezeigt, zusätzlich auf Schaltflächen-Ereignisse zugreifen, was ihnen sehr weitgehende Möglichkeiten verleiht. Von
12.2 Ereignisse
herausragender Bedeutung sind die verwendeten onPress und onRelease, aber auch onRollOver und onRollOut, mit denen ein umfangreiches Arsenal zur Verfügung steht, um ausgefallenste User-Aktionen zu definieren. Nahezu keiner der im vorliegenden Band angeführten Workshops kommt ohne zumindest eines dieser Ereignisse aus. In den vorhergehenden Beispielen haben wir ja bereits Gebrauch von ihnen gemacht. Wie Sie gesehen haben, bedeutet mc.onPress, dass ein Mausklick auf einem MovieClip erfolgt, und mc.onRelease sowie mc.onReleaseOutside, dass die auf dem MovieClip gedrückte Maus wieder losgelassen wird. Für das Loslassen kennt Flash deshalb zwei verschiedene Ereignisse, um unterscheiden zu können, ob sich die Maus noch über dem betreffenden MovieClip befindet oder nicht. Oft ist es ratsam, bei der Verwendung des onRelease auch ein onReleaseOutside zu definieren, um zu vermeiden, dass ein Anwender die gedrückte Maus sehr schnell aus dem aktivierten MovieClip herauszieht und dann erst loslässt. Ein solcher Fall kann nämlich nur durch ein onReleaseOutside, nicht aber durch ein einfaches onRelease aufgefangen werden. Wir werden unten im Zusammenhang mit der startDrag()-Methode ein konkretes Beispiel kennen lernen. Ein mc.onRollOver wird ausgelöst, wenn Sie mit der Maus über einen MovieClip fahren, und mc.onRollOut meint, dass die Maus den MovieClip wieder verlässt. Dabei ist keine Maustaste gedrückt. Während das Klicken und Loslassen der Maus zumeist für das Auslösen irgendwelcher Aktionen verwendet wird (z. B. Aufruf einer neuen Seite, Start einer Animation, Speichern von Daten etc.), benutzen wir die RollOver- und RollOut-Ereignisse, um das betroffene Objekt visuell zu ändern (z. B. Farbe, Deckkraft, mitunter auch Position etc.). Das Objekt deutet damit an, dass es aktiviert werden kann. Oft wird ein RollOver auch verwendet, um zusätzliche Informationen einzublenden (z. B. Tooltip, ausführlicher Text). Im Zusammenhang mit onEnterFrame wurde gezeigt, dass sich ein Ereignis per delete()-Anweisung löschen lässt. Das gilt natürlich genauso für die erwähnten Schaltflächenereignisse. Manchmal ist es jedoch günstiger, ein Ereignis nur temporär zu deaktivieren, um es später wieder einzuschalten. Das ist mit Schaltflächenereignissen – und nur mit diesen – problemlos möglich.
133
1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects ein blaues Rechteck (90 × 28). 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcRecht, Instanzname recht1, Registrierungspunkt links oben). 4. Fügen Sie auf objects eine Kopie unmittelbar neben dem bestehenden Rechteck ein (Instanzname recht2). 5. Weisen Sie actions folgendes Bildskript zu: recht1.onPress = function(){
trace(„Klick auf „ + this._name); this.enabled = false;
recht2.enabled = true; }
recht2.onPress = function(){
trace(„Klick auf „ + this._name); this.enabled = false;
recht1.enabled = true; }
Wenn Sie auf recht1 klicken, wird dessen Name im Nachrichtenfenster angezeigt. Gleichzeitig ändert sich der Fingercursor in den Standardcursor. Ein weiterer Mausklick bleibt ohne Auswirkungen. Klicken Sie auf recht2, geschieht dort dasselbe, allerdings können Sie danach recht1 wieder anklicken. Jeder MovieClip schaltet bei Auftreten des zugeordneten onPress-Ereignisses seine eigene Funktionalität aus und die des anderen ein. Eine Schaltfläche deutet typischerweise durch einen veränderten Cursor auf seine Funktionalität hin. Dies geschieht durch die Verwendung der Schaltflächenereignisse ebenfalls bei MovieClips. Wenn wir jedoch dessen Ereignisse ausschalten, wäre es verwirrend, wenn der Cursor nach wie vor eine Aktivierungsmöglichkeit andeutet. Daher ändert sich der Cursor nach der Deaktivierung der Schaltfläche automatisch in den Standardcursor. Wenn Sie wollen, können Sie auch unabhängig von dem konkreten Zustand Ihres MovieClips den Handcursor ausschalten. 6. Kommentieren Sie durch zwei vorangestellte // alle Zeilen aus, die sich auf die Eigenschaft en abled beziehen. Alternativ können Sie die betreffenden Zeilen löschen, da wir sie nicht mehr benötigen.
134
Kapitel 12 MovieClip-Klasse
7. Fügen Sie am Anfang des Codes folgende Zeile ein: recht1.useHandCursor = recht2. useHandCursor = false;
Der Handcursor wird gezielt ausgeschaltet, so dass bereits beim ersten onRollOver keine Cursoränderung mehr erfolgt. Die MovieClips werden angezeigt, ohne dass sie einen Hinweis auf ihre Schaltflächenfunktionalität geben. In manchen Situationen ist ein derartiges Verhalten erwünscht. Generell gilt jedoch, dass aus Gründen der Usability ein aktivierbares Element in irgendeiner Form auf seine Funktionalität aufmerksam machen sollte. Die Cursoränderung hat sich als Standard etabliert und sollte dementsprechend auch verwendet werden. Diese anwendergesteuerten Schaltflächenereignisse können zu einem beliebigen Zeitpunkt nur jeweils einmal auftreten. Nehmen wir an, Sie haben auf der Bühne drei MovieClips mit jeweils einem onRelease-Ereignis nebeneinander liegen. Wenn Sie nun auf einem Clip klicken und anschließend die Maustaste loslassen, tritt im Flashfilm genau ein einziges onRelease-Ereignis auf und nur der eben aktivierte MovieClip kann darauf reagieren. Das ist nicht weiter überraschend. Doch was geschieht, wenn alle Objekte übereinander liegen oder sich zumindest in genau dem Bereich überlappen, über dem sich die Maus beim Loslassen befindet? 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects ein Rechteck (100 × 30). 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcRecht, Instanzname recht1, Registrierungspunkt mittig). 4. Fügen Sie zwei Kopien auf der Bühne ein (Instanznamen recht2 und recht3). 5. Ordnen Sie die Kopien so an, dass sie sich in einem Bereich überlappen, wie auf Abbildung 25 zu sehen. Die Drehung realisieren Sie mit Hilfe des Drehen-Werkzeugs der Standard-Symbolleiste. Die Reihenfolge der Clips ergibt sich aus dem Einfügen: Im Hintergrund liegt recht1, darauf recht2 und ganz im Vordergrund recht3. 6. Weisen Sie actions folgendes Bildskript zu: recht1.onRelease = recht2.onRelease = recht3.onRelease = function(){ trace(this._name); }
Abbildung 25: Überlappende MovieClips
Beim Testen klicken Sie zunächst auf jene Bereiche, die sich nicht überlappen. Sie erhalten dann als Ausgabe im Nachrichtenfenster jeweils den Namen des angeklickten MovieClips. Anschließend klicken Sie genau auf die Mitte. Als Ergebnis zeigt das Nachrichtenfenster „recht3“. Obwohl alle Objekte übereinander liegen und jeweils dasselbe Ereignis zugeordnet wurde, reagiert immer nur ein einziger MovieClip. Die betreffenden Ereignisse werden nicht weiter gereicht, sondern treten nur ein einziges Mal auf und sind dann quasi „aufgebraucht“. Die Wahrnehmung eines der hier behandelten Schaltflächenereignisse ist also immer an das Objekt gebunden, das sich direkt unter dem Mauszeiger befindet. Alle anderen unter diesem Objekt liegenden Objekte werden ignoriert. Bei manchen Ereignissen wie onRollOut, onRelease und onReleaseOutside müssen Sie zudem beachten, das sie sich ausschließlich auf das zuvor aktivierte Objekt beziehen. Wenn beispielsweise auf recht1 ein onPress ausgelöst und die gedrückte Maustaste erst auf recht2 losgelassen wird, erfolgt dort kein onRelease-Ereignis mehr. 7. Ordnen Sie die drei Rechtecke horizontal ausgerichtet (Drehung 0 Grad) nebeneinander an. 8. Erweitern Sie das Bildskript: recht1.onPress = recht2. onPress=recht3.onPress=function () {
trace(„Geklickt auf: „+this._name); };
9. Ändern Sie das onRelease-Ereignis (Fettdruck): recht1.onRelease = recht2.onRelease =recht3.onRelease=function () {
trace(„Losgelassen auf: „ + this._ name); };
10. Klicken Sie auf recht1 und lassen die Maustaste wieder los.
12.2 Ereignisse
Ausgabe im Nachrichtenfenster: Geklickt auf: recht1 Losgelassen auf: recht1 Flash nimmt auf dem MovieClip recht1 sowohl ein onPress- wie auch ein onRelease-Ereignis wahr und führt die entsprechenden trace()-Anweisungen aus. 11. Klicken Sie auf recht1 und ziehen die noch gedrückte Maus auf recht2, um sie dort loszulassen. Ausgabe im Nachrichtenfenster: Geklickt auf: recht1 Flash nimmt zunächst ein onPress-Ereignis wahr und gibt den Namen des betroffenen MovieClips im Nachrichtenfenster aus. Nun wartet Flash auf das zugehörige onRelease-Ereignis, das auf recht1 erfolgen muss. Da Sie jedoch gemeinerweise die gedrückte Maus aus recht1 herausziehen, kann dieses Ereignis nie stattfinden. Auch das Loslassen auf recht2 ändert daran nichts, denn damit tritt ja zumindest theoretisch nur auf diesem MovieClip ein onRelease ein. Selbst dieses Ereignis wird ignoriert, da es ein auf demselben Objekt erfolgtes onPress voraussetzt. Das jedoch wurde zuvor auf recht1 ausgelöst. In den meisten Fällen macht ein derartiges Verhalten Sinn, denn schließlich wurde recht1 aktiviert, und nicht irgendein beliebiges anderes Objekt. Bei Menüs allerdings erwartet man eine andere Behandlung der Ereignisse. Das können Sie leicht testen, indem Sie in einem beliebigen, Windows-Konventionen folgenden Programm mit gedrückter Maustaste über verschiedene Menüpunkte fahren. Ausgelöst wird eine Aktion erst dann, wenn Sie loslassen, und es wird exakt derjenige Befehl aufgerufen, auf dessen Icon sich Ihre Maus zuletzt befand. Es spielt also keine Rolle, wo der Mausklick zuerst erfolgte. Glücklicherweise lässt sich dieses Verhalten einfach in Flash nachbilden. 12. Fügen Sie ganz am Anfang des Skripts folgende Zeile ein: recht1.trackAsMenu = recht2.trackAsMenu = recht3.trackAsMenu = true;
Wenn Sie nun den Test von vorhin wiederholen, spielt der Mausklick für das Auslösen des onRelease-Ereignisses keine Rolle mehr. Aufgrund der enormen Flexibilität, die Movie Clips bieten, sollte man zu ihren Gunsten möglichst vollständig auf Schaltflächen verzichten. Selbst die
135
spezifische Zeitleiste von Buttons, die ein bequemes Arbeiten ermöglichen, lassen sich mit MovieClips emulieren. Wie das funktioniert, können Sie im Kapitel Button-Klasse, Abschnitt MovieClips als Schaltflächen, nachlesen.
12.2.3 Maus-Ereignisse Neben den auch in der Button-Klasse vorhandenen Mausereignissen kennen MovieClips eigene Mausereignisse wie onMouseDown, onMouseUp und onMouseMove. Die beiden erstgenannten klingen zwar verdächtig nach onPress und onRelease, unterscheiden sich jedoch deutlich von ihnen. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects einen roten Kreis (Durchmesser 80). 3. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Instanzname kreis1, Registrierungspunkt mittig). 4. Weisen Sie actions folgendes Bildskript zu: kreis1.onMouseDown = function() { trace(„Maus gedrückt“); };
Wenn Sie beim Testen auf den Kreis klicken, erfolgt eine Ausgabe im Nachrichtenfenster. Dies entspricht dem Verhalten eines onPress-Ereignisses, wie wir es oben kennen gelernt haben. Klicken Sie jedoch nicht geordnet teutonisch auf den Kreis, sondern wild in der Gegend herum, also auch neben den Kreis, geschieht genau dasselbe. Im Gegensatz zum onPress ist onMouseDown nicht auf die grafische Fläche des zugeordneten Objekts beschränkt. Jeder beliebige Mausklick löst dieses Ereignis aus. 5. Duplizieren Sie den Kreis auf der Bühne. 6. Weisen Sie dem Duplikat den Instanznamen recht2 zu. 7. Positionieren Sie recht2 so, dass keine Überlappung mit recht1 besteht. 8. Ändern Sie das onMouseDown-Ereignis (Fettdruck): kreis1.onMouseDown = kreis2.onMouseDown = function () { trace(„Maus gedrückt“); };
136
Kapitel 12 MovieClip-Klasse
Ein beliebiger Mausklick wird nun von beiden Movie Clips registriert, so dass im Nachrichtenfenster zwei Ausgaben erfolgen. 9. Verschieben Sie kreis2 so, dass er sich mit kreis1 teilweise überlappt. 10. Klicken Sie beim Testen auf den sich überlappenden Bereich. Die Ausgabe im Nachrichtenfenster ändert sich gegenüber vorher nicht. Da das onMouseDown nicht an ein spezifisches Objekt gebunden ist, kann es von jedem Objekt registriert werden, dem wir ein entsprechendes Ereignis zuordnen, was, wie wir oben gesehen haben, bei einem onPress (bzw. onRelease) nicht zutrifft. Ob Sie sich für ein onPress/onRelease oder ein onMouseDown/onMouseUp entscheiden, hängt also von dem spezifischen Einsatzzweck ab. Möchten Sie, dass nur ein ganz bestimmtes Objekt aktiviert wird, dann wählen Sie das Schaltflächenereignis. Sollen dagegen mehrere Objekte gleichzeitig reagieren, verwenden Sie onMouseDown bzw. onMouseUp. Völlig ohne Pendant unter den Schaltflächenereignissen ist das onMouseMove, das bei jeder beliebigen Mausbewegung ausgelöst wird. Es eignet sich hervorragend, um zahlreiche Effekte zu realisieren wie etwa der klassische Mausverfolger, der im Workshop Effekte vorgestellt wird. 11. Duplizieren Sie einen der Kreise auf der Bühne. 12. Weisen Sie ihm den Instanznamen kreis3 zu. 13. Erweitern Sie das Skript um folgendes Ereignis:
kreis3.onMouseMove = function(){
this._x = Math.ceil(Math. random()*Stage.width); this._y = Math.ceil(Math. random()*Stage.height);
}
Bei jeder Mausbewegung vollführt der eigentlich recht ruhig wirkende kreis3 wilde Sprünge. Sobald ein onMouseMove eintritt, errechnen wir einen von der Bühnenbreite und einen von der Bühnenhöhe abhängigen Zufallswert und weisen ihn jeweils der xsowie y-Eigenschaft von kreis3 zu. Der Kreis erhält dadurch jeweils eine neue Position, was den Eindruck erweckt, er springe wild umher. Unsere Formel ist insofern nicht ganz korrekt, als wir Werte erhalten können, bei denen der Kreis um die Hälfte über einen
Bühnenrand hinausragen kann. Aber zur Demonstration des onMouseMove benötigen wir an dieser Stelle keine präziseren Zahlen.
12.3 MovieClip-Methoden Mit einer noch größeren Fülle als bei den Eigenschaften wartet Flash bei Methoden auf. Wie zuvor konzentrieren wir uns auf einige unverzichtbare Methoden. Weitere Informationen finden Sie in den Kapiteln „Geskriptete Animationen“, „Zeichnungsmethoden“, „Filter“, „Externe Ladevorgänge“ sowie in zahlreichen Workshops des Buches.
12.3.1 Zeitleistensteuerung Mit sehr einfachen Mitteln lässt sich die Zeitleiste eines MovieClips kontrollieren, etwa, um eine händisch erstellte Animation mit Hilfe von MovieClip-Buttons zu steuern. 1. Erstellen Sie eine Standarddatei. 2. Importieren Sie eine Grafik in die Bibliothek. Sie sollte natürlich kleiner als die Bühne sein, um etwas Platz für Schaltflächen zu lassen. Alternativ können Sie auch einen beliebigen MovieClip zeichnen. 3. Erstellen Sie einen leeren MovieClip (Bibliotheksname mcAnimation). Er dient als Behälter für eine Animation der Grafik. 4. Erstellen Sie einen weiteren leeren MovieClip (Bibliotheksname mcBild) als Behälter für die Grafik. 5. Fügen Sie im Symbolbearbeitungsmodus von mcBild die Grafik auf der Position 0,0 ein. 6. Öffnen Sie mcAnimation im Symbolbearbeitungsmodus. 7. Fügen Sie dort mcBild auf der Position 0,0 ein. 8. Setzen Sie im Eigenschaftsfenster die Deckkraft der eingefügten Instanz von mcBild auf 0 %, so dass die Grafik unsichtbar wird. 9. Erstellen Sie in Frame 20 ein Schlüsselbild. 10. Ändern Sie die Deckkraft auf 100 %. 11. Fügen Sie in Frame 40 ein Schlüsselbild ein. 12. Ändern Sie die Deckkraft auf 0 %.
12.3 MovieClip-Methoden
13. Weisen Sie Frame 1 und Frame 20 jeweils ein Bewegungstween zu. Damit haben Sie eine Animation erstellt, bei der unsere Grafik zunächst einund anschließend ausblendet. 14. Kehren Sie zur Hauptzeitleiste zurück. 15. Fügen Sie dort auf der Ebene objects den MovieClip mcAnimation an beliebiger Position ein (Instanzname animation). 16. Fügen Sie in der Hauptzeitleiste oberhalb von objects eine neue Ebene namens buttons ein. 17. Zeichnen Sie auf buttons ein blaues Rechteck (90 × 28). 18. Wandeln Sie es in einen MovieClip um (Bibliotheksname btnPlay, Instanzname bPlay, Registrierungspunkt oben links). 19. Öffnen Sie btnPlay im Symbolbearbeitungsmodus. 20. Fügen Sie eine neue Ebene namens txt ein. 21. Fügen Sie dort ein statisches Textfeld mit dem Text „Play“ ein. Die Eigenschaften des Textfeldes sind beliebig. 22. Duplizieren Sie btnPlay viermal in der Bibliothek (Bibliotheksnamen btnStop, btnGo20, btnGo1, btnGoStop20). 23. Ändern Sie jeweils die Texte der Duplikate in „Stop“, „Go to 20“, „Go to 1“ und „Go Stop 20“. 24. Fügen Sie in der Hauptzeitleiste auf buttons alle Duplikate ein. 25. Weisen Sie ihnen folgende Instanznamen zu: bStop, bGo20, bGo1 und bGoStop20. Ihre Bühne müsste jetzt ungefähr aussehen wie in der Abbildung. Zur Verdeutlichung der Position der im ersten Frame unsichtbaren Animation wurde das entsprechende Begrenzungsrechteck eingeblendet (s. Abbildung 26). Wenn Sie testen, wird die Animation andauernd abgespielt, d. h., Ihr Bild blendet permanent ein und aus. Da es keinerlei Befehle gibt, die sich auf die Frames des MovieClips mit der Animation beziehen, spielt Flash einfach alle Frames ab. Ist das Ende erreicht, springt Flash zurück zum ersten Frame und das Spiel beginnt wieder von vorne. 26. Weisen Sie actions folgendes Bildskript zu:
animation.stop();
Wie Sie bereits vorher im Zusammenhang mit unserem Pseudo-Button gesehen haben, können wir eine Zeitleiste anhalten, indem wir den Befehl stop()
137
Abbildung 26: Beispielhafter Aufbau der Übung
verwenden. Dasselbe geschieht auch hier: Wir weisen die Instanz animation an, das Abspielen der Frames anzuhalten. 27. Erweitern Sie das Bildskript:
bPlay.onPress = function(){
animation.play();
trace(„Frame: „+animation._currentframe);
}
bStop.onPress = function(){
animation.stop();
trace(„Frame: „+animation._currentframe);
}
bGo1.onPress = function(){
animation.gotoAndPlay(1);
trace(„Frame: „+animation._currentframe);
}
bGo20.onPress = function(){
animation.gotoAndPlay(20);
trace(„Frame: „+animation._currentframe);
}
bGoStop20.onPress = function(){
animation.gotoAndStop(20);
trace(„Frame: „+animation._currentframe);
}
138
Sie sind nun in der Lage, auf die Animation gezielt mit Hilfe der Buttons Einfluss zu nehmen. Bei jedem Klick führt Flash den betreffenden Befehl aus und gibt denjenigen Frame im Nachrichtenfenster an, der anschließend zu sehen ist. Flash bietet, wie das Beispiel zeigt, mehrere Methoden an, mit denen wir eine Zeitleiste vergleichbar einer Videosteuerung steuern können:
• mcZeitleiste.play(): Spielt die Zeitleiste ab dem aktuellen Frame.
• mcZeitleiste.stop(): Hält die Zeitleiste an. • mcZeitleiste.gotoAndPlay(Wert): Springt zu dem angegebenen Frame und spielt die Zeitleiste ab. • mcZeitleiste.gotoAndStop(Wert): Springt zu dem angegebenen Frame und hält dort an. Die Befehle dürften selbsterklärend sein. Wie der oben eingefügte trace()-Befehl verdeutlicht, hält Flash eine bestimmte Reihenfolge ein: Die Zeitleisten-Methoden werden unmittelbar nach ihrem Aufruf ausgeführt, so dass sich zu dem Zeitpunkt, zu dem wir trace() aufrufen, die Zeitleiste der Instanz animation bereits an ihrem Ziel befindet. Voraussetzung für das Funktionieren dieser Methoden ist natürlich das Vorhandensein einer Zeitleiste mit mindestens 2 Frames. Wenn Ihre Zeitleiste nur 1 Frame umfasst und die Animation komplett über ActionScript definiert wird, nützen Ihnen die Befehle nichts. Wir haben die Animation in einen eigenen MovieClip ausgelagert, so dass die Elemente der Hauptzeitleiste davon unabhängig sind. Sie bewahren sich damit eine größere Flexibilität. Natürlich wäre es auch möglich gewesen, direkt in der Hauptzeitleiste zu animieren. Dann müssten Sie den Pfad im Skript anpassen und anstatt animation.methode() eine relative Adressierung mit this._parent. methode() oder eine absolute Adressierung mit _root.methode() verwenden (.methode() steht hier stellvertretend für die betreffenden Methoden). In unserem Beispiel übergeben wir den Frame, zu dem Flash springen soll, als konkrete Zahl. Eine alternative Möglichkeit besteht darin, den Frames einen Namen zuzuweisen und sie dann über diesen Namen anzusteuern. Das hat den Vorteil, dass bei einer Verschiebung der Frames unser Code nicht geändert werden muss, solange die Bildmarkierungen mit verschoben werden. Arbeitet man statt dessen mit Frame
Kapitel 12 MovieClip-Klasse
nummern, müssen die Zahlen im Code korrigiert werden. 28. Öffnen Sie mcAnimation im Symbolbearbeitungsmodus. 29. Fügen Sie eine neue Ebene namens marker ein. 30. Weisen Sie dem ersten Schlüsselbild auf marker im Eigenschaftsfenster die Bildmarkierung „anfang“ zu. 31. Fügen Sie auf marker in Frame 20 ein Schlüsselbild ein. 32. Weisen Sie diesem Schlüsselbild die Bildmarkierung „mitte“ zu. 33. Korrigieren Sie den Code wie folgt (Fettdruck):
bGo1.onPress = function() {
animation.gotoAndPlay(„anfang“); trace(„Frame: „+animation._currentframe);
};
bGo20.onPress = function() {
animation.gotoAndPlay(„mitte“);
trace(„Frame: „+animation._currentframe);
};
bGoStop20.onPress = function() {
animation.gotoAndStop(„mitte“);
trace(„Frame: „+animation._currentframe);
};
Beim Testen arbeitet die Steuerung wie vorher. Sie können nun jedoch die Frames der Animation (einschließlich der Bildmarkierungen) nach Belieben verschieben, um die Animation zu verkürzen oder zu verlängern. Danach funktioniert das Skript, ohne dass Sie es irgendwo anpassen müssten.
12.3.2 Objekte dynamisch einfügen Im vorhergehenden Beispiel haben wir die benötigten MovieClips händisch erstellt und auf der Bühne eingefügt. Das ist dann sinnvoll, wenn wir von vorneherein genau wissen, wie viele Objekte verwendet werden und dass der User auf jeden Fall diese Objekte sehen muss. Es existieren jedoch zahlreiche Anwendungen, in denen diese Bedingungen nicht zutreffen.
12.3 MovieClip-Methoden
Bei einer Bildergalerie wissen wir beispielsweise nicht, welche Bilder sich der Anwender tatsächlich anschauen möchte. In einem Spiel müssen bestimmte Objekte erst zu einem späteren Zeitpunkt eingeblendet werden, und ihre konkrete Anzahl hängt u. U. vom Verhalten des Spielers oder vom Zufall ab. In solchen Fällen wäre es besser, dynamisch die benötigten Objekte zu erstellen und einzufügen. Es zählt zu den besonderen Stärken des Skripting in Flash, dass wir diesbezüglich gleich über mehrere Möglichkeiten verfügen. Bevor wir uns die konkreten Methoden anschauen, ist ein Blick auf die Art notwendig, wie Flash intern Objekte verwaltet. Sie haben in Flash und sicher auch in anderen Programmen mit Ebenen gearbeitet, um festzulegen, welches Objekt sich vor welchem anderen Objekt befindet. Je höher Ihre Ebene im Ebenenstapel angeordnet ist, desto weiter oben liegt der darauf befindliche Content. Dieses Konzept ermöglicht es uns, auf eine intuitive Art Objekte anzuordnen. Intern allerdings arbeitet Flash nicht einfach mit Ebenen, die eigentlich nur ein Hilfskonstrukt darstellen. Statt dessen erhält jedes Objekt automatisch eine Tiefe zugeordnet, also eine Zahl, die bei –16383 beginnt. Auf diesen Wert haben wir zunächst keinen direkten Einfluss. Es ist nicht möglich, im Eigenschaftsfenster einen entsprechenden Wert einzugeben. Wir können ihn jedoch anzeigen lassen, falls es sich bei dem betreffenden Objekt um ein Symbol handelt. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects ein beliebiges Objekt. 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcDing, Instanzname ding1, Registrierungspunkt links oben). 4. Fügen Sie in actions folgendes Bildskript ein: trace(„ding1: ”+ding1.getDepth());
Ausgabe im Nachrichtenfenster: –16383 Mit der Methode getDepth() lesen wir den zugewiesenen Tiefenwert aus. Da noch kein anderes Objekt außer ding1 existiert, liegt dessen Tiefe bei den erwähnten –16383. Jedes weitere in derselben Ebene erhält einen um 2 höheren Wert. Das nächste Objekt müsste also eine Tiefe von –16381 besitzen. 5. Duplizieren Sie ding1 auf der Bühne (Instanzname ding2).
139
6. Erweitern Sie das Skript: trace(„ding2: ”+ding2.getDepth());
Ausgabe im Nachrichtenfenster: ding1: –16383 ding2: –16381 Da das neue Objekt eine automatische Tiefe erhält, die höher als diejenige des vorhergehenden Objekts ist, würde es sich bei einer Überlappung im Vordergrund befinden. Liegen die Objekte auf verschiedenen Ebenen, wird deren Reihenfolge in der zugewiesenen Tiefe automatisch berücksichtigt. 7. Fügen Sie oberhalb von objects eine neue Ebene namens objects2 ein. 8. Verschieben Sie ding1 auf diese Ebene. Ausgabe im Nachrichtenfenster: ding1: –16381 ding2: –16383 Die Tiefen wurden gegenüber vorher vertauscht. Da sich das zuerst eingefügte Objekt, nämlich ding1, jetzt auf einer höheren Ebene als ding2 befindet, benötigt es dementsprechend eine höhere Tiefe. Flash weist ihm automatisch –16381 zu, während ding2 auf den niedrigeren Wert von –16383 gesetzt wird. Weil diese Hierarchie wichtig für die Darstellung auf dem Screen ist, können zwei beliebige Objekte nie denselben Tiefenwert besitzen, falls sie sich in derselben Zeitleiste befinden. Was hier am Beispiel der Hauptzeitleiste demonstriert wurde, gilt für jede beliebige Zeitleiste, d. h., dass in MovieClips eingefügte Symbole ebenfalls mit der Tiefe von –16383 beginnen. Bei dynamisch erzeugten Objekten müssen wir Flash alle benötigten Informationen mitgeben, die ansonsten entweder im Eigenschaftsfenster eingestellt (z. B. Instanzname) oder automatisch zugewiesen würden (Tiefe). Betrachten wir zunächst das Erstellen eines leeren MovieClips. 9. Löschen Sie alle Objekte von der Bühne. 10. Ersetzen Sie das Skript vollständig durch: var mBeh:MovieClip = this. createEmpty MovieClip(„behaelter“,1);
Wenn Sie nun voll Vorfreude testen, werden Sie leider nichts sehen können – ein leerer MovieClip ist eben leer. Wir haben eine Variable deklariert, deren Datentyp wir als MovieClip festlegen. Sie wird mit einer Referenz auf einen mit der Methode create-
140
Kapitel 12 MovieClip-Klasse
EmptyMovieClip() erzeugten leeren MovieClip in-
itialisiert. Diese Methode benötigt eine Zeitleiste, in der wir den neuen MC ablegen wollen. In unserem Fall handelt es sich um die Hauptzeitleiste, die wir mit this ansprechen. Wie wir oben gesehen haben, ermittelt Flash bei Verwendung von this automatisch das korrekte Objekt, ohne das wir einen absoluten Pfad übergeben müssten. Des weiteren legen wir den Instanznamen mit behaelter fest und definieren eine Tiefe mit 1. Die Verwendung einer Variablen erleichtert später das Skripten und bietet darüber hinaus den Vorteil, dass im Skriptfenster automatisch Codehinweise eingeblendet werden, auf die wir ansonsten erst Zugriff hätten, wenn wir der etwas merkwürdigen Flash-Konvention mit einem spezifischen Suffix folgen würden. Trotz der gähnenden Leere auf dem Screen sind wir in der Lage festzustellen, ob der MovieClip denn überhaupt existiert. 11. Ergänzen Sie das Skript:
trace(mBeh);
Ausgabe im Nachrichtenfenster: _level0.behaelter Da Flash einen Objektpfad ausgibt, muss das von uns eingerichtete Objekt auch tatsächlich vorhanden sein. Nun fragen Sie sich sicher, welchen Sinn es denn machen sollte, einen leeren MovieClip dynamisch zu erzeugen. Schließlich sind wir ja nicht in der Lage, Flash per AS anzuweisen, eine bereits gezeichnete Form in diesen MovieClip hineinzulegen, so, wie wir es auf der Bühne durch Markieren der entsprechenden Illustration und anschließendes Drücken von tun könnten. Ein leerer MC ist dennoch von überragender Bedeutung für die AS-Programmierung, denn er dient uns als Behälter für Objekte, die wir zur Laufzeit entweder aus der Bibliothek einfügen, extern laden oder per Zeichnungsmethoden und BitmapData erstellen. Wir werden daher in den Workshops permanent mit leeren MovieClips arbeiten. Wie Sie oben am Beispiel des Autos mit den drehenden Rädern gesehen haben, bietet die Behälterfunktionalität eines leeren Clips die ideale Möglichkeit, Daten strukturiert zu verwalten. Alle Objekte, die von der Logik her zusammen gehören, können wir in ihn einfügen, um viel einfacher mit ihnen arbeiten zu können. So bietet es sich beispielsweise an, alle Elemente, die zu einem Spiel gehören, in einen über-
geordneten, leeren MovieClip abzulegen. Die entsprechende Vorgehensweise demonstriert der Workshop Spiele. Oder externe Assets werden in einen übergeordneten Behälter geladen, so dass sie sich mit einem einzigen Befehl alle gleichzeitig ändern lassen (z. positionieren, einblenden etc.), eine Methode, die gerne etwa bei dynamischen Galerien verwendet wird, wie der Workshop Bildergalerie zeigt. Ein derartiger Behälter bedarf logischerweise eines konkreten Inhalts, der auf verschiedene Art und Weise entstehen kann:
• mc.attachMovie(): Ein mit einer Verknüpfung
versehenes Symbol aus der Bibliothek wird in den Behälter eingefügt. • mc.attachAudio(): Eine Audioquelle (Mikrophon oder Video als netStream-Objekt) wird dem Behälter zugewiesen. • mc.attachBitmap(): Der Behälter dient dazu, eine Bitmap anzuzeigen. Weitere Informationen finden Sie im Kapitel zur BitmapData-Klasse. • mc.loadMovie(): Eine externe Datei (swf, gif, png, jpg) wird in den Behälter geladen. Mit diesem Thema befasst sich das Kapitel Externe Assets ausführlicher. • mc.lineStyle(), mc.lineTo() etc. : Mit Hilfe der sogenannten Zeichnungsmethoden wird in den MovieClip gezeichnet. Dies entspricht dem Zeichnen auf der Bühne per Werkzeuge der Werkzeugpalette und wird im Abschnitt zu den MovieClip-Zeichnungsmethoden behandelt. • mc.createTextField(): Ein leeres Textfeld wird mit spezifischen, änderbaren Standardeigenschaften im angegebenen MovieClip erzeugt, womit wir uns im Kapitel Text auseinandersetzen. Um ein Objekt per attachMovie() einfügen zu können, muss in der Bibliothek ein entsprechendes Objekt – genauer: ein Symbol (Schaltfläche oder MovieClip) – vorhanden sein. 12. Zeichnen Sie einen roten Kreis (100 × 100). Die Ebene, auf der Sie ihn erstellen, spielt keine Rolle. 13. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Registrierungspunkt mittig). 14. Löschen Sie ihn in der Hauptzeitleiste, da wir ihn per Skripting einfügen wollen. Es besteht keine Möglichkeit, mit ActionScript direkt ein Objekt in der Bibliothek anzusprechen. Statt des-
12.3 MovieClip-Methoden
sen müssen wir explizit einen Verknüpfungsbezeichner (auch: Exportname) zuweisen, der sich dann im Skript verwenden lässt. 15. Klicken Sie in der Bibliothek mit der rechten Maustaste auf mcKreis. Achten Sie darauf, dass Sie sich nicht im Vorschaufenster des Clips befinden, sondern an der Stelle, an der Symbol und Name des Objekts zu sehen sind. 16. Wählen Sie die Option . 17. Klicken Sie unter auf <Export für ActionScript >. 18. Flash schlägt automatisch den Bibliotheksnamen des MovieClips an, den Sie getrost übernehmen können. Gleichzeitig wird die Option <Exportieren in erstes Bild> aktiviert. 19. Bestätigen Sie mit . Durch die Zuweisung eines Exportnamens weisen wir Flash an, beim Erstellen der swf das bezeichnete Objekt auch dann einzubinden, wenn es nicht auf der Bühne eingefügt wurde. Gleichzeitig sorgen wir dafür, dass es bereits im ersten Frame zur Verfügung steht, so dass wir in einem dort vorhandenen Skript darauf zugreifen können. Diese Prozedur ist wichtig, da Flash aus Optimierungsgründen bei der Erstellung einer swf nur diejenigen Elemente in der Bibliothek einbindet, die auf der Bühne Verwendung finden. Da ein per Skripting eingefügtes Element zu diesem Zeitpunkt naturgemäß nicht auf der Bühne anzutreffen ist, müssen wir Flash per Verknüpfungsnamen mitteilen, das es dennoch benötigt wird. 20. Ersetzen Sie den trace()-Befehl durch:
var mCont:MovieClip;
mCont = mMC.attachMovie(„mcKreis“, „kreis“,1);
Wenn Sie testen, hängt in der linken oberen Ecke ein roter Kreis. Wir richten eine weitere Variable ein, um das in den Behälter eingefügte Objekt aufzunehmen. Dadurch ersparen wir uns insbesondere die Angabe langer Pfadnamen. Anschließend weisen wir der Variablen konkreten Inhalt zu. Dabei fügen wir per attachMovie() das in der Bibliothek verknüpfte Objekt ein, wobei die Angabe folgender Argumente notwendig ist:
• Verknüpfungsname. Er kann wie in unserem Fall als String, also im Klartext, oder als Variable übergeben oder per Konkatenation errechnet werden.
141
• Instanzname. Für ihn gilt dasselbe wie für den Ver-
knüpfungsnamen. • Tiefe. Es wird empfohlen, eine Ganzzahl >= 0 zu wählen, so dass Sie nicht Gefahr laufen, einen von Flash bereits automatisch zugewiesenen, negativen Wert zu überschreiben. Zudem müssen Objekte auf einer positiven Tiefe liegen, um sie wieder löschen zu können. Beachten Sie, dass wir bei der Übergabe einer nichtexistenten Verknüpfung leider keine Fehlermeldung erhalten. Statt dessen ignoriert Flash unsere Anweisung und die Bühne bleibt leer. Obgleich zulässig, sollten Sie keinen Instanznamen wählen, der dem Verknüpfungsnamen entspricht. Es macht einen verwirrenden Eindruck, für verschiedene Elemente in einem Skript die gleichen Bezeichnungen vorzufinden. Und spätestens dann, wenn Sie zwei Instanzen desselben Symbols erzeugen, gehen Ihnen die Namen aus – denn jeder Name muss eindeutig sein. Die fraglos ungewöhnliche Position des eingefügten Kreises hängt weniger mit einem störrischen Charakter als vielmehr mit der Tatsache zusammen, dass wir Flash keine Information gegeben haben, wo wir ihn ablegen wollen. Wenn wir ihn händisch einfügen, ergibt sich die Position natürlich automatisch durch das Loslassen der Maustaste bzw. die Stelle, an der wir gezeichnet haben. Ein derartiger Vorgang fehlt jedoch beim dynamischen Einfügen. Daher verwendet Flash einfach einen Defaultwert, bei dem es sich jeweils um den Koordinatenursprung derjenigen Zeitleiste handelt, in die wir etwas einfügen. 21. Erweitern Sie das Skript:
mCont._x = mCont._width/2;
mCont._y = mCont._height/2;
Der MovieClip wird nach dem Einfügen an den linken und an den oberen Rand verschoben, so dass wir ihn vollständig sehen können. Würde sich der Registrierungspunkt in mcKreis nicht in der Mitte, sondern links oben befinden, wäre es nicht notwendig, eine neue Position zuzuweisen. In dem Fall läge er mit dem Defaultwert bereits an der gewünschten Stelle. Da wir in mCont eine Referenz gespeichert haben, können wir den Kreis jederzeit über diese Variable ansprechen. Andernfalls müssten wir schreiben: behaelter.kreis._x = behaelter.kreis._ width/2;
142
Kapitel 12 MovieClip-Klasse
Um ihn in der Mitte abzulegen, müssten Sie die Positionszuweisung folgendermaßen ändern: mCont._x = Stage.width/2;
mCont._y = Stage.height/2;
Natürlich lassen sich auf die gleiche Art alle möglichen Eigenschaften ansprechen und mit spezifischen Werten initialisieren. Alternativ können Sie direkt bei der Verwendung von attachMovie() ein init-Objekt mitgeben, in dem die gewünschten Angaben bereits enthalten sind. Das Objekt kann entweder direkt in diesem Aufruf definiert oder vorher eingerichtet und an attachMovie() übergeben werden. 22. Erweitern Sie den attachMovie()-Aufruf (Fettdruck): mCont = mMC.attachMovie(„mcKreis“, „kreis“,1,{_x:50,_y:50});
23. Löschen Sie die danach folgenden beiden Zeilen mit der Zuweisung der x- und y-Position. Optisch bemerken wir keinen Unterschied, da die in der geschweiften Klammer übergebenen Werte denjenigen entsprechen, die wir in den beiden zusätzlichen Zeilen festgelegt hatten. Die Funktionalität bleibt die gleiche. Da die MovieClip-Klasse eine Unterklasse der Objekt-Klasse darstellt, können wir die so eingerichteten Objekteigenschaften auf den konkreten MovieClip übertragen. Achten Sie auf die Schreibweise innerhalb der geschweiften Klammern: Die Initialisierung der konkreten Werte für die x- und die yPosition erfolgt per Doppelpunkt und nicht mit einem Zuweisungsoperator. 24. Fügen Sie zwischen Deklaration und Initialisierung von mCont folgende Zeilen ein:
var oPos:Object = new Object(); oPos._x = oPos._y=50;
25. Ändern Sie den attachMovie()-Aufruf (Fettdruck): mCont = mMC.attachMovie(„mcKreis“, „kreis“,1, oPos);
Das Ergebnis ist wieder identisch, aber diesmal haben wir ein eigenes Objekt mit den gewünschten Eigenschaften eingerichtet, das wir bei attachMovie() als Argument übergeben. Gegenüber vorher hat dies den Vorteil, dass man, wenn weitere Symbole an dieser Position eingefügt werden sollen, jedes Mal auf
das Objekt zugreifen kann, ohne es neu schreiben zu müssen. Wir können dem Objekt beliebig komplexe Eigenschaften, Ereignisse und Methoden zuordnen, um sie auf diese Weise automatisch auf alles zu übertragen, was wir dynamisch einfügen. 26. Fügen Sie zwischen die Zeilen mit der Initialisierung der x-Eigenschaft von oPos und dem Aufruf von attachMovie() folgende Ereignisdeklaration ein:
oPos.onPress = function(){
}
trace(„Autsch!“);
Ein Mausklick auf den Kreis ruft nun ein empörtes „Autsch!“ im Nachrichtenfenster hervor. Trotz der großen Flexibilität, die wir damit gewinnen, hat dieses Vorgehen leider einen Nachteil, der in der Natur der Sache begründet ist. In Schritt 21 konnten wir mit dynamischen Werten, nämlich der Breite und Höhe des Kreises, arbeiten. Egal, welche konkreten Werte vorliegen, führt dieses Skript immer dazu, dass unser Kreis exakt in der linken oberen Ecke platziert wird. Verwenden wir statt dessen ein init-Objekt, ist zu dem Zeitpunkt, wo wir es einrichten, der Kreis noch nicht auf der Bühne vorhanden und damit sind seine Eigenschaften _width und _height undefiniert. Wir müssen dem Objekt also notgedrungen konkrete Werte (wie in unserem Fall jeweils die 50) mit geben, was jedes Mal dann zu einer Korrektur des Codes zwingt, wenn wir händisch die Größe von mc Kreis modifizieren. Daran ändert sich nichts, wenn wir unmittelbar in attachMovie() wie in Schritt 22 ein solches Objekt einrichten. Denn erst nach Ausführung dieser Zeile existiert unser Kreis auf der Bühne, und erst dann lassen sich seine Breite und Höhe auslesen. Insofern bietet sich trotz unbestreitbarer Vorteile nicht immer die Verwendung eines solchen Objekts an. Wie die Wissenschaft herausgefunden hat, sind Kreise ausgesprochen gesellig. Wir wollen unserem Kreis daher einen Kollegen zur Seite stellen. 27. Fügen Sie am Ende des Skripts folgende Zeile ein: mCont = mMC.attachMovie(„mcKreis“, „kreis2“, 2, {_x: oPos._x + 100, _y: oPos._y});
Ein zweiter Artgenosse schmiegt sich rechts an den ersten Kreis.
12.3 MovieClip-Methoden
Seine Einrichtung erfolgt analog zu derjenigen des ersten: Wir weisen mCont eine Referenz auf die neue Instanz zu, die per attachMovie() erzeugt wird. Der Eindeutigkeit halber erhält der neue Kreis einen vom vorhergehenden verschiedenen Instanznamen sowie eine andere Tiefe. Weisen wir den gleichen Instanznamen kreis zu, erhalten wir zwar keine Fehlermeldung, aber Flash weiß dann im Fall der Fälle nicht, welche Instanz konkret gemeint sei. Wir wären nicht mehr in der Lage, jeden der Kreise einzeln anzusprechen. Der zuletzt eingefügte würde einfach alle Befehle ignorieren. Weisen wir die gleiche Tiefe zu, wird dagegen der erste Kreis gnadenlos gelöscht. Sie können das gerne testen, indem Sie beim zweiten Einfügen die Tiefe von 2 auf 1 setzen. Der bedauernswerte Kreis in der linken oberen Ecke wird daraufhin verschwinden. Während wir beim ersten Mal das init-Objekt übergeben, verwenden wir beim zweiten Mal nur einige Eigenschaften des Objekts, nämlich die x-Position, die wir mit der Breite des vorher eingefügten Kreises verrechnen, sowie die y-Psoition. Hätten wir wie beim ersten Kreis einfach oPos übergeben, würden beide Kreise ununterscheidbar übereinander liegen. Wollen Sie an dieser Stelle nicht auf das onPressEreignis verzichten, können Sie es ebenfalls einfach zuweisen. 28. Ändern Sie den attachMovie()-Aufruf für den zweiten Kreis (Fettdruck): mCont = mMC.attachMovie(„mcKreis“, „kreis2“, 2, {_x:oPos._x+100, _y:oPos._y, onPress:oPos.onPress});
Wir weisen dem onPress-Ereignis des neuen Kreises dasjenige des Objektes zu. Das oPos.onPress darf nicht mit runden Klammern abgeschlossen werden, da Flash ansonsten davon ausgeht, dass Sie das betreffende Ereignis unmittelbar ausführen wollen. In dem Fall würde uns direkt nach Start der Anwendung das Nachrichtenfenster ein überraschendes „Autsch!“ entgegen geschleudert, obwohl wir auf kein Objekt geklickt haben. Darüber hinaus würde kreis2 jeden Mausklick ignorieren, aber einen veränderten Cursor anzeigen, da wir, wenn auch fehlerhaft, ein onPress zugewiesen haben. Übrigens ist es beim zweiten Kreis durchaus möglich, mit dynamischen Werten zu arbeiten, denn nun ist die Kreisbreite durch den vorher eingefügten MovieClip bekannt.
143
29. Ändern Sie den attachMovie()-Aufruf für den zweiten Kreis (Fettdruck): mCont = mMC.attachMovie(„mcKreis“, „kreis2“, 2, {_x:oPos._x+mCont._ width, _y:oPos._y, onPress:oPos. onPress});
Zur Berechnung der korrekten horizontalen Position verwenden wir die Breite des ersten Kreises, auf den nach wie vor eine Referenz in mCont gespeichert ist. Diese wird erst überschrieben, sobald der zweite Kreis eingefügt wurde. Wahre Geselligkeit erschöpft sich nicht in einer Zweierbeziehung. Je mehr, desto besser. Wir wollen beispielsweise 5 Kreise einblenden. Würden wir wie bisher vorgehen, müssten Sie das Skript mehrfach kopieren und einfügen, was eine ausgesprochen mühsame und fehleranfällige Vorgehensweise ist. Aus Gründen der Übersichtlichkeit wollen wir mit einer neuen Datei arbeiten. 1. Erstellen Sie eine Standarddatei (oder: löschen Sie das komplette Skript in der vorher verwendeten Datei und fahren Sie mit Schritt 5 fort). 2. Zeichnen Sie einen Kreis (100 × 100). Die Ebene, auf der Sie ihn erstellen, spielt keine Rolle. 3. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Registrierungspunkt mittig). Dabei können Sie auch direkt einen Verknüpfungsnamen zuweisen (mcKreis). Sollten Sie die entsprechende Option nicht sehen, versteckt sie sich unter der rechts unten im Fenster angezeigten Schaltfläche „Erweitert. 4. Löschen Sie den Kreis in der Hauptzeitleiste, da wir ihn per Skripting einfügen wollen. 5. Weisen Sie actions folgendes Bildskript zu: //-------------- vars –-------------
var mMC:MovieClip = this.createEmpty MovieClip(„behaelter“, 1); var mCont:MovieClip;
//----------- functions –----------- function init(pNum:Number):Void {
for (var i:Number = 0; i
mCont._x = i*mCont._width+mCont._ width/2; mCont._y = Math.floor(Math. random()*300)+mCont._height/2;
144
} }
//------------- aufruf –------------ init(5);
Es werden fünf Objekte horizontal nebeneinander, aber an beliebiger vertikaler Stelle eingefügt. Zunächst legen wir einen Variablen-Block an, in dem notwendige Variablen deklariert und teilweise mit konkreten Werten initialisiert werden. Es handelt sich dabei um den Behälter, in den wir die übrigen Objekte einfügen wollen, sowie um eine noch nicht initialisierte Variable, die später sukzessive alle Objekte referenziert. Im Funktions-Block legen wir eine parametrisierte init()-Funktion fest, die alles zur späteren Verwendung vorbereitet. In unserem Fall handelt es sich bei der Vorbereitung allerdings lediglich um das Einblenden von Objekten ohne weitere Funktionalität. In einer Schleife, deren Anzahl an Durchläufen durch den zu übergebenden Parameter definiert wird, führen wir drei Aktionen aus:
• Der Variablen mCont weisen wir eine Referenz auf
einen in mMC eingefügten MovieClip mit dem Verknüpfungs- bzw. Exportnamen „mcKreis“, einem dynamisch zusammengesetzten Instanznamen, bestehend aus „kreis“ zuzüglich der Zählvariablen i, und einer an i gebundenen Tiefe. Im ersten Durchlauf lautet der Instanzname kreis0, die Tiefe liegt bei 0. Im nächsten Durchlauf erhöht sich i aufgrund des Inkrements im Schleifenkopf auf 1, so dass der nächste Instanzname kreis1 lautet und die nächste Tiefe bei 1 liegt. Dies wiederholt sich solange, bis 5 Objekte auf der Bühne vorliegen. • Unmittelbar nach dem Einfügen eines Objektes setzen wir dessen horizontale Position auf ein Vielfaches von dessen Breite zuzüglich seiner halben Breite. Am Anfang beträgt i 0, so dass wir mit konkreten Werten folgende Berechnung erhalten: 0 * 100 + 50, was eine x-Position von 50 ergibt. Beim zweiten Schleifendurchlauf beträgt i 1, was durch die Berechnung 1 * 100 + 50 zu einer x-Position von 150 führt. Der zweite Kreis wird also horizontal unmittelbar neben dem ersten positioniert. Alle weiteren Kreise werden jeweils um den gleichen Betrag nach rechts versetzt. • Die vertikale Position ermitteln wir diesmal unabhängig von der Zählvariablen i. Wir verwenden
Kapitel 12 MovieClip-Klasse
jeweils einen Zufallswert, der zwischen 0 und 299 liegt, zuzüglich der halben Objekthöhe, so dass kein Kreis am oberen Rand über die Bühne hinausragt. Der kleinstmögliche Wert beträgt damit 50, der größtmögliche 349. Abschließend erfolgt der Aufruf von init(), wobei das Argument 5 übergeben wird. Diese Zahl legt die Anzahl der Objekte fest. Bei fünf kontaktfreudigen Kreisen bleibt alles noch recht überschaubar. Ganz anders sieht es aus, wenn zusätzlich Dreiecke, Rechtecke und andere Gesellen die Bühne betreten – oder, schlicht formuliert, sich die Anzahl der dynamisch eingefügten Objekte drastisch erhöht. Dann läuft man Gefahr, den Überblick zu verlieren und weist schnell mal eine Tiefe zu, die bereits vorher vergeben worden ist. Das führt zu einem ungewollten Überschreiben des betreffenden Objekts. Abhilfe schafft die Methode getNextHighest Depth(). 6. Ändern Sie innerhalb der Schleife das zweite und dritte Argument der attachMovie()-Methode (Fettdruck): mCont = mMC.attachMovie(„mcKreis“, „kreis“+ mMC.getNextHighestDepth(), mMC.getNextHighestDepth());
Anstatt für die Tiefe einen konkreten Wert zu übergeben, lassen wir Flash ermitteln, welcher Wert zulässig ist. Die Methode gibt den jeweils nächst höheren, noch nicht belegten Wert, beginnend bei 0, zurück. Sie bezieht sich immer auf die konkrete Zeitleiste, die wir als Objekt dem Aufruf der Methode voranstellen. Im vorliegenden Beispiel wird also nur nach einer neuen Tiefe innerhalb von mMC gesucht, was nichts mit Tiefen in anderen Zeitleisten zu tun hat. So ist es möglich, dass unsere Methode in Bezug auf mMC den Wert 70 zurück gibt, weil auf den Tiefen 1 bis 69 bereits Objekte liegen, während in der Hauptzeitleiste die nächst höhere Tiefe vielleicht 2 beträgt. Ebenfalls mit Hilfe der Tiefe sorgen wir für eindeutige Instanznamen. In vielen Fällen ist das die bequemste und zugleich sicherste Methode, um Fehler bei der Tiefe zu vermeiden. Sollten Sie bei einzelnen Objekten gezielt die Tiefe ändern wollen, haben Sie die Möglichkeit, die betreffenden Werte nachträglich sowohl abzufragen wie auch gezielt zu setzen.
12.3 MovieClip-Methoden
7. Fügen Sie innerhalb der Schleife unmittelbar nach der Zuweisung der y-Position folgende Zeilen ein: mCont.onPress = tiefe;
8. Fügen Sie im Funktions-Block nach der Deklaration von init() folgende Definition ein: function tiefe(){
trace(„Die Tiefe von „ + this._name + „ beträgt: „ + this.getDepth()); }
Klicken Sie beim Testen auf die Objekte, geben Sie Auskunft über ihre Tiefe. Ein Klick auf den letzten Kreis ergibt beispielsweise folgende Ausgabe im Nachrichtenfenster: Die Tiefe von kreis4 beträgt: 4 Wir weisen jedem in mCont referenzierten Movie Clip ein onPress-Ereignis zu, so dass bei Mausklick im Nachrichtenfenster der Name des aktivierten Objekts und dessen Tiefe ausgegeben wird. Wenig überraschend entspricht die Tiefe jedes Mal dem Suffix des Instanznamens, also kreis0 liegt auf 0, kreis1 auf 1 usw. Da wir vor den Kreisen kein anderes Objekt in mMC einfügen, beträgt die erste dort verfügbare Tiefe 0, ein Wert, der zufälligerweise auch mit dem ersten Wert unserer Zählvariable i übereinstimmt. Warum dann die ganze Mühe mit dem soviel längeren getNextHighestDepth() anstelle des knackig-kurzen i? 9. Fügen Sie ganz am Ende des Skripts einen zweiten Aufruf von init() ein: init(5);
Zehn prächtige Kreise begrüßen Sie auf dem Screen, da die Funktion init() zweimal hintereinander mit dem gleichen Argument aufgerufen wird. Wir können alle 10 Kreise sehen, weil jeder eine eigene, eindeutige Tiefe erhält. Hätten wir dagegen wie zuvor in der init()-Funktion die Tiefe mit i festgelegt, stünden nur die Werte von 0 bis 4 zur Verfügung – was naturgemäß für 10 Objekte nicht ausreicht, da jeder Wert dann zweimal vorkommt. Sie können das testen, indem Sie vorübergehend innerhalb der init()-Funktion das mMC.getNextHighestDepth() jeweils durch i ersetzen. Dann bleiben nur 5 Kreise auf der Bühne übrig. Wenn sich viele Objekte einen relativ begrenzten Platz teilen müssen, ist es unvermeidbar, dass sie
145
sich überlappen. Mitunter benötigen wir dann eine Möglichkeit, auf User-Interaktion das unten liegende Objekt in den Vordergrund zu holen (ein konkretes Beispiel findet sich im Workshop „Spiele“, Abschnitt „Puzzle“). 10. Fügen Sie in der Deklaration von tiefe() nach dem trace()-Befehl folgende Zeilen ein: this.swapDepths(mMC.kreis0);
trace(„Neue Tiefe: „+this.getDepth());
Wenn Sie einen beliebigen Kreis anklicken, wird er seine Tiefe mit derjenigen des ersten Kreises tauschen. Die Methode swapDepths() kann auf zwei Arten verwendet werden:
• mc.swapDepths(andererMC):
Die Tiefe des angesprochenen MovieClips wird mit derjenigen getauscht, der als Argument im Klammerausdruck steht. • mc.swapDepths(Zahl): Die Tiefe des angesprochenen MovieClips wird auf den Wert gesetzt, der als Argument im Klammerausdruck steht. Wir haben uns für die erstgenannte Variante entschieden, aber Sie können gerne die zweite ausprobieren, indem Sie im Variablen-Block eine entsprechende Variable initialisieren und sie beim Aufruf von swap Depths() in der Klammer übergeben. Alternativ dazu lassen sich dynamische Inhalte erzeugen, indem wir ein bereits bestehendes Objekt duplizieren. Dabei spielt es keine Rolle, ob dieses Objekt händisch auf die Bühne abgelegt oder per attachMovie() eingefügt wurde. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects an beliebiger Position einen roten Kreis (100 × 100). 3. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Instanzname kreis1, Registrierungspunkt mittig). 4. Weisen Sie actions folgendes Bildskript zu: kreis1.onPress = function(){
this.duplicateMovieClip(„kreis2“,1); }
Momentan sehen Sie nach einem Klick auf kreis1 noch nichts. Tatsächlich führt Flash Ihren Befehl aus, dupliziert also denjenigen MovieClip, dem Sie die Methode duplicateMovieClip() zugeordnet ha-
146
ben, in der Zeitleiste, in der er sich befindet. Aber da keine Positionsangaben vorliegen, setzt Flash das Duplikat einfach an die Stelle des Originals, so dass sich beide vollständig überlappen.. 5. Fügen Sie unmittelbar vor der schließenden Klammer ein: k reis2._x = Math.floor(Math. random()*Stage.width); k reis2._y = Math.floor(Math. random()*Stage.height);
Jetzt erhält die Kopie eine zufällige x- und y-Position, hüpft also scheinbar bei jedem Klick über den Screen. Wenn Sie versuchen, auf die Kopie zu klicken, werden Sie feststellen, dass keine Reaktion erfolgt. Das Duplikat übernimmt zwar die Eigenschaften des Originals, nicht jedoch dessen Ereignisse. Wäre das Ihr Ziel, müssten Sie es explizit skripten. 6. Ersetzen Sie das bisherige Skript vollständig durch: //------------- vars –-------------- var mMC:MovieClip;
var nTiefe:Number = 1;
//---------- functions –------------ kreis1.onPress = function() {
mMC = this.duplicateMovieClip („kreis“+nTiefe, nTiefe); mMC._x = Math.floor(Math. random()*Stage.width); mMC._y = Math.floor(Math. random()*Stage.height);
mMC.onPress = this.onPress; nTiefe++; };
Wenn Sie testen, können Sie per Klick auf einen beliebigen Kreis diesen duplizieren. Jedes Duplikat übernimmt also das dem Original zugewiesene onPressEreignis. Im Variablen-Block legen wir mMC an, um später jeweils eine Referenz auf das Duplikat aufzunehmen. Die Variable nTiefe sorgt dafür, dass wir bei jedem Kopiervorgang jeweils eine eindeutige Tiefe zuweisen können. Statt dessen wäre es natürlich auch möglich, mit getNextHighestDepth() zu arbeiten. Bei jedem Klick duplizieren wir den angeklickten
Kapitel 12 MovieClip-Klasse
MovieClip und weisen dem Duplikat eine Zufallsposition zu. Der Instanzname des Duplikats muss dynamisch gebildet werden, da er jedes Mal wechselt. Der Einfachheit halber binden wir ihn, wie wir es bereits bei der attachMovie()-Methode kennen gelernt haben, an die für die Tiefe zuständige Variable. Da diese immer einmalig und eindeutig sein muss, erzeugen wir so auch eindeutige Namen. Das onPress-Ereignis des angeklickten Clips kopieren wir anschließend in das onPress-Ereignis des Duplikats. Dadurch verhalten sich alle auf der Bühne vorhandenen Objekte exakt gleich. Abschließend inkrementieren wir nTiefe. Im Laufe der bisherigen Kapitel haben Sie sicher den einen oder anderen roten Kreis näher kennen gelernt. So angenehm deren Natur auch sein mag, so verspürt man dennoch von Zeit zu Zeit den Wunsch, sie wieder los zu werden. Dazu stellt Flash eine ausgesprochen simpel zu verwendende Methode zur Verfügung. 7. Erweitern Sie das onPress-Ereignis unmittelbar nach dem Inkrement von nTiefe: this.onPress = function(){ this.removeMovieClip(); }
Wie zuvor erzeugt ein Klick auf einen MovieClip eine Kopie. Wenn Sie dann jedoch ein zweites Mal auf denselben Clip klicken, verschwindet er – mit Ausnahme des ersten, händisch eingefügten Kreises. Bei einem Mausklick ersetzen wir das bestehende, für den Kopiervorgang verantwortliche onPress-Ereignis durch ein anderes onPress, das lediglich die removeMovieClip()-Methode aufruft. Ihr übergeben wir als Ziel das angeklickte Objekt, was zu seinem Löschen führt. Nur der erste Kreis erweist sich als ausgesprochen hartnäckig, er lässt sich scheinbar nicht von der Bühne entfernen. Die removeMovieClip()Methode löscht laut Flash-Hilfe nur Objekte, die mit attachMovie(), createEmptyMovieClip() und duplicateMovieClip() erzeugt wurden. Das ist jedoch nicht ganz korrekt. Tatsächlich wirkt sie sich unabhängig von deren Erzeugung nur auf MovieClips aus, deren Tiefe >= 0 ist. Würden wir Clips dynamisch auf einer negativen Tiefe erzeugen – was durchaus möglich ist –, dann bliebe ein removeMovieClip() folgenlos.
12.3 MovieClip-Methoden
8. Fügen Sie innerhalb des gerade zugewiesenen onPress-Ereignisses unmittelbar vor dem Aufruf von removeMovieClip() ein: this.swapDepths(0);
Selbst der sich einst so tapfer widersetzende erste Kreis gewinnt an Einsicht und weicht beim wiederholten Mausklick. Wir setzen einfach jeden angeklickten Clip auf eine Tiefe, von der wir wissen, dass sie nicht negativ ist und sich dort kein anderes Objekt befindet (ansonsten würden wir dieses Objekt durch den Tiefentausch unbeabsichtigt löschen). Damit sind wir in der Lage, die removeMovieClip()-Methode zu verwenden. Sowohl beim Erzeugen wie beim Löschen spielen negative und positive Tiefen eine wichtige Rolle. Die Entwickler von Flash haben dies bewusst eingebaut, um strikt zwischen händisch und dynamisch erstellten Elementen trennen zu können. Insofern ist es nur logisch, dass removeMovieClip() ausschließlich bei positiven Tiefen verwendet werden kann. Sie sollten sich daher an dieser Konvention orientieren und alle dynamischen Elemente auf positiven Tiefen einfügen. Damit vermeiden Sie das unbeabsichtigte Löschen von zuvor per Hand eingefügten MovieClips. Wenn Sie dennoch einmal eine negative Tiefe zuweisen müssen, dann wählen Sie möglichst einen Wert irgendwo zwischen –1 und –1000. Damit stehen ihnen einerseits genügend Werte zur Verfügung, andererseits laufen Sie garantiert nicht Gefahr, ein bestehendes Objekt zu überschreiben.
12.3.3 Drag and Drop Zur Interaktion mit Objekten gehört ein klassisches Verhalten, dass wir von nahezu jeder Applikation gewohnt sind, nämlich das Verschieben bzw. drag and drop. Ob als Fenster, das wir hin- und herschieben können, als Icon, das wir auf ein anderes Symbol ziehen oder als Objekt, das permanent am Cursor klebt – überall steckt die gleiche Funktionalität dahinter. Sie lässt sich in Flash mit einer eigenen Methode nachbilden. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects an beliebiger Position ein blaues Quadrat (80 × 80).
147
3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcQuadrat, Instanzname quadrat, Registrierungspunkt mittig). 4. Zeichnen Sie auf objects an beliebiger Position ein orangenes Dreieck (80 × 80). Achten Sie darauf, dass es das Quadrat weder überlappt noch berührt. 5. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcDreieck, Instanzname dreieck, Registrierungspunkt mittig). 6. Weisen Sie actions folgendes Bildskript zu: quadrat.onPress = dreieck. onPress=function () { this.startDrag(); };
quadrat.onRelease = dreieck. onRelease=function () { this.stopDrag(); };
Wenn Sie auf eines der Objekte klicken, können Sie es bei gedrückter Maustaste über den Screen verschieben. Beim Loslassen bleibt es an der Stelle liegen, an der das onRelease-Ereignis eintritt. Die Methoden startDrag() und stopDrag() implementieren ein Ziehen, bei dem die Position des aktivierten Objekts automatisch and die Mausposition gebunden wird. Das einzige, das wir definieren müssen, ist das Ereignis, wann dieses Verhalten eintreten soll. In unserem Fall wählen wir ein onPress für den Beginn und ein onRelease für das Ende des Ziehvorgangs. Das entspricht der üblichen Vorgehensweise, aber prinzipiell können Sie jede Art von Ereignis wählen. Vermutlich haben Sie recht vernünftig und ohne große Hektik eines der Objekte über den Bildschirm gezogen. Es gibt jedoch keine Garantie, dass sich der Benutzer Ihrer Applikation ebenso verhält. Ein nervöser Finger kann nämlich zu einem überraschenden Ergebnis führen. 7. Testen Sie erneut, indem Sie ein Objekt anklicken, anschließend bei gedrückter Maustaste heftig hinund herschütteln und plötzlich loslassen. Das von Ihnen aktivierte Objekt wird nach dem Loslassen mehr Anhänglichkeit beweisen als Ihnen vermutlich lieb ist: Es bleibt am Mauszeiger kleben, obwohl die Taste nicht mehr gedrückt wird. Durch das heftige
148
Schütteln kann es vorkommen, dass sich der Mauszeiger beim Loslassen außerhalb des MovieClips befindet. Dort kann er jedoch kein onRelease-Ereignis mehr wahrnehmen. Fehlt dieses Ereignis, wird auch die stopDrag()-Methode nicht aufgerufen, so dass Flash weiterhin das Ziehverhalten ausführt. 8. Fügen Sie ein onReleaseOutside-Ereignis ein: dreieck.onReleaseOutside = quadrat. onReleaseOutside=function () { this.stopDrag(); };
Selbst ein aufgrund des nervösen Fingers hyperaktives Objekt wird jetzt auf Wunsch brav innehalten. Es spielt keine Rolle mehr, ob sich der Cursor beim Loslassen über dem aktivierten Objekt befindet oder nicht. Entscheidend ist nur das Auftreten des onPress-Ereignisses, das zwangsläufig wahrgenommen wird, sonst könnte ja kein Ziehen erfolgen. Danach wird jedes Loslassen der Maustaste so interpretiert, als beziehe es sich auf den aktivierten Clip. Aufgrund der Reihenfolge, in der wir die Objekte erstellt haben, befindet sich das Quadrat hinsichtlich seiner Tiefe unter dem Dreieck. Wenn wir das Quadrat ziehen und dabei das Dreieck überlappen, entsteht ein merkwürdiger Eindruck, der unserer Alltagserfahrung widerspricht. Ein Objekt, das wir bewegen, tritt gegenüber den übrigen Objekten immer in den Vordergrund. Daher sollten Sie bei drag and drop-Verhalten mit Hilfe von swapDepths() dafür sorgen, dass das gezogene Objekt vor den anderen liegt. In diesem Beispiel orientiert sich das Ziehverhalten an der Position, an der wir auf den MovieClip klicken. Mitunter ist es jedoch erwünscht, dass sich das Objekt mit seinem Mittelpunkt am Cursor ausrichtet. 9. Erweitern Sie die startDrag()-Methode (Fettdruck): this.startDrag(true);
Bei einem Klick springt der aktivierte MovieClip mit seinem Registrierungspunkt an die Mausposition, so dass wir es exakt an seiner Mitte anfassen und ziehen können. Die startDrag()-Methode lässt sich wie zuvor ohne und mit Argumenten aufrufen. Als erstes Argument können wir einen Bool’schen Wert übergeben, der Flash anweist, den Registrierungspunkt an die Maus zu binden. Verzichten wir auf das Argument,
Kapitel 12 MovieClip-Klasse
geht Flash automatisch von einem false aus und wir erhalten das Verhalten, das wir oben kennen lernten. 10. Erweitern Sie die startDrag()-Methode (Fettdruck): this.startDrag(true, 40, 40, 360, 300);
11. Fügen Sie ganz am Anfang des Skripts ein:
quadrat._x = quadrat._y = 80;
dreieck._x = dreieck._y = 180;
Die Objekte lassen sich jetzt nur noch innerhalb eines eng definierten Bereichs ziehen. Beim Aufruf der startDrag()-Methode übergeben wir die Koordinaten eines gedachten Rechtecks, dessen linke obere Ecke bei 40,40 und rechte untere Ecke bei 360,300 liegt. Die Koordinaten beziehen sich jeweils auf die Zeitleiste, in die wir das ziehbare Objekt eingefügt haben. Das Ziehen erfolgt solange, wie sich der Registrierungspunkt des jeweils aktivierten MovieClips in diesem Rahmen befindet. Andernfalls stoppt die Bewegung automatisch. Um zu gewährleisten, dass sich die Objekte vor einem Mausklick nicht außerhalb der Grenzen befinden, haben wir ihre Position in unserem Beispiel per Skripting festgelegt. Zwar würde das Ziehen auch ohne diese beiden Zeilen funktionieren, aber es sähe merkwürdig aus, weil die angeklickten Objekte einen Sprung in das gedachte Rechteck machen. Die Begrenzung ermöglicht es uns, beispielsweise schnell einen Slider zu programmieren. Im Gegensatz zu startDrag() lässt stopDrag() keine Übergabe von Argumenten zu. Die startDrag()- bzw. stopDrag()-Methode kann jeweils nur für einen einzigen MovieClip aufgerufen werden. Wir wären damit beispielsweise nicht in der Lage, bei Klick auf das Dreieck gleichzeitig auch das Quadrat zu ziehen. Ein derartiges Verhalten müsste man mit onEnterFrame, setInterval() oder onMouseMove realisieren.
12.3.4 Kollision Wenn Objekte bewegt werden, kann es wichtig sein, herauszufinden, ob sie irgendwo mit anderen Objekten kollidieren. Das bekannteste Beispiel stellen zweifelsohne Spiele dar, in denen Kollisionen eine
12.3 MovieClip-Methoden
149
enorme Rolle zukommt. So geht es in jeder Art von „Ballerspiel“ darum, einerseits irgendwelchen Objekten auszuweichen und andererseits Objekte zu zerstören. Dabei testet man jedes Mal, ob eine Kollision stattgefunden hat. Oder es müssen in einem Lernprogramm Objekte einander per drag and drop zugeordnet werden. In diesen und zahlreichen anderen Fällen kommt zumeist die hitTest()-Methode zum Einsatz.
5. Erweitern Sie das onRelease/onReleaseOutside-Ereignis nach der stopDrag()-Methode:
1. Sie können an dieser Stelle die Flash-Datei aus der vorhergehenden Übung verwenden. Dann fahren Sie mit Schritt 3 fort. Ansonsten erstellen Sie eine Standarddatei. 2. Erstellen Sie auf objects wie in der vorhergehenden Übung, Schritte 2 bis 5, ein blaues Quadrat (80 × 80, MovieClip, Bibliotheksname mcQuadrat, Instanzname quadrat, Registrierungspunkt mittig) und ein orangenes Dreieck (80 × 80, MovieClip, Bibliotheksname mcDreieck, Instanzname dreieck, Registrierungspunkt mittig). Achten Sie darauf, dass sich die Objekte nicht überlappen. 3. Weisen Sie actions folgendes Bildskript zu:
trace(„Kollision!“);
dreieck._x = 80;
dreieck._y = 100;
quadrat._x = dreieck._x+dreieck._ width * 2; quadrat._y = dreieck._y;
quadrat.onPress=function () { this.startDrag(); };
quadrat.onRelease = quadrat. onReleaseOutside=function () { this.stopDrag(); };
Die Objekte werden per Skript im linken oberen Bildschirmbereich angeordnet. Das Quadrat befindet sich auf gleicher Höhe wie das Dreieck, aber um dessen Breite nach rechts versetzt, um etwas Abstand zwischen ihnen zu schaffen. Im Gegensatz zu vorher kann nur das Quadrat gezogen werden. 4. Erweitern Sie das onPress-Ereignis nach der startDrag()-Methode: this.onEnterFrame = kollision;
delete this.onEnterFrame;
6. Fügen Sie am Ende des Skripts folgende Deklaration ein: function kollision() {
if (this.hitTest(dreieck)) { } }
Wenn Sie beim Testen das Quadrat horizontal auf das Dreieck ziehen, öffnet sich das Nachrichtenfenster und gibt ständig die Meldung „Kollision“ aus. Wenn wir herausfinden wollen, ob eine Kollision stattfindet, müssen wir zunächst überlegen, wann eine Kontrolle Sinn macht. In unserem Fall setzt eine Kollision logischerweise voraus, dass das Quadrat bewegt wird. Also müssen wir ab dem Augenblick, in dem auf das Quadrat geklickt wird, die Kontrolle durchführen. Das geschieht, in dem wir im onPressEreignis ein onEnterFrame-Ereignis einrichten, das permanent die Funktion kollision() ausführt. Wenn wir die Maustaste loslassen, erfolgt keine Bewegung mehr und die Kontrolle ist infolgedessen überflüssig. Daher löschen wir das onEnterFrameEreignis im onRelease-Ereignis. Die Funktion kollision() ruft die Methode hitTest() auf. Dieser Methode ordnen wir einen MovieClip zu, dessen Begrenzungsrechteck daraufhin getestet wird, ob es sich mit demjenigen des in der Klammer übergebenen MovieClips überschneidet. Das Schlüsselwort this bezieht sich in unserem Beispiel auf quadrat, dessen Begrenzungsrechteck auf Überlappungen mit dreieck kontrolliert wird. Die Methode gibt true zurück, wenn sie sich berühren, andernfalls false. Sobald eine Berührung festgestellt wird, erfolgt der Aufruf des trace()-Befehls und damit die Ausgabe im Nachrichtenfenster. Zweifelsohne ist das eine ausgesprochen bequeme Methode, um eine Kollision zu überprüfen. Sie hat jedoch den schwerwiegenden Nachteil, mit Begrenzungsrechtecken zu arbeiten. Denn wenn Sie das Quadrat nicht horizontal, sondern leicht nach oben versetzt auf das Dreieck ziehen, erfolgt bereits eine aufgeregte Kollisionsmeldung, noch bevor sich die Objekte optisch berühren, wie in Abbildung 27 zu sehen.
150
Abbildung 27: Kollisionserkennung mit hitTest()
Solange Sie mit Objekten arbeiten, die annähernd eine rechteckige Form besitzen, stellt das kein Problem dar. Doch wie oft kommt das tatsächlich vor? Es dürfte wesentlich häufiger der Fall sein, dass Ihre Objekte eine komplexere Form besitzen, und dann versagt diese Methode. Zwar gibt es noch eine alternative Verwendung von hitTest(), bei der wir einen Punkt gegen ein Begrenzungsrechteck kontrollieren können. 7. Fügen Sie unmittelbar vor der Deklaration von kollision() ein: dreieck.onMouseMove = kollisionMaus;
8. Fügen Sie am Ende des Skripts ein: function kollisionMaus() {
if (this.hitTest(_xmouse, _ymouse, true)) { trace(„Mauskollision!“); } }
Sobald Ihre Maus die Grafik berührt, meldet das Nachrichtenfenster „Mauskollision!“. Wir weisen dem Dreieck ein onMouseMove-Ereignis zu, das bei jeder Mausbewegung die Funktion kollisionMaus() aufruft. In dieser Funktion kontrollieren wir, ob die Form von dreieck unabhängig von dessen Begrenzungsrechteck mit dem Mauszeiger kollidiert. Anders als zuvor übergeben wir als Argument in den Klammern einen Punkt, bestehend aus der x-Position und der y-Position der Maus sowie den Bool’schen Wert true, damit Flash weiß, dass wir einen Punkt und eine Form, aber kein Begrenzungsrechteck testen wollen. Wir haben in Flash also nur die Möglichkeit, zwei Begrenzungsrechtecke oder einen läppischen Punkt und eine Form zu testen – aber gerade die wichtigste Möglichkeit, nämlich zwei Formen oder zumindest
Kapitel 12 MovieClip-Klasse
eine Form und ein Begrenzungsrechteck zu testen, gibt es nicht. Das ist um so erstaunlicher angesichts der Tatsache, dass ein Autorensystem wie beispielsweise Director genau dies schon seit Jahren beherrscht. Die geplagten Flash-Entwickler haben sich daher einige workarounds einfallen lassen, zu denen die Entfernungsberechnung bei Kreisen, das Auslesen der Farbwerte oder die Verwendung zahlreicher Punkte, um eine Form halbwegs abbilden zu können, zählen.
12.3.5 Maskierung Neben den oben erwähnten Eigenschaften bezieht sich auch eine wichtige Methode auf die Sichtbarkeit von MovieClips, nämlich setMask(). Damit lässt sich genauso wie wir es von der Autorenumgebung her gewohnt sind, ein Objekt als Maske definieren, mit dem wir ein darunter befindliches Objekt freilegen können. Diese Technik kommt bei vielen Grafik-Effekten und Spielen zum Einsatz. 1. Erstellen Sie eine Standarddatei. 2. Stellen Sie die Hintergrundfarbe auf schwarz. 3. Importieren Sie eine beliebige Grafik (Größe z. B. 320 × 240, z. B. erdhorn.jpg) in die Bibliothek. 4. Erstellen Sie einen leeren MovieClip (Bibliotheksund Verknüpfungsname mcBild). 5. Fügen Sie die importierte Grafik im Koordinatenursprung von mcBild ein. 6. Erstellen Sie einen weiteren leeren MovieClip (Bibliotheks- und Verknüpfungsname mcMaske). 7. Zeichnen Sie dort, beginnend im Koordinatenursprung, ein Rechteck beliebiger Farbe (100 × 80) ohne Rahmen. 8. Kehren Sie zur Hauptzeitleiste zurück. 9. Fügen Sie in actions folgendes Bildskript ein: this.attachMovie("mcBild","bild",this .getNextHighestDepth()); this.attachMovie("mcMaske","maske", this.getNextHighestDepth()); bild._x = Stage.width/2-bild._ width/2; bild._y = 50;
maske._x = bild._x + 40; maske._y = bild._y + 12; bild.setMask(maske);
12.3 MovieClip-Methoden
Falls Sie die angegebene Grafik verwendet haben, sehen wir im Bildausschnitt den Kopf eines nach rechts schauenden Erdhorns. Wir fügen dynamisch zwei MovieClips ein:
• Die Grafik, die wir maskieren wollen. • Das Objekt, das als Maske dient. Wie bei händisch
erstellten Masken spielt die Farbe der Füllung keine Rolle. Die zugewiesene Tiefe muss logischerweise höher sein als diejenige des zu maskierenden Objekts.
Bei der Positionierung beider MovieClips arbeiten wir hier mit willkürlichen Werten. Das Bild wird auf der Bühne horizontal mittig ausgerichtet. Diese Position errechnet sich aus der halben Bühnenbreite, von der wir aufgrund des links angeordneten Registrierungspunkts noch die halbe Bildbreite subtrahieren müssen. Als y-Wert weisen wir einfach 50 zu. Die Maske muss sich natürlich auf diesem Bild befinden. Ihre Position orientiert sich daher an dessen Koordinaten. Dabei müssen Sie darauf achten, den korrekten Ausschnitt freizulegen, der sich in unserem konkreten Beispiel aufgrund des Bildmotivs – Charakterkopf eines Erdhorns – ergibt. Bei einem anderen Motiv müssen Sie die Werte natürlich anpassen. Eine weiche Maske ist ebenfalls möglich. 10. Weisen Sie mcMaske im Symbolbearbeitungsmodus einen linearen Farbverlauf von schwarz (100 % Deckkraft) zu schwarz (0 % Deckkraft) zu. 11. Drehen Sie den Verlauf mit Hilfe des Füllungstransformationswerkzeugs um 90 Grad, so dass der transparente Bereich am unteren, der nichttransparente am oberen Ende liegt. 12. Ergänzen Sie das Skript unmittelbar vor dem Aufruf von setMask() um folgende Zeile: maske.cacheAsBitmap = bild.cacheAsBitmap = true;
Jetzt blendet der Charakterkopf unseres Tieres von oben nach unten aus. Testen Sie dasselbe einmal mit einer kreisförmigen Maske und einem radialen Verlauf. Der Eigenschaft cacheAsBitmap können die Werte true und false zugewiesen werden. Ist sie true, speichert Flash intern eine Bitmap der Vektorgrafik, was sich in vielen Fällen positiv auf die generelle Performance auswirkt. In unserem Fall ermöglicht sie es, Verläufe mit wechselnder Deckkraft
151
auf eine Maske zu übertragen und damit Bildbereiche nur partiell freizulegen. Behält diese Eigenschaft den vom Programm vorgegebenen Standardwert false, behandelt Flash eine als Maske definierte Grafik so, als sei ihre Deckkraft auf den Wert 100 % gesetzt, wodurch natürlich jegliche Abstufung der Deckkraft verloren geht.
12.3.6 Ausdehnung und Koordinaten Wie wir mehrfach gesehen haben, besitzen Movie Clips (und andere Objekte wie beispielsweise Schaltflächen) eine Ausdehnung, die sich in den Eigenschaften _width, _height, _xscale und _yscale widerspiegelt. Flash gibt uns einen visuellen Hinweis auf diese Ausdehnung, wenn wir einen MovieClip anklicken (kein Doppelklick!), indem das sogenannte Begrenzungsrechteck angezeigt wird, wie bereits im Abschnitt über Kollisionen gesehen. Dabei handelt es sich um ein Rechteck, das vollständig den Inhalt eines Symbols umschließt. Dieses Rechteck kann von uns nicht gesetzt werden, sondern richtet sich ausschließlich nach dem vorliegenden Content. Wir können aber seine Ausdehnung auslesen, was insbesondere dann wichtig sein kann, wenn sich der Registrierungspunkt weder in der linken oberen Ecke noch irgendwo in der Mitte befindet. Die Ausdehnung eines MovieClips lässt sich mit den Methoden getBounds() und getRect() auslesen. 1. Erstellen Sie eine Standarddatei. 2. Erstellen Sie einen leeren MovieClip (Bibliotheksname mcRecht). 3. Zeichnen Sie an beliebiger Position ein blaues Rechteck (140 × 80, Linienstärke 2). 4. Weisen Sie ihm die Position –40,-20 zu. 5. Kehren Sie zur Hauptzeitleiste zurück. 6. Fügen Sie das Rechteck auf der Ebene objects an beliebiger Position ein. 7. Weisen Sie ihm den Instanznamen recht zu. 8. Weisen Sie actions folgendes Bildskript zu: //------------- vars –-------------- var oBounds:Object; var oRect:Object;
//------------ functions –-----------
152
Kapitel 12 MovieClip-Klasse
function init() {
recht._x = 120; recht._y = 80;
oBounds = recht.getBounds(this); oRect = recht.getRect(this); for (e in oBounds) {
trace(„bounds „+e+“: „+oBounds[e]); }
for (e in oRect) {
trace(„oRect „+e+“: „+oRect[e]);
Abbildung 28: Ergebnis von getBounds() bei verschiedenen Koordinatensystemen
}
_width), wird sie von getRect() ignoriert. Daher ist ein von getRect() zurück gegebenes Objekt im-
}
//----------- aufruf –---------------
• xMin: Der kleinste x-Wert (links). • xMax: Der größte x-Wert (rechts). • yMin: Der kleinste y-Wert (oben). • yMax: Der größte y-Wert (unten).
mer kleiner oder maximal gleich demjenigen, das wir bei getBounds() erhalten. Da in Flash verschiedene Koordinatensysteme vorliegen, nämlich dasjenige der Hauptzeitleiste und dasjenige des MovieClips rechteck, müssen wir beim Aufruf der Methoden jeweils mitgeben, auf welches System wir uns beziehen wollen. In unserem Fall verwenden wir _root, übergeben als Argument also this. Ersetzen Sie es testweise durch recht, dann erhalten Sie im Nachrichtenfenster komplett andere Werte. Den gesamten Vorgang für beide Koordinatensysteme visualisiert Abbildung 28 am Beispiel von getBounds(). Wenn wir einen der Eckpunkte in beiden Koordinatensysteme benötigen, können wir einfach getBounds() oder getRect() mit jeweils verschiedenen Koordinatensystemen aufrufen. Falls wir jedoch von einem Punkt, der nicht mit den Eckpunkten zusammen hängt, die Koordinaten benötigen, können wir zwischen Koordinatensystemen hin- und herrechnen, indem wir die Methoden localToGlobal() und globalToLocal() verwenden. Mit local ist das Koordinatensystem des MovieClips gemeint, in dem sich der betreffende Punkt befindet, während sich global auf diejenige Zeitleiste bezieht, in der sich der MovieClip befindet. In unserem Fall entspricht global also der Hauptzeitleiste.
Diese vier Werte beschreiben zwei Punkte: Die linke obere Ecke (xMin, yMin) und die rechte untere Ecke (xMax, yMax) des Begrenzungsrechtecks. Während getBounds() die ein Objekt umgebende Linie mit einbezieht (vgl. o., die Ausführungen zur Eigenschaft
9. Öffnen Sie mcRecht im Symbolbearbeitungs modus. 10. Fügen Sie eine neue Ebene hinzu. 11. Zeichnen Sie dort an beliebiger Stelle einen weißen Kreis (10 × 10).
init();
Ausgabe im Nachrichtenfenster: bounds yMax: 141 bounds yMin: 59 bounds xMax: 221 bounds xMin: 79 oRect yMax: 140 oRect yMin: 60 oRect xMax: 220 oRect xMin: 80 Im Variablen-Block initialisieren wir zwei Variablen, um die entsprechenden Werte der Methoden getBounds() und getRect() aufnehmen zu können. In der init()-Funktion positionieren wir das Rechteck an einer bestimmten Stelle, um später die ausgegebenen Werte besser nachvollziehen zu können. Anschließend weisen wir den Variablen die entsprechenden Methoden zu. Die Methoden geben bei Aufruf ein Objekt zurück, das jeweils vier Werte, bezogen auf das Begrenzungsrechteck des zugeordneten Movie Clips, enthält:
12.3 MovieClip-Methoden
12. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Instanzname kreis, Registrierungspunkt mittig). 13. Fügen Sie im Variablen-Block am Ende ein:
var oPunkt:Object;
14. Erweitern Sie die init()-Funktion unmittelbar nach der Zuweisung der x- und y-Position von recht:
recht.kreis._x = 50; recht.kreis._y = 40;
15. Kommentieren Sie die beiden for in-Schleifen aus, da wir sie für die aktuelle Übung nicht benötigen. 16. Fügen Sie danach, aber noch innerhalb der init()-Funktion, ein: oPunkt = {x:recht.kreis._x, y:recht. kreis._x};
recht.localToGlobal(oPunkt);
trace(„y Global: „+oPunkt.y);
trace(„x Global: „+oPunkt.x);
Ausgabe im Nachrichtenfenster x Global: 170 y Global: 130 Für die Umrechnung der Punkte benötigen wir ebenfalls ein eigenes Object, das wir im Variablen-Block einrichten. Innerhalb der init()-Funktion sorgen wir zunächst für die gewünschte Positionierung. Wichtig ist dabei, das die gewählte Position nicht im Koordinatenursprung liegt – dann wäre eine Umrechnung in das globale Koordinatensystem natürlich witzlos, weil der betreffende Wert identisch mit demjenigen von mcRecht wäre. Statt dessen wählen wir einen willkürlichen Wert, der den Kreis innerhalb von mcRecht nach rechts unten verschiebt. Um eine Umrechnung zu erreichen, müssen wir zunächst die gewünschten Koordinaten in unserem Objekt speichern. Die Eigenschaften, die Sie mit den Werten belegen, dürfen dabei nicht frei gewählt werden. Sie müssen die Bezeichnungen x und y wählen. Andernfalls erhalten Sie nach der Berechnung fehlerhafte Werte (nämlich die Originalwerte). Die Umrechnung erfolgt über localToGlobal(), der wir als Argument das zuvor initialisierte Objekt übergeben. Als Rückgabewert erhalten wir die neuen Koordinaten, die der Position von recht.kreis auf der Hauptzeitleiste entsprechen.
153
Ob der Wert stimmt, lässt sich leicht nachprüfen. 17. Fügen Sie nach den beiden trace()-Befehlen folgende Zeilen ein: trace(„x errechnet: „+(recht._ x+recht.kreis._x)); trace(„y errechnet: „+(recht._ y+recht.kreis._y));
Ausgabe im Nachrichtenfenster: x Global: 170 y Global: 120 x errechnet: 170 y errechnet: 120 Auf derjenigen Zeitleiste, auf der sich recht befindet, entspricht die Position von recht.kreis schlicht der Summe aus der Position von recht und der Position von kreis innerhalb von recht. Genau diese Summe berechnen wir innerhalb der trace()Befehle. Tatsächlich stimmt sie mit den Werten der localToGlobal()-Methode überein. Die umgekehrte Berechnung, nämlich globalToLocal(), macht an dieser Stelle natürlich keinen Sinn, da wir es bei den im Objekt oPunkt vorliegenden Werten ja schon mit lokalen Koordinaten zu tun haben.
12.4
Die Mutter aller MovieClips: _root (_level0)
Im Laufe dieses Kapitels haben Sie gesehen, welche enorme Bedeutung MovieClips zukommt. Aber den wichtigsten MovieClip von allen haben wir bisher eher am Rande erwähnt, nämlich _root selbst, also die Hauptzeitleiste. Sie ist gewissermaßen die Mutter aller MovieClips. Das können wir leicht testen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: trace(typeof(_root));
Ausgabe im Nachrichtenfenster: movieclip Mit der Funktion typeof() können wir uns den Datentyp eines Objekts angeben lassen, der im Fall von _root „movieclip“ lautet, sie stellt also eine Instanz der MovieClip-Klasse dar. Daher muss sie auch
154
über alle Eigenschaften etc. verfügen, die wir auf den vorangegangenen Seiten vorgestellt haben. Anders formuliert, können wir mit _root genau dieselben Dinge anstellen, zu denen wir unsere getreuen roten Kreise und blauen Rechtecke überredet haben. 3. Zeichnen Sie auf objects an beliebiger Stelle einen Kreis beliebiger Farbe (doch, er muss nicht rot sein, gönnen Sie sich diese Freiheit). 4. Zeichnen Sie auf objects an beliebiger Stelle ein Rechteck beliebiger Farbe. 5. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcRecht, Registrierungspunkt links oben). 6. Fügen Sie am Ende des Skripts folgende Zeile ein: _root._visible = false;
Wenn Sie testen, verschwindet plötzlich der komplette Inhalt Ihres Flashfilms. Dabei spielt es übrigens keine Rolle, welcher Art dieser Inhalt ist, es muss sich also nicht unbedingt wie im Fall des Rechtecks um MovieClips handeln. Selbstverständlich stehen auch die anderen Eigenschaften zur Verfügung, etwa die Positionierung. Ersetzen Sie das bisherige Bildskript durch: _root.onMouseDown = function(){ this._x += 25; }
Wenn Sie beim Testen irgendwo auf der Bühne klicken, hüpft alles um 25 Pixel nach rechts. Das ist nun zugegebenermaßen nicht übermäßig sinnvoll, denn wer möchte schon seine komplette Webseite bei Mausklick verschieben oder gar unsichtbar schalten! Aber es zeigt die prinzipiellen Möglichkeiten, die uns das Konstrukt eines übergeordneten MovieClips bietet. Zum Schluss bleibt noch eine Besonderheit zu erwähnen, die wir an der einen oder anderen Stelle bereits kurz kennen gelernt haben, ohne dass sie ausführlicher behandelt worden wäre. Flash speichert nämlich die Inhalte von MovieClips (einschließlich ihrer Eigenschaften etc.) in Hashes. 1. Erstellen Sie eine Standarddatei.
Kapitel 12 MovieClip-Klasse
2. Zeichnen Sie auf objects einen Kreis (60 × 60, Farbe beliebig, Position beliebig). 3. Wandeln Sie ihn in einen MovieClip um (Bibliotheksname mcKreis, Registrierungspunkt beliebig). 4. Erstellen Sie an beliebiger Position zwei Duplikate dieser Instanz. Achten Sie darauf, keiner der Instanzen einen Instanznamen zuzuweisen. 5. Zeichnen Sie an beliebiger Position ein Rechteck (80 × 40, Farbe beliebig). 6. Weisen Sie actions folgendes Bildskript zu: this.onMouseDown = function(){ for(a in this){
trace(„a: „+a); trace(this[a]);
this[a]._x += 10; } }
Klicken Sie beim Testen an beliebiger Stelle auf die Bühne, springen nur die MovieClips um 10 Pixel nach rechts und offenbaren einen automatisch von Flash zugewiesenen, internen Namen. Das Rechteck dagegen verharrt störrisch auf seiner Position, was man ihm nicht nachteilig auslegen sollte, denn es entspricht einfach seiner Natur: Eine Form, die in Flash erstellt wurde, kann nicht per Skripting angesprochen werden. Bei MovieClips klappt das trotz fehlendem Namen, weil Flash eben automatisch eine Bezeichnung vergibt. Wie Sie im Kapitel zur Array-Klasse gesehen haben, greift man auf die Elemente eines Hashes mit Hilfe eines String anstelle eines Index’ zu. Die erste trace()-Anweisung gibt uns den Namen des betreffenden String aus, während der zweite trace()Befehl den konkreten Wert anzeigt. Handelt es sich beispielsweise um MovieClips, erhalten wir so ihre Instanznamen bzw. wie in vorliegendem Fall einen automatisch generierten Namen, so dass wir in jedem Fall auf sie zugreifen können. Zwar liegt auch die von uns vorher gezeichnete Form in dem MovieClip, den wir ansprechen. Aber da Formen keinen Bezeichner besitzen, also anonym in der digitalen Welt unterwegs sind, werden sie von ActionScript ignoriert.
13
Zeichnungsmethoden der MovieClip-Klasse
Flash verfügt mit den Zeichnungsmethoden der MovieClip-Klasse über ein interessantes Werkzeug, um per AS dynamische Grafiken zu erstellen. Leider wird ihr Potential selten wirklich ausgeschöpft, obwohl sie eine Reihe von Vorteilen bieten: Hier zu nennen ist – wie an anderer Stelle – die größere Flexibilität und, noch wichtiger, Interaktionsmöglichkeiten des Anwenders mit unserer Schöpfung. Zudem lassen sich zahlreiche Effekte nur auf diese Weise realisieren. Zugegebenermaßen besitzt ein derartiges Vorgehen seine Grenzen: Eine komplexe Illustration lässt sich in der Regel schneller händisch erstellen als mit den Zeichnungsmethoden. Unsere Tabelle listet die bestehenden Zeichnungsmethoden auf, die wir uns im Folgenden näher anschauen wollen. Gezeichnet wird in einem MovieClip, der als Behälter dient. Befindet sich bereits Content im Behälter, wird dieser durch das Zeichnen nicht gelöscht. Daher lässt die Kombination bestehender Zeichnungen, die eventuell sogar in einem externen Illustrations- oder Grafikprogramm erstellt wurden, mit den Zeichnungsmethoden interessante Effekte zu. Die programmierte Zeichnung lässt sich wie die Instanz eines händisch erstellten Symbols einsetzen, d. h., man kann ihr Ereignisse, Methoden und Variablen zuweisen und mit ihr beliebig interagieren. Während des Zeichenvorgangs kann selbstverständlich mit verschiedenen Linien gearbeitet werden, so dass ein Objekt nicht zwangsläufig aus einheitlichen Elementen bestehen muss. Nach Fertigstellung jedoch sind einzelne Änderungen nicht mehr möglich – auch darin gleicht die programmierte Zeichnung der Instanz eines händisch erstellten MovieClips. Möchten Sie dennoch beispielsweise eine andere Füllung verwenden, bleibt nur das Neuzeichnen. Lediglich der MovieClip-Be-
Methode
Bedeutung
mc.moveTo(x-Position, y-Position)
Setzt den virtuellen Zeichnungsstift auf seine Anfangsposition
mc.lineStyle(Stärke, Farbe, Alpha)
Legt Linieneigenschaften fest
mc.lineGradientStyle (Typ, Farben, Deckkraft, Verhältnisse, Matrix)
Legt eine Verlaufs füllung für die Linie fest
mc.beginBitmapFill( BitmapData)
Definiert eine BitmapGrafik als Füllung
mc.beginFill(Füllfarbe, Alpha)
Definiert eine Füllfarbe
mc.beginGradienFill (Typ, Farben, Deckkraft, Verhältnisse, Matrix)
Definiert eine Verlaufsfüllung
mc.endFill()
Schließt Füllung ab
mc.lineTo(x-Position, y-Position)
Zeichnet eine Linie zur angegebenen Position
mc.curveTo(Steuerpunkt x, Steuerpunkt y, x-Position, y-Position)
Zeichnet eine Kurve zur angegebenen Position
mc.clear()
Löscht die Zeichnung
Zeichnungsmethoden der MovieClip-Klasse
hälter lässt sich nachträglich modifizieren, etwa bezüglich seines Alphawerts. Die verschiedenen Methoden müssen in einer bestimmte Reihenfolge verwendet werden. Obwohl Sie direkt in _root zeichnen können, empfiehlt es sich, einen davon unabhängigen leeren MovieClip als Behälter zu definieren. Damit laufen Sie einerseits nicht Gefahr, dass Ihre Zeichnung irgendwelchen anderen Objekten auf der Bühne in die Quere kommt. Andererseits können Sie die Zeichnung insgesamt durch die Änderung des Behälters ändern (z. B. skalieren, ver-
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
155
156
Kapitel 13 Zeichnungsmethoden der MovieClip-Klasse
schieben etc.). Bezüglich der konkreten Zeichnungsmethoden benötigt Flash an erster Stelle die Definition der Linienart. Das gilt selbst dann, wenn wir ein Objekt ohne Linie, nur bestehend aus einer Füllung, erstellen wollen. Anschließend erfolgt die Angabe einer Füllung, falls gewünscht. Dabei kann man wählen zwischen einer einfarbigen Füllung sowie einem Verlauf, der aufgrund zahlreicher Parameter sehr komplex ausfallen kann. Im nächsten Schritt wird die konkrete Zeichnung als eine Folge von Linien und Kurven definiert. Abschließend teilt man Flash mit, dass die Füllung beendet wird, falls man zuvor eine zugewiesen hat. Verzichtet man darauf, versucht Flash selbständig zu ermitteln, an welcher Stelle die Füllung endet, was mitunter zu ungewollten Ergebnissen führt.
Um etwas sehen zu können, wird die Funktion am Ende des Skripts aufgerufen. Zeichnungen mit lineTo() (und curveTo(), s. u.) beginnen immer im Koordinatenursprung desjenigen Objekts, für das die betreffenden Methoden aufgerufen werden. Da wir mZeich keine Position zugewiesen haben und der Behälter infolgedessen automatisch in der linken oberen Ecke der Hauptzeitleiste eingefügt wird, liegt der Startpunkt unserer Linie an dieser Stelle. Der Endpunkt entspricht dem als Argument bei Aufruf von lineTo() übergebenen Wert, wobei selbstverständlich das Koordinatensystem von mZeich, nicht das von _root maßgeblich ist. Generell liegt der Registrierungspunkt in gezeichneten MovieClips links oben, es sei denn, es würden für lineTo(), curveTo() oder moveTo() negative Werte verwendet. Der Unterschied wird deutlich, wenn wir mZeich verschieben.
13.1 Linien, Kurven und Füllungen
3. Fügen Sie innerhalb der Deklaration von initZeichnung() vor Definition des Linienstils folgende Zeilen ein:
Naheliegenderweise lassen sich Linien am einfachsten zeichnen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: //----------- vars –-----------------
var mZeich:MovieClip = this.createEmp tyMovieClip(„behZeichnung“, 1); //-–------ functions –-------------- function initZeichnung():Void {
mZeich.lineStyle(1, 0xffaa00, 100); mZeich.lineTo(200, 200); }
//----------- aufruf –-------------- initZeichnung();
Flash zeichnet mit hoher Professionalität eine vom Koordinatenursprung bis zur Position 200,200 reichende Gerade. Im Variablen-Block referenzieren wir einen leeren MovieClip als Behälter für alle Zeichenvorgänge. Anschließend deklarieren wir die Funktion initZeichnung(), die für diesen Behälter:
• Einen
Linienstil der Stärke 1 Pixel, der Farbe Orange und der Deckkraft von 100 % definiert. • Eine Linie vom Koordinatenursprung bis zur angegebenen horizontalen und vertikalen Position von jeweils 200 Pixeln zeichnet.
mZeich._x = 200; mZeich._y = 50;
Start- und Endpunkt der Geraden verschieben sich um den Wert, den wir der Position von mZeich zuweisen. In flash-typischer Manier lässt lineStyle() zahlreiche Parameter zu, von denen nur der erste mitgegeben werden muss, die restlichen gelten als optional: Linienstärke: Wenig überraschend handelt es sich um eine Pixelangabe für die Dicke der Linie. Die kleinstmögliche Linie stellt die Haarlinie dar. Sie wird normalerweise mit der Stärke 0.25 eingerichtet. Wer Flash überlisten möchte, indem er 0 oder eine negative Zahl als Stärke angibt, wird ebenfalls mit einer Haarlinie belohnt. Es ist also nicht möglich, komplett auf eine Linie zu verzichten (z. B. weil man nur eine Füllung benötigt), indem man einen kleinen Wert wählt.
• Farbe: Ein beliebiger RGB-Farbwert in Hexcode. Fehlt die Angabe, verwendet Flash 0 × 000000. • Deckkraft. Die Deckkraft, die bei fehlender Angabe automatisch mit 100 angesetzt wird. • Pixel: Boolean, der angibt, ob Striche als ganze Pixel dargestellt werden, was u. a. die Linienstärke beeinflusst. Damit lässt sich die Darstellung von Rundungen optimieren. • Skalierung: String, um mitzuteilen, was bei einer Skalierung der Grafik geschehen soll. Zulässig sind „none“, „normal“, „vertical“, „horizontal“.
13.1 Linien, Kurven und Füllungen
• Abschluss: String, der festlegt, wie der Abschluss
einer Linie darzustellen ist. Zulässig sind „none“, „round“ und „square“. • Verbindung: Ein String zur Kennzeichnung der Verbindung zwischen zwei Winkel, wobei „miter“, „round“ und „bevel“ möglich sind. • Winkel: Pflichtangabe bei der Verwendung von „miter“ in der vorhergehenden Option. Dabei übergeben Sie keine Grade, sondern Werte zwischen 0 und 255. Die erwähnten Parameter stimmen mit den Optionen, die im Eigenschaftsfenster bei einer markierten, händisch erstellten Linie angezeigt werden, überein. In den meisten Fällen reicht es aus, wenn Sie Stärke, Farbe und Alpha angeben. Wir wollen uns die Zeichenmethoden an einem umfangreicheren Beispiel etwas ausführlicher anschauen, bei dem wir ein kleines Haus auf stabilem Untergrund und einen grünen Hügel erstellen – ein genaues Abbild des Anwesens, in dem der Autor dieser Zeilen weilt. 4. Ersetzen Sie den kompletten Inhalt von initZeichnung() durch: var nBodenHoehe:Number = Math. round(Stage.height * 0.7);
var nWandHoehe:Number = nBodenHoehe – 80; var nHausXStart:Number = 100; var nHausXEnde:Number = 220;
mZeich.lineStyle(3, 0xffaa00, 60); mZeich.moveTo(0,nBodenHoehe);
mZeich.lineTo(Stage.width, nBodenHoehe);
Am oberen Rand des unteren Screendrittels zeichnet Flash eine Gerade, die vollständig über die Bildschirmbreite reicht. Wir speichern einige numerische Angaben, die mehrfach verwendet werden müssen, in lokale Variablen. Das könnten wir im Variablen-Block machen, so dass wir einen vollständigen Überblick erhalten, oder innerhalb dieser Funktion, da die Werte nur hier relevant sind. Wir wählen den letztgenannten Weg. In nBodenHoehe legen wir den Baugrund für unser Haus fest. Er befindet sich im unteren Drittel des Screens, aber Sie können natürlich mit beliebigen anderen Werten arbeiten. Ohne Dach erhebt sich unser
157
Haus 80 Pixel über den Boden, so dass nWandHoehe gleich nBodenHoehe – 80 ist. Die Startposition für das Hauszeichnen beträgt 100, die Endeposition 220 Pixel, woraus sich rechnerisch eine Hausbreite von 120 Pixel ergibt, also ausreichend für eine kleine Familie. Der eigentliche Zeichenvorgang beginnt mit der Definition der Linieneigenschaften. Wir verwenden eine drei Pixel dicke, orangene Linie mit einer Deckkraft von 60, so dass die Farbe nicht zu grell wirkt. Anders als im ersten Beispiel möchten wir unsere Zeichnung nicht im Koordinatenursprung beginnen lassen. Denn da er sich in der linken oberen Ecke befindet, würde das bedeuten, dass wir von dort aus vertikal nach unten und dann nach rechts zeichnen müssten, stünde uns nur lineTo() zur Verfügung. Mit Hilfe von moveTo() können wir den virtuellen Zeichenstift an eine beliebige Position verschieben, ohne dabei zu zeichnen. Wir setzen ihn auf die horizontale Position 0, also den linken Rand, und auf die vertikale Position nBodenHoehe. Von dort aus zeichnen wir mit lineTo() eine Gerade, die auf der gleichen Höhe bleibt, aber am rechten Rand endet. Durch die Verwendung von Stage.height und Stage.width passt sich unser Code automatisch an die Größe Ihres Screens an. 5. Erweitern Sie den Code in initZeichnung(): mZeich.lineStyle(1, 0xffaa00, 100);
mZeich.moveTo(nHausXStart,nBodenHoehe); mZeich.beginFill(0xffff99,80); mZeich.lineTo(nHausXStart, nWandHoehe); mZeich.lineTo(nHausXEnde, nWandHoehe); mZeich.lineTo(nHausXEnde, nBodenHoehe); mZeich.endFill();
Ein wunderbares Haus, einer Villa nicht unähnlich, erhebt sich über dem Boden, wie Abbildung 29 zeigt.
Abbildung 29: Beispiel einer geskripteten Zeichnung
158
Innerhalb einer Zeichnung kann man an beliebiger Stelle den Linienstil ändern, was wir hier tun, um Stärke, Farbton und Deckkraft anzupassen. Für das Zeichnen der ersten, linken Wand setzen wir den Stift auf die Anfangsposition, die wir in einer Variablen als horizontalen Start definiert haben. Der vertikale Wert ergibt sich aus der Bodenhöhe – ein solides Bauwerk bedarf nun mal einer gewissen Bodenhaftung. Eine Füllung lässt sich mit der Methode beginFill() zuweisen, der wir als Argumente die gewünschte Farbe und Deckkraft mitgeben. Sie wird demjenigen Bereich zugewiesen, den wir mit lineTo() (und später curveTo() ) umschließen. Um eine korrekte Füllung zu erhalten, muss diese Methode aufgerufen werden, bevor wir die betreffenden Linien zeichnen. Die nächsten drei lineTo() zeichnen die linke Wand von unten nach oben, vom oberen Ende zum oberen Ende der noch nicht gezeichneten rechten Wand, die rechte Wand von oben nach unten. Formal korrekt wäre eine Gerade von diesem Punkt zurück zum Anfang, um die Grafik zu schließen. Wir ersparen uns jedoch diese Zeile und rufen direkt die endFill()-Methode auf, so dass Flash den umrandeten Bereich mit der zuvor definierten Farbe füllt. Da die schließende Linie fehlt, wird sie in demselben Stil wie die übrigen Linien automatisch hinzugefügt. Sie können das kontrollieren, indem Sie in der swf in die Grafik hineinzoomen (<strg><+> und Verschieben per gedrückter Maus). In unserem Fall überschneiden sich Hausgrafik und Boden, was zu unschönen Effekten führen kann. Zur Korrektur müssten Sie in unserem Fall das Haus um zwei oder drei Pixel nach oben versetzt zeichnen, was uns aber nicht weiter kümmern muss, da wir hier die prinzipielle Funktionsweise der betreffenden Methoden betrachten. 6. Erweitern Sie den Code: mZeich.lineStyle(2, 0 × 663300, 100); mZeich.moveTo(nHausXStart, nWandHoehe);
mZeich.beginFill(0 × 663300, 100); mZeich.lineTo(nHausXStart + (nHausXEnde – nHausXStart)/2, nWandHoehe – 60);
Kapitel 13 Zeichnungsmethoden der MovieClip-Klasse
Die Vorgehensweise entspricht der aus dem vorhergehenden Schritt: Linie definieren, Stift setzen, Füllung definieren, Linien ziehen (von der linken oberen Ecke des Hauses zum Scheitelpunkt des Daches, vom Scheitelpunkt zur rechten oberen Ecke), Füllung beenden. Die einzige Berechnung bezieht sich auf den Dachscheitelpunkt. Er liegt horizontal genau in der Mitte zwischen linker Wand (nHausXStart) und rechter Wand (nHausXEnde). Um ihn zu ermitteln, benötigen wir die Differenz zwischen beiden Wänden, dividiert durch 2. Das Ergebnis addieren wir zum Startpunkt der Hauszeichnung bzw. der linken Wand. Als Höhe wählen wir einen willkürlichen Wert von 60 Pixel, den wir von der Wandhöhe subtrahieren. Da sich das Haus in einer landschaftlich reizvollen Umgebung befindet, setzen wir das betreffende Terraforming-Tool von Flash ein. 7. Erweitern Sie den Code: mZeich.lineStyle(2, 0 × 00ff33, 80); mZeich.moveTo(400,nBodenHoehe); mZeich.beginFill(0 × 00cc00,80);
mZeich.curveTo(580,nBodenHoehe100,600,nBodenHoehe); mZeich.endFill();
Rechts neben dem Haus erhebt sich sanft ein grüner Hügel. Abbildung 30 zeigt unser Gesamtkunstwerk. Die einzige neue Methode stellt curveTo() dar, mit der wir Kurven zeichnen können. Es handelt sich dabei um eine quadratische Bezier-Kurve, die mit nur einem Steuerpunkt arbeitet, um eine Kurve zwischen aktueller Position und angegebenem Zielpunkt zu berechnen. Bildlich gesprochen kann man sich den Steuerpunkt als Magneten vorstellen, in dessen Richtung sich die Kurve neigt. Die Methode curveTo() benötigt vier Argumente:
• Horizontale Position des Steuerpunkts. • Vertikale Position des Steuerpunkts. • Horizontale Position des Zielpunkts. • Vertikale Position des Zielpunkts.
mZeich.lineTo(nHausXEnde, nWandHoehe); mZeich.endFill();
Ein mit viel Liebe konstruiertes Dach ziert unser Haus.
Abbildung 30: Haus und Grundstück des Autors
13.1 Linien, Kurven und Füllungen
Wenn wir eine Krümmung nach oben wünschen, müssen wir für den Steuerpunkt eine vertikale Position angeben, die unter derjenigen der Bodenhöhe liegt. Daher subtrahieren wir einen beliebigen Wert von nBodenHoehe. Horizontal muss der Punkt irgendwo zwischen Anfang und Ende der Kurve liegen, sonst wirkt die Krümmung zu unrealistisch. Wollen Sie einen Hügel, der zu beiden Seiten die gleiche Krümmung aufweist, müssten Sie den x-Wert ändern: mZeich.curveTo(500,nBodenHoehe-100, 600,nBodenHoehe);
Der x-Wert muss also exakt auf halber Strecke zwischen Anfangs- und Endpunkt der Kurve liegen. Falls Sie aller Anstrengung zum Trotz mit dem Ergebnis nicht wirklich zufrieden sind, hilft: 8. Fügen Sie innerhalb des Funktions-Blocks nach der schließenden Klammer der init()-Funktion folgende Ereigniszuweisung ein: this.onMouseDown = function() { mZeich.clear(); };
159
Abbildung 31: Zur Funktionsweise von curveTo()
1. Erstellen Sie eine Standarddatei. 2. Erstellen Sie einen leeren MovieClip (Bibliotheksund Verknüpfungsname mcInfo). 3. Erstellen Sie dort im Koordinatenursprung ein dynamisches Textfeld (Instanzname txt, 12 pt, linksbündig, Farbe 0098ff (Blauton), Verdana, nicht auswählbar, einzeilig). 4. Kehren Sie zur Hauptzeitleiste zurück. 5. Weisen Sie actions folgendes Bildskript zu: //------------ vars –--------------- var mZeich:MovieClip = this.create EmptyMovieClip(„behZeichnung“, 1); var mInfo:MovieClip;
Als Kurzform könnte man statt dessen auch verwenden:
var nX1:Number = 200;
this.onMouseDown = mZeich.clear;
var nY:Number = Stage.height/2;
Ein Mausklick an beliebiger Stell löscht die komplette Zeichnung. Wenn wir eine Zeichnung wieder loswerden wollen, verwenden wir die Methode clear(). Der Behälter für die Zeichnung bleibt bestehen, nur sein gesamter, vor Aufruf dieser Methode mit Hilfe der Zeichnungsmethoden erstellter Content wird gelöscht. Falls Sie danach keine weiteren Zeichnungen mehr anfertigen wollen, können Sie statt clear() auch den Behälter selbst löschen, indem Sie mZeich.removeMovie Clip() aufrufen. Nun ja, Programmierer sind eben keine Architekten. Am Anfang erweist es sich vielleicht als gewöhnungsbedürftig, die gewünschte Krümmung bei curveTo() mit den korrekten Werten für den Steuerpunkt zu definieren. Wir wollen uns daher mit Hilfe einer kleinen Applikation, bei der wir interaktiv die Krümmung einstellen und gleichzeitig den zugehörigen Wert des Steuerpunkts ablesen können, die Funktionsweise der Methode anschauen. Abbildung 31 zeigt einen Beispielscreen.
var nX2:Number = Stage.width-nX1; //----------- functions –----------- function init():Void {
mInfo = mZeich. attachMovie(„mcInfo“, „info“, 1); mInfo.txt.autoSize = „left“; kurve();
mZeich.onMouseMove = kurve; }
function kurve():Void {
var nXMouse:Number = _xmouse; var nYMouse:Number = _ymouse; mZeich.clear();
mZeich.lineStyle(2, 0 × 0098ff, 100); mZeich.moveTo(nX1, nY);
mZeich.curveTo(nXMouse, nYMouse, nX2, nY);
mZeich.lineStyle(1, 0 × 666666, 100); mZeich.moveTo(nX1, nY);
mZeich.lineTo(nXMouse, nYMouse);
160
mZeich.lineTo(nX2, nY);
Kapitel 13 Zeichnungsmethoden der MovieClip-Klasse
mInfo.txt.text = nXMouse+”, “+nYMouse;
würde jedoch unter dem entstehenden Liniengewirr untergehen. Daher rufen wir zunächst clear() auf, ehe die Kurve definiert wird, bestehend aus folgenden Einzelschritten:
mInfo._y = nYMouse;
• Definition der Linie. Es handelt sich um den glei-
mInfo._x = nXMouse+12; }
//----------- aufruf –-------------- init();
Flash zeichnet eine Kurve, deren Krümmung von der Mausposition bestimmt wird, indem wir den Steuerpunkt auf dessen Stelle setzen. Um ihn deutlicher zu sehen, zeichnen wir eine hellgraue Linie jeweils vom Anfangs- und vom Endpunkt der Kurve bis zum Steuerpunkt . Gleichzeitig blenden wir ein Textfeld mit den aktuellen Werten der Mausposition ein. Im Variablen-Block legen wir einen Behälter für die Zeichnung an und deklarieren eine Variable, um eine Referenz auf den MovieClip mit dem Textfeld aufzunehmen. Um horizontal einen gleichen Abstand von den Rändern zu erreichen, legen wir einen entsprechenden Wert in der Variablen nX1 für den linken Rand fest. Der rechte Abstand ergibt sich dann aus der Bühnenbreite minus diesem Wert. Das Ergebnis der Berechnung speichern wir in nX2. Da wir vertikal für beide Kurvenpunkte den gleichen Wert benutzen wollen, reicht eine Variable aus, um ihn zu speichern, nämlich nY. Im Funktions-Block definieren wir zunächst die init()-Funktion, die eben dieses Textfeld einblendet und es linksbündig ausrichtet. Alternativ hätte man das auch bereits im Variablen-Block als Initialisierung von mInfo durchführen können. Wir rufen die Funktion kurve() einmal auf, so dass bereits von Anfang an unsere Zeichnung zu sehen ist, und weisen die gleiche Funktion dem onMouseMove-Ereignis des Behälters zu. Infolgedessen wird kurve() jedes Mal neu ausgeführt, sobald wir die Maus bewegen. Für den User entsteht so der Eindruck, als würden wir permanent zeichnen. Die Funktion kurve() definiert den eigentlichen Zeichenvorgang. Da wir die Position der Maus mehrfach benötigen, speichern wir sie in zwei Variablen (je eine für die horizontale und die vertikale Position). Eine bereits bestehende Zeichnung muss zuerst gelöscht werden, bevor wir neu zeichnen können. Andernfalls würde unsere Grafik vielleicht in ästhetischer Hinsicht gewinnen, der Informationsgehalt
chen Vorgang wie bei der Methode lineTo(). • Bewegung zur Startposition, ohne zu zeichnen. • Zeichnen der Kurve, wobei sich der Steuerpunkt aus der Mausposition ergibt. Um die Linien zum Steuerpunkt zeichnen zu können, legen wir einen neuen Stil fest und setzen den virtuellen Zeichenstift auf die Position des Linienanfangs. Von dort aus zeichnen wir eine Linie zur Maus und von dieser zum Endpunkt der Kurve. Anschließend weisen wir dem Textfeld einen Inhalt zu, bestehend aus einer Konkatenation, die x- und y-Position der Maus zusammenzieht. Den MovieClip, der das Textfeld enthält, richten wir an der Mausposition, um 12 Pixel nach rechts versetzt, aus. Zum Schluss rufen wir init() auf, so dass der Zeichenvorgang beginnen kann.
13.2 Verlaufsfüllungen Das Zeichnen der Umrisse und Füllungen gestaltet sich insgesamt problemlos. Etwas komplexer dagegen ist die Verwendung von Farbverläufen, die sich auf drei Arten einrichten lassen. Wir schauen uns zunächst eine einfachere Variante an, die in älteren Versionen funktioniert, und befassen uns anschließend mit dem „moderneren“ Pendant, das Flash ab Version 8 voraussetzt. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: //------------ vars –--------------- var mZeich:MovieClip = this.create EmptyMovieClip(„behZeichnung“, 1);
//---------- functions –------------ function initZeichnung():Void { mZeich._x = mZeich._y=100;
var aColors:Array = [0xff0000, 0xffaa00];
var aAlphas:Array = [100, 100]; var aRatios:Array = [0, 255];
13.2 Verlaufsfüllungen
var oMatrix:Object = {matrixType: „box“, x:0, y:0, w:200, h:200, r:0}; mZeich.beginGradientFill(„linear“, aColors, aAlphas, aRatios, oMatrix); mZeich.lineTo(200, 0);
mZeich.lineTo(200, 200); mZeich.lineTo(0, 200); mZeich.lineTo(0, 0); mZeich.endFill(); }
//––--------- aufruf –-------------- initZeichnung();
Flash stellt ein Rechteck mit einem linearen, horizontal ausgerichteten Farbverlauf von Rot nach Orange dar. Nach dem Einrichten unseres Behälters definieren wir im Funktions-Block wieder unsere initZeichnung(), die den Zeichenvorgang steuert. Zunächst positionieren wir den Behälter auf 100,100. Anschließend legen wir einige Werte fest, die unsere Methode für die Verlaufsfüllung benötigt:
• aColors: Ein Array, dass alle im Verlauf enthal-
tenen Farben aufnimmt. Naturgemäß sollte es sich mindestens um zwei in Hexcode angegebene Farbwerte handeln, da der Verlauf andernfalls einen recht eintönigen Eindruck erwecken würde. Die erste Farbe definiert den Beginn bzw. den Mittelpunkt des Farbverlaufs, je nach Art, die beim Aufruf von beginGradientFill verwendet wird. • aAlphas: Ein Array mit der Deckkraft der jeweiligen Farben. • aRatios: Ein Array mit den Anteilen der Farbwerte am gesamten Farbverlauf. Werte zwischen 0 und 255 sind gültig. Die Anzahl der Elemente muss logischerweise in allen drei Arrays miteinander übereinstimmen. Ist das irgendwo nicht der Fall, schweigt sich Flash vornehm aus anstatt mit einer Fehlermeldung zu protestieren, und es wird auch nichts angezeigt. Gleiches gilt für einen ungültigen Wert in einem der Arrays. Da macht Fehlersuche insbesondere in komplexen Skripten wieder richtig Spaß. • oMatrix: Ein Objekt, das, einfach gesprochen, Position, Größe und Drehung des Verlaufs festlegt. Als matrixType beharrt Flash in jedem Fall auf einer „box“. Die x- und y-Werte beziehen sich auf die linke obere Ecke des Verlaufs. Diese Werte müssen nicht zwangsläufig mit der ersten Zeich-
161
nungsposition übereinstimmen. Die beiden folgenden Werte für w und h legen Breite und Höhe fest, während zum Schluss mit r die Drehung in Grad definiert wird. Natürlich sind die Namen der Arrays und Objekte beliebig, auch wenn es sich eingebürgert hat, Bezeichnungen wie die genannten oder zumindest ähnliche Namen zu verwenden. Die Methode beginGradientFill() übergibt den Füllungstyp, der entweder einem linearen oder einem radialen Farbverlauf entspricht, sowie die zuvor definierten Arrays bzw. Objekte. Erst danach beginnt der eigentliche Zeichenvorgang, indem die Form durch vier lineTo() festgelegt wird. Das entspricht dem Zeichenvorgang, den wir bereits in der vorhergehenden Übung kennen gelernt haben. Abgeschlossen wird mit der endFill()-Methode. Der Aufruf am Ende des Skripts löst den Zeichenvorgang aus. Auf den ersten Blick verwirrend dürfte die Angabe von aRatios sein. Gehen wir der Einfachheit halber von einem radialen Farbverlauf mit einer Drehung von 0 Grad aus, wie er oben gezeichnet wurde. Dann lässt sich der Verlauf horizontal in insgesamt 256 verschiedene Stufen (von 0 bis 255) unterteilen, an denen eine beliebige Farbe ungemischt dargestellt wird. Abbildung 32 zeigt dies am Beispiel unseres Verlaufs von Rot nach Orange. Wir haben also an der frühestmöglichen Position, nämlich direkt am Anfang des Verlaufs, ein ungemischtes Rot, und an der letztmöglichen Position,
Abbildung 32: Zweifarbiger, radialer Farbverlauf Rot – Orange
162
am Ende des Verlaufs, ein reinrassiges Orange. Alle Farbtöne dazwischen stellen eine Mischung zwischen beiden Farben und damit einen Verlauf von Rot zu Orange dar. Die 256 Abstufungen dürfen nicht mit Pixeln verwechselt werden. Es spielt prinzipiell keine Rolle, ob wir unserem Verlauf eine Breite von 100 Pixeln oder gleich bildschirmfüllende 1024 gönnen: In jedem Fall steht die gleiche Anzahl an Stufen zur Verfügung. Ihre Wirkungsweise lässt sich noch besser verstehen, wenn wir in unserem Beispiel einfach mit verschiedenen Werten experimentieren. 3. Ändern Sie die Definition der Farbverteilung wie folgt (Fettdruck): var aRatios:Array = [127, 255];
Nun verschiebt sich der gesamte Farbverlauf nach rechts. Auf den Positionen 0 bis 127 erscheint Rot, und erst ab dieser Stelle beginnt der eigentliche Verlauf, d. h. die Mischung Richtung Orange. Wenn Sie extreme Werte wie beispielsweise zweimal 255 verwenden, erscheint nur noch Rot. Die Reihenfolge und Größe der Zahlen, die in aRatios definiert werden, spielen eine wichtige Rolle. Um einen Verlauf erzeugen zu können, muss jede Zahl größer sein als die vorhergehende. Was passiert, wenn das nicht der Fall ist, können Sie bei entsprechender Codeänderung testen. 4. Ändern Sie die Definition der Farbverteilung wie folgt: var aRatios:Array = [200,100];
Es ergibt sich keine Farbmischung, sondern einfach eine aus zwei Farbblöcken bestehende Füllung. Der radiale Farbverlauf beginnt ja bekanntlich links und erst an der Position 200 ist der letzte Punkt erreicht, an dem wir ein ungemischtes Rot erhalten. Unser reinrassiges Orange dagegen beginnt schon vor dieser Stelle, nämlich an Position 100. Daher kann es keinen Farbverlauf zwischen beiden mehr geben. Anders sieht es aus, wenn wir den zweiten Wert erhöhen, z. B. auf 220. Dann gibt es einen recht kleinen Bereich, in dem ein Verlauf von Rot zu Orange dargestellt wird. Ein Verlauf muss natürlich nicht zwangsläufig aus lediglich zwei Farben bestehen, so schön diese auch sein mögen. Entscheidend ist nur, dass für jede Farbe ein Alphawert, ein Hexwert und ein Anteil (bzw. eine Position) angegeben werden.
Kapitel 13 Zeichnungsmethoden der MovieClip-Klasse
5. Ändern Sie die Definitionen von aColors, aAlphas und aRatios wie folgt (Fettdruck): var aColors:Array = [0xFF0000, 0xffaa00, 0xffffff, 0 × 000099]; var aAlphas:Array = [100, 100, 80,100];
var aRatios:Array = [0,120,190,255];
Sie erhalten einen aus vier Farben bestehenden Verlauf. Vergleichbar dem Transformationswerkzeug, mit dem man einen Farbverlauf drehen kann, lässt sich per ActionScript eine Drehung definieren. Dazu muss lediglich in unserem Matrix-Objekt an letzter Stelle ein entsprechender Wert mit gegeben werden. Da Flash mit Grad arbeitet, beginGradientFill() aber mathematisch korrekt auf Bogenmaß beharrt, wenn wir einen Winkel angeben, muss unser Wert noch dementsprechend umgerechnet werden. Ohne hier auf den mathematischen Hintergrund näher eingehen zu wollen, lautet die Umrechnungsformel: Bogenmaß = Grad/180 * PI. Weitere Informationen zu Bogenmaß und Grad bzw. Radiant entnehmen Sie bitte dem Kapitel Trigonometrie. 6. Um den Farbverlauf zu drehen, ändern Sie den bisherigen Code wie folgt (Fettdruck): var oMatrix:Object = {matrixType: „box“, x:0, y:0, w:200, h:200, r:(45/180)*Math.PI};
Sie erhalten einen Farbverlauf, der um 45 Grad nach rechts gedreht wird und von links oben nach rechts unten verläuft. Ähnlich funktioniert der Verlauf bei einem Kreis. Allerdings werden unsere 256 verschiedenen Stufen vom Verlaufsmittelpunkt aus berechnet. Wenn man der ersten Farbe 0 zuweist, bedeutet dies natürlich, dass sie nur im Kreismittelpunkt zu 100 % dargestellt, an jeder anderen Stelle aber bereits mit der zweiten Farbe gemischt wird. 7. Ändern Sie den Aufruf von beginGradientFill() folgendermaßen (Fettdruck): mZeich.beginGradientFill(„radial“, aColors, aAlphas, aRatios, oMatrix);
Flash zeigt einen kreisförmigen Verlauf mit Rot im Kreismittelpunkt und einem blauen, rechteckigen Rand.
13.2 Verlaufsfüllungen
Wie Sie sehen, kann man leicht zwischen Verlaufs typen umschalten, indem man entweder den String „linear“ für einen linearen oder „radial“ für einen kreisförmigen Verlauf beim Aufruf von beginGradientFill() übergibt. Der Rand ist unvermeidlich, da als matrixType nur der String „box“ zulässig ist. Unter bestimmten Voraussetzungen können wir uns seiner mit einem einfachen Trick entledigen. 8. Ändern Sie die Initialisierung von aColors wie folgt (Fettdruck): var aColors:Array = [0xFF0000, 0xffaa00, 0xffffff, 0 × 000000];
9. Ändern Sie die Hintergrundfarbe in Schwarz. Das Rechteck verschwindet, so dass nur noch ein Kreis zu sehen ist. Der Trick besteht einfach darin, der letzten Farbe des Verlaufs denselben Wert zuzuweisen, den der Hintergrund besitzt. Einen interessanten Effekt erzielen Sie übrigens, wenn Sie den vorher verwendeten BlauTon beibehalten und statt dessen den letzten Wert in aAlphas auf 0 setzen. Dann entsteht der Eindruck einer leicht bläulich schimmernden Korona um einen Stern im Weltall, wie Abbildung 33 zeigt. Bei den bisherigen Übungen haben wir der Einfachheit halber darauf geachtet, dass gezeichnetes Rechteck und Füllung deckungsgleich sind. Das muss jedoch nicht zwangsläufig so sein. Das Rechteck, das mit lineTo() definiert wird, stellt nämlich nichts anderes dar als eine Art Maske, durch die man auf den darunter liegenden Verlauf blickt. Dessen Koordinaten dürfen sich also von denjenigen des Rechtecks unterscheiden.
Abbildung 33: Koronaartiger Grafikeffekt mit Verlaufs füllung
163
10. Ändern Sie die Einrichtung von oMatrix folgendermaßen (Fettdruck): var oMatrix:Object = {matrixType: „box“, x:100, y:0, w:200, h:200, r:0};
Der Kreis verschiebt sich nach rechts und wird in der Mitte abgeschnitten. Relativ zum linken Rand des gezeichneten Rechtecks wird der Kreis nach rechts verschoben. Übrig bleibt ein Halbkreis mit einer an der Schnittseite sehr harten Kante. Wird er dagegen um 200 Pixel verschoben, bleibt nichts mehr vom Kreis übrig, denn das gezeichnete Rechteck ist nur 200 Pixel breit. Der Kreisrand läge exakt rechts außerhalb des Rechtecks. Der Nullpunkt dieses für den Verlauf verwendeten Objektes liegt also im Koordinatenursprung desjenigen MovieClips, in dem gezeichnet wird. Interessanterweise lässt sich auch der Mittelpunkt des Verlaufs verschieben. 11. Fügen Sie unmittelbar nach der Definition des Matrix-Objekts folgende Zeile ein:
var nFokus:Number = 0.9;
12. Ändern Sie den Aufruf von beginGradientFill() wie folgt (Fettdruck): mZeich.beginGradientFill(„radial“, aColors, aAlphas, aRatios, oMatrix, null, null, nFokus);
Das Ergebnis sehen Sie in Abbildung 34. Der Verlauf scheint eine Art Schweif hinter sich herzuziehen und nach rechts zu blicken.
Abbildung 34: Verschiebung des Verlaufsmittelpunkts
164
Kapitel 13 Zeichnungsmethoden der MovieClip-Klasse
Die Methode beginGradientFill() akzeptiert mehrere optionale Argumente, dessen letztes sich, vereinfacht formuliert, auf die horizontale Position des Mittelpunktes eines nicht gedrehten Verlaufs bezieht. Bei einem positiven Wert größer 0 und kleiner/ gleich 1 liegt der Verlaufsmittelpunkt zwischen der Kreismitte und dem rechten Rand, bei einem negativen Wert kleiner/gleich –1 und größer 0 zwischen Mitte und linkem Rand. Andere Werte werden automatisch auf –1 bzw. 1 gerundet. Da wir nur den für den Fokus zuständigen Parameter mit einem Wert belegen wollen, nicht jedoch zwei weitere, müssen wir beim Aufruf zweimal null als Argument übergeben. Andernfalls stimmt die Reihenfolge der Argumente nicht mehr. Mit dem genannten Argument lässt sich der Fokus nur bei einem radialen Verlauf verschieben Bei den beiden von uns so geflissentlich ignorierten optionalen Parametern handelt es sich um:
• spreadMethod: Modus der Verlaufsfüllung, der
über einen String definiert wird. Zulässig sind „pad“, der Standardwert, „reflect“ und „repeat“. Mit „reflect“ kann eine Füllung am Verlaufsende gespiegelt, mit „repeat“ wiederholt werden. Diese Option spielt nur dann eine Rolle, wenn Breite und/oder Höhe des Verlaufs kleiner sind als das zu füllende Zeichnungsobjekt. • interpolationMethod: Art der Berechnung der Farben im Verlauf. Dieser Parameter akzeptiert einen String mit den Werten „linearRGB“ oder dem Standardwert „RGB“.
Um Ihnen einen Vergleich einfacher zu ermöglichen, wollen wir einen neuen MovieClip für die zweite Zeichnung verwenden anstatt den bestehenden zu überschreiben. 15. Fügen Sie im Funktions-Block ein (Änderungen gegenüber initZeichnung() in Fettdruck):
function initZeichnung2():Void {
mZeich2._x = mZeich._x+mZeich._ width+50; mZeich2._y = mZeich._y;
var aColors:Array = [0xFF0000, 0xffaa00, 0xffffff, 0 × 000000];
var aAlphas:Array = [100, 100, 80, 100]; var aRatios:Array = [0, 120, 190, 255]; var nFokus:Number = 0.9;
var mxMatrix:Matrix = new Matrix(); mxMatrix.createGradientBox(200, 200, 0, 0, 0); mZeich2. beginGradientFill(„radial“, aColors, aAlphas, aRatios, mxMatrix, null, null, nFokus); mZeich2.lineTo(200, 0);
mZeich2.lineTo(200, 200); mZeich2.lineTo(0, 200); mZeich2.lineTo(0, 0); mZeich2.endFill();
Oben wurde eine Variante von beginGradientFill() angesprochen, die mit dem geom-Package, das eine Reihe von „geometrischen“ Klassen umfasst, arbeitet.
13. Fügen Sie ganz am Anfang des Skripts ein:
Flash stellt zwei identische Farbverläufe nebeneinander dar. Wir positionieren die neue Zeichnung rechts neben der vorhergehenden. Dazu addieren wir die Position von mZeich, dessen Breite sowie einen willkürlichen Abstand von 50 Pixeln. Die y-Position beider Zeichnungen ist identisch miteinander. Der Zeichenvorgang selbst unterscheidet sich nur in der Art, wie wir unser Matrix-Objekt definieren. Im vorhergehenden Beispiel verwendeten wir ein Object, in diesem Beispiel greifen wir explizit auf die Matrix-Klasse zu, die wir in mxMatrix instanziieren. Anschließend
//------------ import –------------
import flash.geom.*;
Die einzelnen Klassen des Pakets stehen erst zur Verfügung, wenn wir sie in der Gesamtheit – wie hier – oder explizit einzeln importieren. Die benötigte Matrix-Klasse ist Teil dieses Packages. 14. Erweitern Sie den Variablen-Block um: var mZeich2:MovieClip = this.create EmptyMovieClip(„behZeichnung2“, 2);
}
16. Rufen Sie die Funktion am Skriptende auf:
initZeichnung2();
13.4 Geometrische Grundfiguren (Kreis, Oval, Polygon, Stern)
165
rufen wir für mxMatrix die Methode createGradientBox() auf. Sie benötigt prinzipiell dieselben Angaben, die wir vorher oMatrix übergeben haben, nur in veränderter Reihenfolge, nämlich: Breite des Verlaufs, Höhe, Drehung, x-Position, y-Position. Den Typ „box“ müssen wir nicht definieren, da er automatisch von createGradientBox() eingerichtet wird. Anstelle von oMatrix übergeben wir an beginGradientFill() mxMatrix. Wie Sie sehen, ist diese Schreibweise nahezu identisch mit der vorher gezeigten und gilt als die offiziell empfohlene Variante, die jedoch eine neuere Version von Flash voraussetzt. Die Methode lineGradientStyle() funktioniert genau so wie die behandelte Methode beginGradientFill(). Daher wollen wir nicht näher darauf eingehen. Beachten Sie, dass lineGradientStyle() keinen Ersatz für lineStyle() darstellt. Diese Definition ist nach wie vor an erster Stelle notwendig, unabhängig davon, ob Sie den Linien einen Verlauf zuweisen möchten oder nicht.
nach rechts und dreht sich zusätzlich um seinen Registrierungspunkt, was in einer recht eiernden Bewegung resultiert.
13.3 Ereignisse, Methoden, Eigenschaften
1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu:
13.4 Geometrische Grundfiguren (Kreis, Oval, Polygon, Stern) Als Fingerübung wollen wir noch einige Hinweise geben, wie Sie bestimmte geometrische Figuren mit den Zeichnungsmethoden realisieren können. Spontan könnte man meinen, dass sich ein Kreis mit curveTo() ebenso einfach wie effektiv zeichnen lassen würde. Dem ist nicht so. Tatsächlich benötigen Sie solide Kenntnisse der Geometrie, um den Steuerungspunkt korrekt zu setzen. Wir wollen uns damit an dieser Stelle nicht auseinandersetzen (eine Variante finden Sie online unter den livedocs von Adobe zur Zeichen-API von Flash). Nicht ganz ohne Geometrie, aber immerhin etwas einfacher gelangen Sie mit lineTo() zu einem akzeptablen Kreis.
//----------- functions –------------
Da es sich hier nicht um ein eigenständiges Zeichnungsobjekt handelt, besitzen so erstellte Grafiken keine eigenen Eigenschaften oder Ereignisse. Statt dessen stehen alle Eigenschaften, Ereignisse und Methoden der MovieClip-Klasse zur Verfügung. Wollen Sie Ihre Grafiken beispielsweise mit Interaktionen versehen, gehen Sie genau so vor wie bei einem MovieClip. Bezogen auf die Übungen des vorherigen Abschnitts könnten Sie den Behältern für die Zeichnungen zuweisen: mZeich.onPress = function(){ this.removeMovieClip(); }
mZeich2.onPress = function(){ this._x += 20;
this._rotation += 10; }
Bei Klick auf mZeich löschen Sie den MovieClip mitsamt des gezeichneten Inhalts. Wenn Sie dagegen auf mZeich2 klicken, bewegt er sich um 20 Pixel
function kreis(pWer:MovieClip, pRadius:Number, pSchritte:Number, pXPos:Number, pYPos:Number):Void {
var nTiefe:Number = pWer.getNextHighestDepth(); var nWinkel = 0;
var nWinkelDist:Number = 360/ pSchritte; var xPos:Number, yPos:Number;
var mKreis:MovieClip = pWer.create EmptyMovieClip(„behKreis“+nTiefe, nTiefe); mKreis._x = pXPos; mKreis._y = pYPos;
mKreis.lineStyle(1, 0 × 000000, 100);
mKreis.moveTo(Math. cos(nWinkel*(Math.PI/180))*pRadius, Math.sin(nWinkel*(Math. PI/180))*pRadius); for (var i = 0; i
166
xPos = Math.cos(nWinkel*(Math. PI/180))*pRadius; yPos = Math.sin(nWinkel*(Math. PI/180))*pRadius; mKreis.lineTo(xPos, yPos); } }
//---------- aufruf –--------------- kreis(this, 50, 120, Stage.width/2, Stage.height/2);
Ein nicht gefüllter Kreis taucht in der Bühnenmitte auf. Wir verwenden eine parametrisierte Funktion, so dass wir leicht Kreise mit verschiedenen Eigenschaften einrichten können. Zur Vorbereitung richten wir einige Variablen ein:
• nTiefe
speichert die nächst höhere unbelegte Tiefe in demjenigen MovieClip, der als Ziel übergeben wird. • nWinkel setzt den Startwinkel für den Zeichenvorgang auf 0. • nWinkelDist enthält die Winkeldifferenz zwischen den einzelnen Linien. Ein Kreis umfasst 360 Grad, den wir in pSchritte Schritten zeichnen wollen. • xPos und yPos nehmen später die errechnete xund y-Position eines Punktes unserer Linie auf. • mKreis speichert eine Referenz auf einen bei jedem Aufruf von kreis() neu eingerichteten MovieClips. Um eindeutige Instanzen zu erzeugen, binden wir Instanznamen und Tiefe an die Variable nTiefe, in der automatisch jedes Mal ein neuer Wert enthalten ist. Wir setzen mKreis auf die Werte, die wir beim Aufruf der Funktion als Argumente übergeben. Dadurch können wir wechselnde Positionen für unsere Kreise erhalten. Der Zeichenvorgang beginnt mit der Festlegung eines Linienstils. Anschließend setzen wir den virtuellen Stift an die Anfangsposition. Alle Punkte auf einem Kreis lassen sich mit Hilfe trigonometrischer Funktionen berechnen. Dabei verwenden wir Sinus für die y- und Kosinus für die x-Position, jeweils multipliziert mit dem gewünschten Radius. Innerhalb der Schleife wiederholen wir diesen Prozess entsprechend der Anzahl der Schritte, wobei wir jeweils den verwendeten Winkel um die zuvor ermittelte Winkeldifferenz erhöhen und lineTo() anstatt moveTo()
Kapitel 13 Zeichnungsmethoden der MovieClip-Klasse
verwenden – ansonsten würde natürlich keine Zeichnung entstehen. Abschließend rufen wir die Funktion mit diversen Argumenten auf:
• this bezeichnet den MovieClip, in dem der Behäl-
ter für die Zeichnung eingerichtet wird. In unserem Beispiel handelt es sich dabei um _root, also die Hauptzeitleiste, aber Sie können prinzipiell jeden anderen existierenden MovieClip übergeben. • 50 bezieht sich auf den gewünschten Kreisradius in Pixeln. • 120 legt die Anzahl der Schritte fest, d. h., es werden 120 Linien verwendet, um den Kreis zu zeichnen. • Stage.width/2 und Stage.height/2 definieren die x- und y-Position des Zeichenbehälters. Wir verwenden jeweils die halbe Bühnenbreite und halbe Bühnehöhe, also die Bühnenmitte. In diesem Beispiel wird der Kreis vom Mittelpunkt aus gezeichnet, d. h. der Registrierungspunkt entspricht dem Mittelpunkt. Das können Sie leicht überprüfen, indem Sie beim Aufruf der Funktion kreis() jeweils für die x- und die y-Position den Wert 0 übergeben. Sie werden dann nur noch ein Viertel sehen, nämlich das rechte untere Segment, während der Rest aus der Bühne herausragt. Möchten Sie den Registrierungspunkt statt dessen links oben setzen, muss beim Zeichnen der x- und y-Positionen jeweils der Radius hinzu addiert werden. Es ändern sich dann folgende Zeilen (Fettdruck): mKreis.moveTo(Math.cos(nWinkel*(Math. PI/180))*pRadius + pRadius, → Math.sin(nWinkel*(Math. PI/180))*pRadius + pRadius);
xPos = Math.cos(nWinkel*(Math. PI/180))*pRadius + pRadius; yPos = Math.sin(nWinkel*(Math. PI/180))*pRadius + pRadius;
Wählen Sie einfach die Variante, die Ihnen am ehesten zusagt. Die Methode lineTo() erstellt sogar ein kreis ähnliches Gebilde, ohne dass wir auf trigonometrische Funktionen zugreifen. 3. Fügen Sie im Funktions-Block folgende Deklaration ein: function kreis2(pWer:MovieClip, pDurchmesser:Number, pXPos:Number, pYPos:Number):Void {
13.4 Geometrische Grundfiguren (Kreis, Oval, Polygon, Stern)
var nTiefe:Number = pWer.getNextHighestDepth();
var mKreis:MovieClip = pWer.create EmptyMovieClip(„kreisFake“+nTiefe, nTiefe); mKreis.lineStyle(pDurchmesser, 0xffaa00, 100); mKreis.lineTo(0, 0.20); mKreis._x = pXPos;
167
man die letzte Farbe des Verlaufs unseres Kreises mit einer Deckkraft von 0 zeichnen, was gegebenenfalls einen unerwünschten Halo-Effekt hervorruft (s. o., Abschnitt beginGradientFill()). Ein beliebter Zwitter zwischen Kreis und Rechteck stellt das Oval dar, das häufig in Interfaces Verwendung findet. Es lässt sich beispielsweise mit einer Kombination aus lineTo() und curveTo() nachbilden.
mKreis._y = pYPos;
1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu:
this._rotation += 5;
//------------ functions –––---------
mKreis.onEnterFrame = function() { }; }
kreis2(this, 50, 100, 200);
Es taucht ein orangefarbener Kreis in der linken Screenhälfte auf. Wir missbrauchen die Linie, indem wir ihr eine Stärke im gewünschten Durchmesser geben, aber anschließend eine Gerade zwischen zwei Punkten zeichnen, die vertikal 0.2 Pixel voneinander entfernt sind. Dadurch entsteht ein Kreis, der faktisch eine kleine, nicht wahrnehmbare Ausbuchtung nach oben und unten besitzt. Bedenken Sie, dass diese Variante zu unerwünschten Ergebnissen führen kann, da Flash mitunter Probleme hat, den minimalen Versatz von lineTo() korrekt darzustellen. Das ist insbesondere dann der Fall, wenn diese Kreise pixelgenau positioniert oder animiert werden müssen. Zudem ist die Wiedergabe nicht in jeder Version des Flash-Players gleich. In älteren Versionen werden so erstellte Kreise, wenn man sie dreht – nun ja, wer dreht schon Kreise? – die Ausbuchtungen je nach Winkel überdeutlich anzeigen, was aussieht, als wachsen dem Kreis plötzlich Flügel. Hat er etwa an einem bekannten Getränk genippt? Der kleinstmögliche Wert, der über lineTo() zur Ausgangsposition addiert werden kann, beträgt 0.15. Kleinere Werte führen dazu, dass der Kreis nicht dargestellt wird. Wird dieser Kreis gedreht, verschwindet er zeitweise. Eine weitere Variante mit Hilfe von beginGradientFill() haben Sie bereits weiter oben kennen gelernt. Dabei zeichneten wir einen radialen Farbverlauf, dessen äußerste Farbe derjenigen des Hintergrunds entsprach. Ein derartiges Vorgehen findet natürlich schnell da seine Grenze, wo der Hintergrund mehrfarbig ist oder einen Verlauf enthält. Dann müsste
function oval(pWer:MovieClip, pRundung:Number, pBreite:Number, pHoehe:Number, pXPos:Number, pYPos:Number):Void {
var nTiefe:Number = pWer.getNextHighestDepth();
var mRecht:MovieClip = pWer.creat eEmptyMovieClip(„button“+nTiefe, nTiefe); mRecht.lineStyle(2, 0 × 000000, 100); mRecht.moveTo(pRundung, 0);
mRecht.lineTo(pBreite-pRundung, 0); mRecht.curveTo(pBreite, 0, pBreite, pRundung); mRecht.lineTo(pBreite, pHoehe-pRundung); mRecht.curveTo(pBreite, pHoehe, pBreite-pRundung, pHoehe);
mRecht.lineTo(pRundung, pHoehe); mRecht.curveTo(0, pHoehe, 0, pHoehe-pRundung); mRecht.lineTo(0, pRundung);
mRecht.curveTo(0, 0, pRundung, 0); mRecht._x = pXPos; mRecht._y = pYPos;
}
//--------------- aufruf –---------- oval(this, 40, 300, 150, 200, 250);
Auf dem Screen ist ein großes Oval zu sehen. Innerhalb der Funktion erfolgt nach dem Einrichten des Behälters wie gewohnt der Zeichenvorgang.
168
Kapitel 13 Zeichnungsmethoden der MovieClip-Klasse Abbildung 35: Oval
Er besteht schlicht aus einer Folge von lineTo() für die geraden und curveTo() für die gerundeten Ecken, beginnend mit der oberen Geraden. Gezeichnet wird im Uhrzeigersinn. Der Registrierungspunkt liegt links oben. Abbildung 35 verdeutlicht den Aufbau des Ovals, wobei die ersten 5 Schritte des Zeichnens nummeriert dargestellt werden. Anstelle des curveTo() könnte man auch das lineTo() so verwenden, wie wir es oben im Zusammenhang mit dem Kreis getan haben. Vielleicht kommt Ihnen der Gedanke, mit dieser Methode einen Kreis zu zeichnen, wenn Sie auf die lineTo()-Befehle verzichten und nur curveTo() verwenden. Sie werden jedoch kein sauberes Ergebnis erzielen, da die Kurven für unsere abgerundeten Ecken nicht zu einem Kreis mit dem gleichen Mittelpunkt passen. Mit Hilfe der trigonometrischen Funktionen sind wir in der Lage, ein beliebiges gleichseitiges Vieleck (oder professioneller: Polygon) zu zeichnen, beginnend mit einem Dreieck. Die entsprechenden Grundlagen vermittelt das erwähnte Kapitel Trigonometrie, das Sie spätestens an dieser Stelle aufschlagen sollten, falls Ihr Wissen kleinere Lücken aufweist. Wir wollen uns hier lediglich die benötigte Formel anschauen und sie in eine Funktion einbauen.
var mPoly:MovieClip = pWer.create EmptyMovieClip(„behPolygon“ +nTiefe, nTiefe);
1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu:
}
var nDif = 360/pEcken;
var nRadius:Number = (pLaenge/2)/ Math.sin((nDif/2)*(Math.PI/180)); var nWinkel:Number = 0; mPoly._x = pXPos; mPoly._y = pYPos;
mPoly.lineStyle (1, 0 × 000000, 100);
mPoly.moveTo(Math. cos(nWinkel*(Math. PI/180))*nRadius, Math. sin(nWinkel*(Math. PI/180))*nRadius);
for (var i:Number = 0; i
xPos = Math.cos(nWinkel*(Math. PI/180))*nRadius; yPos = Math.sin(nWinkel*(Math. PI/180))*nRadius; mPoly.lineTo(xPos, yPos); }
//–--------- aufruf –----------------
//–––------ functions –--------------
polygon(this, 3, 100, Stage.width/2, Stage.height/2);
var xPos:Number, yPos:Number;
Flash zeigt in Bühnenmitte ein nach rechts zeigendes gleichseitiges Dreieck mit einer Seitenlänge von 100 Pixeln. Wir übergeben als Parameter ähnlich unserer Kreisfunktion die Zeitleiste, in der ein Behälter für
function polygon(pWer:MovieClip, pEcken:Number, pLaenge:Number, pXPos:Number, pYPos:Number):Void {
var nTiefe:Number = pWer.getNextHighestDepth();
13.4 Geometrische Grundfiguren (Kreis, Oval, Polygon, Stern)
169
die Zeichnung eingerichtet wird, die Anzahl der Vertices bzw. Eckpunkte, die Seitenlänge sowie x- und y-Position des Behälters. Am Anfang der Funktion definieren wir einige Variablen, die später für die Positionsberechnung der Vertices benötigt werden. In nTiefe speichern wir den nächsten freien Wert, so dass wir leicht mehrere Kreise in der selben Zeitleiste bzw. in demselben MovieClip zeichnen können. Anschließend referenzieren wir den Zeichnungsbehälter in mPoly. Alle Eckpunkte oder Vertices eines Polygons liegen auf einem gedachten Kreis. Der Trick besteht nun darin, den Radius des Kreises aus der vorgegebenen Seitenlänge zu errechnen. Die trigonometrischen Funktionen ermöglichen es uns, bei bekannter Gegenkathete und Winkel die Hypotenuse eines rechtwinkligen Dreiecks zu berechnen. Ein derartiges Dreieck erhalten wir, wenn wir eine Seitenlänge des gewünschten Dreiecks, das nicht rechtwinklig, sondern nur gleichschenklig ist, halbieren. Dann entspricht die halbe Strecke von pLaenge der Gegenkathete und die Hypotenuse dem gesuchten Radius. Der benötigte Winkel zur Hypotenuse ergibt sich aus einer Drehung von 360 Grad dividiert durch die Anzahl der Seiten des Polygons sowie dividiert durch 2 (s. u.). Wenn wir beispielsweise drei Seiten (für unser zu zeichnendes Dreieck) verwenden, dann ist jedes so gezeichnete Segment um 120 Grad gegenüber dem vorhergehenden versetzt. Diese Berechnung führen wir in obigem Skript für die Variable nDif durch. Die Position der Eckpunkte lässt sich über Sinus und Kosinus bestimmen.
Der eigentliche Zeichenvorgang greift wieder die Kreisfunktion auf, die wir oben bereits verwendet haben: Über Kosinus und Sinus können wir Punkte auf einem Kreis berechnen. In unserem Fall ergibt sich die Anzahl der Punkte einfach aus der Anzahl der im Parameter pEcken übergebenen Vertices. Wenn wir die Position der Punkte errechnet haben, verbinden wir sie mit einer Geraden. Im Prinzip haben wir oben beim Zeichnen des Kreises genau dasselbe getan. Dort war jedoch die Länge der Linien so klein und ihre Anzahl so hoch, dass für uns optisch ein Kreis entstand. Beim in der letzten Zeile erfolgenden Aufruf von polygon() übergeben wir als Argumente den MovieClip, in dem der Zeichenbehälter erstellt werden soll, die Anzahl der Vertices, die Seitenlänge sowie die horizontale und vertikale Position des Behälters. Falls Sie mit der Drehung der Zeichnung nicht zufrieden sind, so können wir einfach festlegen, dass beispielsweise die Oberkante in jedem Fall horizontal gerade gezeichnet werden soll. Ein so gezeichnetes Dreieck weist mit der Spitze nach unten.
Die benötigte Formel lässt sich ableiten aus:
4. Ändern Sie den zweiten Aufruf von polygon() (Fettdruck):
Sinus Winkel = Gegenkathete/Hypotenuse Um die Hypotenuse zu errechnen, ergibt sich daraus: Hypotenuse = Gegenkathete/ Sinus Winkel Dem entspricht in unserem Skript die Berechnung von nRadius. Dabei müssen wir jedoch beachten, dass unser rechtwinkliges Dreieck nur eine Hälfte des zu zeichnenden Dreiecks bildet, denn wir haben ja die Seitenlänge halbiert. Dementsprechend müssen wir auch den Winkel, der in die Berechnung von nRadius einfließt, halbieren. Last not least erfolgt eine Umrechnung von Grad in Bogenmaß, da Flash einen anderen Maßstab verwendet als der sogenannte Einheitskreis. Wir legen willkürlich als Startwinkel 0 fest. Dieser Wert bestimmt die Drehung des Objekts.
3. Ändern Sie die Initialisierung von nWinkel (Fettdruck): var nWinkel:Number = -90 – nDif/2;
Da der Einheitskreis bei einer Drehung von 0 Grad nach rechts weist, drehen wir entgegen dem Uhrzeigersinn um 90 Grad, was einer Subtraktion von 90 entspricht. Zusätzlich müssen wir davon die Hälfte der errechneten Differenz subtrahieren, um den bei der Zeichnung entstehenden Versatz am Anfang auszugleichen.
polygon(this, 11, 120, Stage.width/2, Stage.height/2);
Wie beim Dreieck wird auch beim wie-auch-immerPolygon mit elf Kanten die Oberkante gerade gezeichnet. 5. Ändern Sie die Initialisierung von nWinkel (Fettdruck): var nWinkel:Number = 90 – nDif/2;
Alternativ: var nWinkel:Number = -90;
Jetzt wird bei beiden Objekten die Unterkante horizontal gerade dargestellt.
170
Achten Sie darauf, beim Experimentieren mit verschiedenen Werten die hier als Argument übergebene Seitenlänge nicht mit dem Radius zu verwechseln. Wenn Sie jeweils eine gleichbleibende Seitenlänge von beispielsweise 100 wählen und sukzessive die Anzahl der Ecken erhöhen, muss sich der Radius zwangsläufig ebenfalls vergrößern. Irgendwann ist er so groß, dass Sie das gezeichnete Polygon nicht mehr sehen können, weil seine Seiten außerhalb der Bühne liegen. Diese hier vorgestellte Funktion ist letzten Endes nichts anderes als die oben beim Kreiszeichnen kennen gelernte, nur dass wir dort mit einem vorgegebenen Radius arbeiten konnten, der jetzt erst errechnet werden muss. Mit kleinen Änderungen können wir die Funktion zur Erstellung von Sternen einsetzen. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu (Änderungen gegenüber vorhergehender Übung in Fettdruck): //--------- functions –------------- function stern(pWer:MovieClip, pEcken:Number, pLaenge:Number, pXPos:Number, pYPos:Number, pSpitze:Number):Void { pEcken *= 2;
var xPos:Number, yPos:Number, nKnick:Number;
var nTiefe:Number = pWer.getNextHighestDepth();
var mPoly:MovieClip = pWer.create EmptyMovieClip(„behPolygon“+nTiefe, nTiefe); var nDif = 360/pEcken;
var nRadius:Number = (pLaenge/2)/ Math.sin((nDif/2)*(Math.PI/180)); var nWinkel:Number = 0; mPoly._x = pXPos; mPoly._y = pYPos;
mPoly.lineStyle(1, 0 × 000000, 100);
mPoly.moveTo(Math. cos(nWinkel*(Math.PI/180))*nRadius, Math.sin(nWinkel*(Math. PI/180))*nRadius); for (var i:Number = 1; i<=pEcken; i++) { nWinkel += nDif;
Kapitel 13 Zeichnungsmethoden der MovieClip-Klasse
nKnick = (i%2)*(nRadius* pSpitze);
xPos = Math.cos(nWinkel*(Math. PI/180))*(nRadius-nKnick); yPos = Math.sin(nWinkel*(Math. PI/180))*(nRadius-nKnick); mPoly.lineTo(xPos, yPos); } }
//---------- aufruf –--------------- stern(this, 7, 80, Stage.width/2, Stage.height/2, 0.6);
Flash zeichnet einen Stern mit 7 Zacken. Eigentlich stellt ein Stern nichts anderes dar als ein Polygon mit eingedrückten Seiten. Daher kann man ein Polygon zeichnen, muss aber bei jedem zweiten Schritt dafür sorgen, dass der Radius kleiner als bei einer Spitze ist. Außerdem ist zu beachten, dass die Anzahl der Spitzen und der Dellen miteinander übereinstimmt. Gegenüber der polygon()-Funktion benötigen wir einen zusätzlichen Parameter pSpitze, in dem festgelegt wird, wie tief die Delle sich eindrückt. Um einen dynamischen Zusammenhang zum Radius herzustellen, wird der hier übergebene Wert später mit dem Radius verrechnet. Zwar könnte man beim Aufruf jeweils eine gerade Zahl übergeben, so dass Spitzen und Dellen gleich verteilt werden, was jedoch fehleranfällig wäre. Daher übergeben wir lediglich die Anzahl der Spitzen und duplizieren einfach den Wert. Zusätzlich richten wir die Variable nKnick ein. In der für das Zeichnen verantwortlichen Schleife errechnen wir einen konkreten Wert für nKnick, den wir jedes Mal vom errechneten nRadius subtrahieren. Da jedoch nur bei jedem zweiten Zeichnen der Radius verkleinert werden soll, muss nKnick zwischen dem Wert 0 und einem Wert größer 0 oszillieren. Dies geschieht, indem wir den Restwert der Division von i durch 2 verwenden. Bei jeder ungeraden Zahl beträgt der Rest 1, bei jeder geraden 0. Multiplizieren wir dieses Ergebnis mit einer anderen Zahl, erhalten wir bei jeder zweiten Multiplikation 0, ansonsten eben diese Zahl. Als Faktor verwenden wir eine Multiplikation von nRadius mit einem positiven Wert kleiner 1. Dadurch wird die Zahl kleiner als nRadius, so dass der resultierende Knick nie bis zum Sternmittelpunkt reicht, egal welchen Radius wir errechnen. Andererseits ist die Zahl groß genug,
13.4 Geometrische Grundfiguren (Kreis, Oval, Polygon, Stern)
so dass ein wahrnehmbarer Knick nach innen entstehen kann. Da wir den Aufruf von moveTo() unverändert übernommen haben, also im letzten Schritt vor der Schleife eine Spitze zeichnen, benötigen wir im ersten Schritt innerhalb der Schleife einen Knick. Würden wir wie zuvor die Schleife mit i = 0 beginnen, erhielten wir infolge Modulo ebenfalls 0 und es würde eine Spitze gezeichnet. Daher verschieben wir den Startwert um 1 und müssen gleichzeitig den Endwert um 1 erhöhen, so dass uns kein Zeichenschritt fehlt. Beim Aufruf in der letzten Zeile übergeben wir wie gewohnt die Argumente, wobei an letzter Stelle der Faktor hinzugekommen ist, mit dem wir den Radius multiplizieren, um eine Delle zeichnen zu können. Vernünftige Resultate erhalten Sie, wenn Werte zwischen 0.4 und 0.7 übergeben werden. Zugegebenermaßen werden Sie eher nicht mit den Zeichnungsmethoden ein Haus zeichnen (zumal Sie schneller wären, wenn die Grafik händisch erstellt wird). Aber sie machen Sinn bei aufwändigeren Applikationen. Stellen Sie sich beispielsweise eine Anwendung vor, die externe Daten einliest, um sie in Diagrammen zu visualisieren. Die Diagramme lassen sich dann per Skripting erzeugen. Natürlich könnte man vorher MovieClips erstellen und damit etwa Balkendiagramme aufbauen (attachMovie, _ xscale/_yscale). Aber die Darstellung von Kreisdiagrammen würde zur aufwendigen Fummelarbeit. Ein Kreissegment lässt sich nicht mehr wie ein Balken durch simples Skalieren abbilden, programmiertechnisch kann es aber sehr wohl erstellt werden.
171
Zahlreiche Effekte können nur dynamisch gezeichnet werden, etwa geometrische Figuren, die ihre Form und Position abhängig von User-Aktionen ändern. Stellen Sie sich Linien vor, die über den Screen, ausgehend von der Maus, gezeichnet werden. Sie weisen eine Krümmung abhängig von der Mausposition innerhalb der unteren oder oberen Screenhälfte auf. Schon die Krümmung wäre mit einem vorgefertigten MovieClip nicht mehr realisierbar, wenn sich die Kurve dynamisch anpassen soll. Ein derartiges Beispiel finden Sie im Workshop Effekte. Ein beliebter Effekt ist das Nachzeichnen des Umrisses eines Objekts. Dazu verwendet man üblicherweise eine Maske, die den Umriss freilegt. Dieses Vorgehen ist nicht nur recht unflexibel, da kleine Änderungen schon mal zum vollständigen Neuaufbau des Maskentweens zwingen können, sondern auch noch ziemlich zeitaufwendig. Erheblich einfacher lässt sich derselbe Effekt mit einer geskripteten Zeichnung realisieren. Da sich auch Masken dynamisch zeichnen lassen, ergeben sich weitere Möglichkeiten für Effekte, von denen Sie einige im Workshop Maskeneffekte kennen lernen. Last not least ermöglichen es diese Methoden, zur Laufzeit ein Interface aufzubauen, das, mit Filter- und Ebeneneffekten aufgepeppt, einem händisch erstellten in nichts nachsteht. Es hat jedoch den enormen Vorteil, jederzeit leicht änderbar zu sein – vor allem durch den User, der sich beispielsweise eine eigene Skin aussuchen kann, die erst dann als Grafik erzeugt wird, wenn wir sie benötigen.
14
String, Textfield, Textformat
Seit Jahrtausenden zählt geschriebener Text zu den wichtigsten Informationsträgern menschlicher Kultur. Daran haben weder Fernsehen noch die digitalen Medien etwas geändert. Auch wenn viele Webseiten mit visuellen Mitteln arbeiten, so kommen doch die meisten nicht ohne Text aus. Darüber hinaus übt Text wie das Bild oft eine ästhetische Funktion aus, auch das ein Charakteristikum vieler Schriftkulturen. Man denke nur an die verzierten Buchstaben von mittelalterlichen Inkunabeln und Frühdrucken. Zumindest prinzipiell sind davon viele Webseiten nicht weit entfernt, wenn sie Schrift als „Hingucker“ verwenden. Dessen eingedenk bietet ActionScript mehrere Klassen und zahlreiche Methoden, um mit Texten arbeiten zu können. Der Datentyp String wurde bereits im Kapitel über Datentypen angesprochen. Wir wollen uns nun näher damit befassen, um anschließend die Text- und die Selection-Klasse zu erörtern.
14.1 Strings erzeugen und String-Wert auslesen Text besteht aus einem oder mehreren Strings, worunter Zeichenfolgen zu verstehen sind. Wie wir oben gesehen haben, gehört ein String zu den einfachen bzw. primitiven Datentypen. Die String-Klasse stellt einen Wrapper bzw. eine Wrapper-Klasse für diesen Datentyp dar und bietet mit eigenen Methoden und Eigenschaften spezielle Bearbeitungsmöglichkeiten. Sie verwendet den Unicode-Zeichensatz. Um einen String kenntlich zu machen, muss er, wie Sie im Kapitel über Variablen erfahren haben, von einem einfachen (‚, auf der Tastatur das Zeichen über der Raute #) oder doppelten Anführungszeichen („) eingeschlossen werden.
Wollen wir von der Wrapper-Klasse wieder zum primitiven Datentyp bzw. dem eigentlichen Wert gelangen, gibt es drei Vorgehensweisen, wie das folgende Beispiel zeigt. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var sZitat:String = „Die Giraffe ist ein durch Neugier verlängertes Pferd.“; trace(sZitat.valueOf());
trace(sZitat.toString()); trace(sZitat);
Ausgabe im Nachrichtenfenster: Die Giraffe ist ein durch Neugier verlängertes Pferd. Die Giraffe ist ein durch Neugier verlängertes Pferd. Die Giraffe ist ein durch Neugier verlängertes Pferd. Zunächst richten wir einen String durch simple Zuweisung ein. Alternativ zu dieser Schreibweise kann man einen String mit Hilfe des gleichlautenden Konstruktors einrichten. Dann sähe die erste Zeile folgendermaßen aus: var sZitat:String = new String(„Die Giraffe ist ein durch Neugier verlängertes Pferd.“);
Wir verwenden hier und im nachfolgenden die kürzere, üblichere Schreibweise. Anschließend greifen wir in der ersten Variante auf die Methode valueOf() zurück. Sie ermittelt für jede Klasse den zugehörigen primitiven Datentyp, falls vorhanden. Die toString()-Methode wandelt das zugeordnete Element in einen String zurück, unabhängig davon, worum es sich vorher handelte. Bedenken Sie bei der Verwendung der Methode, dass sie nicht notwendi-
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
173
174
gerweise überall Sinn macht. Last not least bleibt der direkte Zugriff auf den String, der zwangsläufig den Datentyp String besitzt. Der Einfachheit halber verwenden man i. d. R. die letztgenannte Variante.
14.2 Länge Die String-Klasse stellt nur eine einzige Eigenschaft zur Verfügung, nämlich die Länge der betreffenden Zeichenkette. 3. Erweitern Sie obiges Beispiel um folgende Zeile: trace(„Der String sZitat umfasst „ + sZitat.length + „ Zeichen.“);
Ausgabe im Nachrichtenfenster: Der String sZitat umfasst 53 Zeichen. Berücksichtigt werden alle Zeichen einschließlich der Leerzeichen, woraus sich für unseren Beispielstring die angezeigte Gesamtlänge ergibt. Die Eigenschaft length ist schreibgeschützt und kann anders als etwa bei Arrays nicht gesetzt werden, um den String zu verkürzen oder zu verlängern.
14.3 Verkettung Im vorhergehenden Beispiel lag der verwendete Text als ein einheitlicher String vor. Gerade in dynamischen Anwendungen jedoch setzen wir oft aus verschiedenen Informationen einen String zusammen. Dazu bietet Flash einerseits den Konkatenationsoperator + an, den wir bereits im Kapitel über Operatoren kennen gelernt und beispielsweise oben in Schritt 3 verwendet haben. Andererseits existiert eine concat()-Methode, die Ähnliches leistet wie die entsprechende Methode der Array-Klasse. 4. Erweitern Sie obiges Beispiel um folgende Zeile: var sVorname:String = „Ramon Gomez „; var sNachname:String = „de la Serna“; var sAutor:String = „Das Zitat stammt von: „; trace(sAutor.concat(sVorname, sNach name));
Kapitel 14 String, Textfield, Textformat
Ausgabe im Nachrichtenfenster: Das Zitat stammt von: Ramon Gomez de la Serna Wir definieren drei verschiedene Strings. Anschließend rufen wir für den letzten String die concat()Methode auf und übergeben als Argumente die beiden ersten Strings. Dadurch werden sie zu einem einzigen String zusammengesetzt und per trace()Anweisung im Nachrichtenfenster ausgegeben. Die concat()-Methode fügt einfach an denjenigen String, dem sie durch die Punktsyntax zugeordnet wird, all diejenigen Zeichenketten an, die sie als Argumente erhält. Wir können dabei einen String sowohl im Klartext wie auch als Objekt bzw. Variable angeben. Beachten Sie, das der Aufruf von trace() nicht zu einer permanenten Änderung von sAutor führt. Wollten wir dies erreichen, müssten wir zuerst eine entsprechende Zuweisung ausführen. 5. Fügen Sie vor der letzten trace()-Anweisung folgende Zeile ein: sAutor = sAutor. concat(sVorname,sNachname);
6. Ändern Sie den darauf folgenden trace()-Befehl (Fettdruck): trace(sAutor);
Ausgabe im Nachrichtenfenster: Das Zitat stammt von: Ramon Gomez de la Serna Wir ändern sAutor permanent, indem wir für diese Variable die concat()-Methode in derselben Form aufrufen, wie wir es zuvor innerhalb der trace()Anweisung taten. Danach geben wir sAutor aus und erhalten dieselbe Meldung wie vorher.
14.4 Escape-Sequenzen und Sonderzeichen Wir haben oben in Schritt 3 den Inhalt des Zitats ausgeben lassen. In der dort angegebenen Form ist das unproblematisch. Anders sieht es jedoch aus, wenn Sie das Zitat bei der Ausgabe in Anführungszeichen setzen wollen. 7. Erweitern Sie das Skript um folgende Zeilen:
14.4 Escape-Sequenzen und Sonderzeichen
var sZitat2:String = „Zitat: „Mikro phone sind das Einzige, das sich Po litiker gerne vorhalten lassen.““; trace(sZitat2);
Wenn Sie testen, erhalten Sie eine Fehlermeldung. Denn Flash ist nicht in der Lage, die inneren Anführungszeichen, die das konkrete Zitat umschließen, von den äußeren, die den gesamten String kennzeichnen, zu unterscheiden. Formal endet der String nach dem auf den Doppelpunkt folgenden Leerzeichen, während die nachfolgenden Begriffe als falsch verkettete Variablen gesehen werden. Eine Lösung wäre, mit verschiedenen Anführungszeichen zu arbeiten.
175
führungszeichen werden durch einen vorangestellten Schrägstrich gekennzeichnet, so dass sie nicht als Anfang bzw. Ende eines Strings, sondern als genau so darzustellendes Zeichen gelesen werden. Allgemein ermöglicht es das Escape-Zeichen, diejenigen Zeichen, die für ActionScript in irgendeiner Form eine eigenständige Bedeutung haben, als simple Strings darzustellen. Außerdem dienen sie dazu, Sonderzeichen einzufügen, etwa um in einem Text einen Zeilenumbruch oder einen Tabulator zu erzwingen. 10. Ändern Sie die Initialisierung von sZitat2 (Fettdruck):
8. Ersetzen Sie die äußeren, doppelten Anführungszeichen jeweils durch einfache Anführungszeichen (Fettdruck):
var sZitat2:String = „Zitat: \n\t\ „Mikrophone sind das Einzige,\n\tdas sich Politiker gerne vorhalten las sen.\““;
var sZitat2:String = ‚Zitat: „Mikro phone sind das Einzige, das sich Po litiker gerne vorhalten lassen.“‚;
Ausgabe im Nachrichtenfenster: Zitat: „Mikrophone sind das Einzige,
Ausgabe im Nachrichtenfenster: Zitat: „Mikrophone sind das Einzige, das sich Politiker gerne vorhalten lassen.“
das sich Politiker gerne vorhalten lassen.“
Ein String erstreckt sich immer vom ersten bis zum nächsten Auftreten eines Anführungszeichens. Da das einfache Anführungszeichen den vollständigen Text inklusive der doppelten Anführungszeichen umschließt, kann Flash den String korrekt lesen und dementsprechend im Nachrichtenfenster wiedergeben. Dabei spielt es überhaupt keine Rolle, wie viele doppelte Anführungszeichen eingeschlossen werden. Eine wesentlich flexiblere Methode besteht in der Verwendung sogenannter Escape-Sequenzen, die durch einen simplen Backslash gekennzeichnet werden. 9. Ändern Sie die Initialisierung von sZitat2 (Fettdruck): var sZitat2:String = „Zitat: \„Mikro phone sind das Einzige, das sich Po litiker gerne vorhalten lassen.\“„;
Ausgabe im Nachrichtenfenster: Zitat: „Mikrophone sind das Einzige, das sich Politiker gerne vorhalten lassen.“ Aus Gründen der Übersichtlichkeit verwenden wir wie anfangs zur Kennzeichnung von Strings doppelte statt einfacher Anführungszeichen. Die inneren An-
Wir fügen innerhalb des sZitat2 zugewiesenen Strings vor Beginn des Zitats einen Zeilenumbruch (\n) sowie eine Einrückung (\t) ein. Dasselbe machen wir innerhalb des Zitats unmittelbar nach dem Komma. Dadurch erreichen wir eine rudimentäre Formatierung. Wenn Sie den Backslash vor den Zeichen n und t entfernen, werden diese Zeichen als Strings unmittelbar mit ausgegeben, eine Formatierung des Textes entfällt dann. Zusätzlich lassen sich auf diese Weise Sonderzeichen aus dem Unicode-Satz angeben. Sie werden jeweils eingeleitet mit \u, gefolgt von dem entsprechenden Unicode. 11. Fügen Sie am Ende des Skripts folgende Zeile ein: trace(„sZitat2 \u00A9 by Günter Müg genburg“);
Ausgabe im Nachrichtenfenster: sZitat2 © by Günter Müggenburg Mit Hilfe des Escape-Zeichens und dem nachgestellten u als Kürzel für Unicode fügen wir das Copyright-Symbol ein. Dessen Code lautet im UnicodeZeichensatz 00A9. Den betreffenden Zeichensatz und weitergehende Informationen hält die Site www. unicode.org bereit. Zugegebenermaßen kann man
176
Kapitel 14 String, Textfield, Textformat
geistige Bonmonts noch nicht patentieren lassen, aber in einer Welt, in der sogar Patente auf Lebensformen möglich sind, dürfte auch der Copyright-Vermerk bei Zitaten nicht mehr lange auf sich warten lassen. Folgende Tabelle gibt einen Überblick über die Sonderzeichen: Escape- Bedeutung Sequenz
einzelnen Zeichens innerhalb einer Zeichenkette wird genauso wie bei Arrays über einen mit 0 beginnenden Index bestimmt. Die Deklaration von sBuchst muss nicht zwangsläufig erfolgen. Statt dessen wäre der direkte Aufruf der charCodeAt()-Methode möglich: var nASCII:Number = („A“). charCodeAt(0);
\’
Einfaches Anführungszeichen
\“
Doppeltes Anführungszeichen
\\
Backslash
\b
Rückschritt
\f
Seitenvorschub
Die Variable besitzt jedoch den Vorteil der größeren Flexibilität, etwa wenn wir sie zur Laufzeit noch einmal verwenden, aber mit anderem Inhalt füllen. Misstrauische Naturen erwarten an dieser Stelle übrigens die Gegenprobe hinsichtlich der ausgegebenen Zahl.
\n
Zeilenumbruch
3. Erweitern Sie den Code:
\r
Wagenrücklauf
\t
Tabulator
trace(„Zeichen: „+String. fromCharCode(nASCII));
\u
Leitet Sonderzeichen per Unicode-Zeichensatz ein
Beispielhafte Escape-Sequenzen
14.5 ASCII-Zeichensatz Auch unabhängig von den Escape-Sequenzen ist jeder String kodiert, d. h. ihm entsprechen jeweils spezifische Zeichen des ASCII-Codes. Diese Zeichen lassen sich auslesen und dazu verwenden, um Strings zu verändern. 1. Erstellen Sie eine Standarddatei. 2. Fügen Sie auf actions folgendes Bildskript ein: var sBuchst:String = „A“;
var nASCII:Number = sBuchst. charCodeAt(0); trace(„Code: „+nASCII);
Ausgabe im Nachrichtenfenster: Code: 65 Zunächst legen wir in einer Variablen den Buchstaben fest, dessen Code wir ermitteln wollen. Anschließend speichern wir den Code, der einer Zahl entspricht, in einer weiteren Variablen und lassen diese im Nachrichtenfenster ausgeben. Der Zugriff auf den Code erfolgt mit der charCodeAt()-Methode, die als Argument eine Indexzahl erwartet. Die Position eines
Ausgabe im Nachrichtenfenster: Zeichen: A Auch der umgekehrte Weg ist möglich: Wir können eine Zahl angeben und mit Hilfe der fromCharCode()Methode das zugehörige Zeichen ermitteln. In unserem Fall übergeben wir den über charCodeAt() herausgefunden Code und lassen uns den dort definierten String im Nachrichtenfenster ausgeben. Damit lässt sich eines der ältesten Prinzipien von Geheimschriften und Verschlüsselungsverfahren demonstrieren: Die Substitution, also die Ersetzung eines Buchstaben durch einen anderen. Schon Cäsar benutzte diese Form, die – darf man das hier erwähnen? – sogar vom Kamasutra den Damen als eine von 64 notwendigen Künsten anempfohlen wird. Um nicht ungewollt in Konkurrenz zu Herrn Zimmermann zu treten, beschränken wir uns auf die denkbar einfachste Form. 1. Erstellen Sie eine Standarddatei. 2. Fügen Sie auf actions folgendes Bildskript ein: //-------------- vars –------------- var sKlartext:String = „Verschlüsse lung ist eine Kunst!“; var sVerschluesselt:String = „“; var sEntschluesselt:String = „“; var nCode:Number;
var nSubst:Number = 1;
14.5 ASCII-Zeichensatz
//----------- functions ––––-------- function schluessel(psText:String, pnPlusMinus:Number):String { nCode = psText.charCodeAt(0); nCode += nSubst*pnPlusMinus; var sErgebnis = String. fromCharCode(nCode); return sErgebnis; }
this.onMouseDown = function() {
for (var i:Number = 0; i<sKlartext. length; i++) { sVerschluesselt += schluessel (sKlartext.charAt(i), 1); }
trace(„Verschlüsselt: „+sVerschluesselt); };
this.onMouseUp = function() {
for (var i:Number = 0; i<sVerschluesselt.length; i++) { sEntschluesselt += schluessel(sVerschluesselt. charAt(i), -1); }
trace(„Entschlüsselt: „+sEntschluesselt); };
trace(„Unverschlüsselt: „+sKlartext);
Ausgabe im Nachrichtenfenster nach einmaligem Mausklick: Unverschlüsselt: Verschlüsselung ist eine Kunst! Verschlüsselt: Wfstdimýttfmvoh!jtu!fjof!Lvotu“ Entschlüsselt: Verschlüsselung ist eine Kunst! Die zugrundeliegende Idee ist ausgesprochen simpel: Wir schauen nach, welchem ASCII-Code ein String Zeichen für Zeichen entspricht. Dann verschieben wir diesen Code durch Addition einer bestimmten Zahl und ermitteln, welches Zeichen sich daraus ergibt. Dieses Zeichen repräsentiert das verschlüsselte Element. Bei der Entschlüsselung müssen wir das Verschieben des ASCII-Codes lediglich rückgängig machen, also statt einer Addition eine Subtraktion durchführen. Wir richten uns zunächst eine Reihe von Variablen ein:
177
• var
sKlartext: String, der ver- und entschlüs-
• var
sVerschluesselt: String, der die Ver-
• var
sEntschluesselt: String, der die Ent-
selt wird;
schlüsselung enthält; schlüsselung enthält;
• var
nCode:Number: ASCII-Code, um das neue Zeichen zu errechnen; • var nSubst:Number: Zahl, um die der ASCIICode verschoben werden soll.
Anschließend definieren wir eine Funktion, die als Parameter das Zeichen, das zu ver- bzw. entschlüsseln ist, sowie einen Multiplikator erwartet. In ihrer ersten Anweisung ermitteln wir den ASCII-Code des beim Aufruf als Argument übergebenen Strings (der hier immer nur aus einem einzigen Zeichen besteht). Der ASCII-Code entspricht einer Zahl, die wir in der nächsten Zeile entweder um 1 inkrementieren oder dekrementieren. Unser Problem an dieser Stelle besteht darin, eine Anweisung zu schreiben, die einmal addiert (bei der Verschlüsselung) und einmal subtrahiert (bei der Entschlüsselung). Da sich eine Subtraktion auch als Addition einer negativen Zahl beschreiben lässt, müssen wir dafür sorgen, dass nSubst in der betreffenden Zeile negativ werden kann. Das funktioniert, indem wir entweder beim Aufruf die Variable als negative Zahl oder einfach eine –1 übergeben, mit der die Variable hier in der Anweisung multipliziert wird. Eine positive mit einer negativen Zahl multipliziert ergibt eine negative Zahl. Abschließend schauen wir nach, welches Zeichen dem so veränderten ASCII-Code entspricht und geben es mit return an den Aufruf zurück. Der Aufruf erfolgt sowohl im onMouseDown-Ereignis wie auch im onMouseUp-Ereignis, wobei wir lediglich verschiedene Argumente übergeben müssen. Beim Drücken der Maus erhält die schluessel()Methode sukzessive jedes einzelne Zeichen, das wir ganz am Anfang in sKlartext speicherten. Die Methode charAt() erlaubt den Zugriff auf jedes beliebige, durch einen Index festgelegte Zeichen, wobei wir als Wert einfach die Zählvariable i verwenden. Und ebenfalls Stück für Stück erhalten wir das veränderte Zeichen zurück, das jeweils an den String sVerschluesselt angehängt wird. Sind alle Zeichen abgearbeitet, erfolgt die Ausgabe des verschlüsselten Textes.
178
Lassen wir die Maus los, rufen wir erneut die Methode auf, übergeben diesmal aber zeichenweise den bereits verschlüsselten Text. Um die oben erwähnte Subtraktion zu erreichen, erhält die Methode als weiteres Argument eine negative Zahl. Das zurückgegebene Zeichen hängen wir an den String sEnt schluesselt, den wir nach der vollständigen Entschlüsselung im Nachrichtenfenster anzeigen. Zwecks Kontrolle rufen wir, noch bevor der Anwender mit der Maus klicken kann, die trace()-Anweisung auf, um den Originaltext einsehen zu können.
14.6 Teilstrings extrahieren Neben charAt(), mit dem wir auf nur ein einzelnes Zeichen zugreifen, verfügt die String-Klasse über drei weitere Methoden, um ganze Zeichenketten aus einer größeren Zeichenkette zu extrahieren. Es handelt sich dabei um die substr()-, substring()- und slice()-Methoden, die recht ähnlich arbeiten. 1. Erstellen Sie eine Standarddatei. 2. Fügen Sie auf actions folgendes Bildskript ein: var sZitat:String = „Die Demokratie ist jene Staatsform, in der man sagt, was man will, und tut, was einem ge sagt wird.“; var nStart:Number = 4;
var nLaenge:Number = („Demokratie“). length; trace(„substr()-Methode: „ + sZitat. substr(nStart,nLaenge));
trace(„substring()-Methode: „ + sZi tat.substring(nStart,nStart+nLaenge)); trace(„slice()-Methode: „ + sZitat. slice(nStart,nStart+nLaenge));
Ausgabe im Nachrichtenfenster: substr()-Methode: Demokratie substring()-Methode: Demokratie slice()-Methode: Demokratie In der ersten Zeile initialisieren wir eine Variable mit einem String, aus dem wir jeweils die Zeichenkette „Demokratie“ extrahieren wollen. Da der gewünschte Begriff ab Index 4 beginnt, speichern wir diesen Wert in einer Variablen. Ebenso verfahren wir mit der Länge des Begriffs, die wir später auf zwei verschiedene Arten verwenden wollen.
Kapitel 14 String, Textfield, Textformat
Als erstes rufen wir die substr()-Methode auf, die als erstes Argument genau wie die anderen beiden Methoden auch den Index erwartet, an dem die ausgewählte Zeichenkette beginnt. Als zweites Argument übergeben wir die Gesamtlänge und erhalten den gesuchten Begriff im Nachrichtenfenster. Bei der substring()-Methode müssen wir nach dem erwähnten Start-Index denjenigen Index angeben, an dem das erste Zeichen liegt, das nicht mehr zum gesuchten Teilstring gehört. Den benötigten Wert errechnen wir einfach durch Addition des Startindex und der Länge des gewünschten Teilstrings. Die slice()-Methode funktioniert genauso wie die substring()-Methode, kann aber auch mit negativen Indexwerten arbeiten. I. d. R. können Sie getrost darauf verzichten. Bei allen Methoden ist das zweite Argument jeweils optional. Lassen Sie es weg, verwendet Flash immer den gesamten String ab angegebenem Startindex. Eine sinnvolle Namenskonvention vorausgesetzt, stellen diese Methoden eine mächtige Möglichkeit dar, um Objekte abhängig von User-Interaktionen einander zuzuordnen. So können wir beispielsweise über die Instanznamen bei onRollOver anzuzeigenden Infotext ermitteln oder bei Mausklick auf Thumbnails herausfinden, welches größere Bild eingeblendet werden muss. Eine vierte Methode funktioniert etwas anders, indem sie einen String in ein Array umwandelt. 3. Erweitern Sie den Code um folgende Zeilen: var aZitat:Array = [];
aZitat = sZitat.split(„ „); trace(aZitat[1]);
Ausgabe im Nachrichtenfenster: Demokratie Wir richten ein leeres Array ein und weisen ihm in der nächsten Zeile den Inhalt des durch die split()Methode geteilten Strings zu. Dabei wird das Leerzeichen als Trennzeichen definiert, so dass jeder durch Leerzeichen getrennter String als einzelnes Element des Arrays interpretiert wird. Anschließend greifen wir beispielhaft auf das zweite Element innerhalb von aZitat zu und erhalten „Demokratie“. Diese Methode erweist sich beispielsweise dann als wichtig, wenn wir mit der loadVars-Klasse externe Textdateien laden wollen, die wir in Form eines Arrays weiter bearbeiten müssen. Da Textdateien notgedrungen nur einfache Strings enthalten, ermöglicht die genannte Methode die notwendige Umwandlung.
14.8 Groß-/Kleinbuchstaben
Als Trennzeichen sind bei der split()-Methode beliebige Zeichen möglich.
179
if (nStart != -1) {
t race(„Der gesuchte Begriff befindet sich an der Position „+nStart); }
14.7 Teilstrings ermitteln
Ausgabe im Nachrichtenfenster: Der gesuchte Begriff befindet sich an der Position 4
Im vorhergehenden Beispiel sahen wir, wie ein Teilstring extrahiert werden kann, wenn seine Position innerhalb des Gesamtstrings bekannt ist. Gerade in dynamischen Anwendungen fehlt genau dieser Wert, so dass wir eine Möglichkeit benötigen, herauszufinden, ob und gegebenenfalls wo sich der gesuchte String befindet.
Wir speichern in nStart den Startindex, den wir mit der indexOf()-Methode ermitteln. Um sicher zu gehen, dass wir bei der Verwertung von nStart keinen Fehler begehen, fragen wir zunächst ab, ob dessen Wert ungleich –1 ist. Falls ja, lassen wir uns eine entsprechende Meldung ausgeben. Andernfalls wurde er nicht gefunden und wir verzichten auf eine wie auch immer geartete Verwendung von nStart. Praktischerweise können wir an die indexOf()Methode ein optionales zweites Argument mitgeben, das bestimmt, ab welcher Position innerhalb des gesamten Strings wir suchen wollen. Damit besitzen wir den unschätzbaren Vorteil, einen String nicht nur einmal zu finden, sondern so oft, wie er tatsächlich vorhanden ist. Das Pendant zu dieser Methode ist die lastIndexOf()-Methode, die uns angibt, an welchem Index ein bestimmter String zuletzt auftritt. Diese Methoden helfen uns etwa bei der Prüfung einer Texteingabe auf ihre formale Korrektheit.
1. Erstellen Sie eine Standarddatei. 2. Fügen Sie auf actions folgendes Bildskript ein: var sZitat:String = „Die Demokratie ist jene Staatsform, in der man sagt, was man will, und tut, was einem ge sagt wird.“;
var sSuchstring:String = „Demokratie“; trace(sZitat.indexOf(sSuchstring));
Ausgabe im Nachrichtenfenster: 4 Nach der Definition des gesamten Strings legen wir in einer Variablen den Suchstring fest. Mit Hilfe der indexOf()-Methode können wir herausfinden, ob der Suchstring im gesamten String enthalten ist. Falls ja, gibt uns die Methode den Index des ersten Zeichens wieder, mit dem der gesuchte String beginnt. Wird er nicht gefunden, erhalten wir eine –1. 3. Lassen Sie testweise den Suchbegriff mit einem Kleinbuchstaben beginnen (Fettdruck):
14.8 Groß-/Kleinbuchstaben Mit den Methoden toUpperCase() und toLower Case() erlaubt es Flash, einen angegebenen String in Groß- oder Kleinbuchstaben umzuwandeln.
var sSuchstring:String = „demokratie“;
1. Erstellen Sie eine Standarddatei. 2. Fügen Sie auf actions folgendes Bildskript ein:
Ausgabe im Nachrichtenfenster: –1
var sName:String = „ambrose Bierce“; trace(„Initialisierung: „ + sName);
Flash kann die gesuchte Zeichenkette nicht finden und gibt dementsprechend ein negatives Ergebnis zurück.
trace(„Großbuchstaben: „ + (sName. charAt(0)).toUpperCase() + sName.sub str(1));
4. Korrigieren Sie den eben geradezu mutwillig eingefügten Schreibfehler wieder (Fettdruck): var sSuchstring:String = „Demokratie“;
5. Ersetzen Sie den trace()-Befehl durch: var nStart:Number = sZitat. indexOf(sSuchstring);
trace(„Großbuchstaben: „ + sName. toUpperCase());
trace(„Kleinbuchstaben: „ + sName. toLowerCase());
Zunächst definieren wir einen scharfzüngigen String, bestehend aus einem Vor- und Nachnamen. Das erste Zeichen des Vornamens wird absichtlich klein geschrie-
180
ben. In der nächsten Zeile lassen wir uns den String zur Kontrolle ausgeben. Anschließend ändern wir den Klein- in einen Großbuchstaben. Da es sich dabei nur um das erste Zeichen handelt, übergeben wir an die toUpperCase()-Methode den auf Index befindlichen Buchstaben des Strings sName. Um auch den Rest des Namens im Nachrichtenfenster zu erhalten, hängen wir an den ersten geänderten Buchstaben per Konkatenation alle Zeichen ab Index 1 an. Die nächste Anweisung mit toUpperCase() verwandelt alle Buchstaben des Strings unterschiedslos in Majuskeln, während die letzte Anweisung mit toLowerCase() die gesamte Zeichenkette in Minuskeln ausgibt. Abschließend ein kleines Beispiel: 3. Zeichnen Sie auf objects an beliebiger Position einen Kreis. 4. Wandeln Sie den Kreis in einen MovieClip um (Bibliotheksname beliebig, Instanzname kreis, Registrierungspunkt beliebig). 5. Fügen Sie nach dem bisherigen Code folgende Zeilen ein: kreis.onPress = function(){
var sName:String = this._name;
sName = (sName.charAt(0)).toUpper Case() + sName.substr(1); trace(„Ich bin ein „ + sName); }
Bei Mausklick gibt das Objekt seine eigene Identität preis: „Ich bin ein Kreis“. Das könnte man als einen sehr simplen Tooltip verwendet. Da wir mit Camel Case arbeiten und somit alle Instanznamen mit einer Minuskel beginnen, speichern wir bei Mausklick in einer lokalen Variable den Namen des angeklickten Objekts. Anschließend ändern wir das erste Zeichen des Strings in einen Großbuchstaben und hängen daran alle nachfolgenden Zeichen unverändert an. Dieser Schritt entspricht exakt demjenigen, den wir bereits oben im zweiten trace()-Befehl kennen gelernt haben. Schließlich geben wir den so geänderten String aus.
Kapitel 14 String, Textfield, Textformat
von Flash mit Hilfe dreier Arten von Textfeldern erfasst werden: Statische, dynamische und Eingabefelder. Da statische Textfelder nicht veränderbar sind, kann man bis Flash 8 nicht per AS auf sie zugreifen. Das ist jedoch nicht weiter tragisch, denn mit dynamischen Textfeldern lässt sich all das realisieren, was man ohne Scripting mit statischen Feldern tun könnte – und noch einiges darüber hinaus. Wir betrachten zunächst ein in der Autorenumgebung erstelltes dynamisches Textfeld. 1. Erstellen Sie eine Standarddatei. 2. Fügen Sie auf objects links oben ein dynamisches Textfeld mit folgenden Eigenschaften ein: –– Verdana –– 11 Pkt. –– Dunkelgrau –– Bold –– Linksbündig –– Mehrzeilig –– Rahmen um Text –– Nicht auswählbar –– Breite ca. 160 Pixel –– Höhe ca. 33 Pixel –– Instanzname haendTxt. 3. Schreiben Sie in das Feld folgenden aussagekräftigen Satz: „Ich bin ein händisch erstellter Text!“. 4. Weisen Sie actions folgendes Bildskript zu: for(a in haendTxt){
trace(a+“: „+haendTxt[a]); }
Wenn Sie testen, ergibt sich die in Abbildung 36 dargestellte Situation. Im Nachrichtenfenster werden alle Eigenschaften des Textfeldes wieder gegeben, die wir mit Hilfe der for-Schleife auslesen. Flash initialisiert Textfelder automatisch mit einer Reihe von Eigenschaften, die Standardwerte erhalten, falls wir sie nicht explizit setzen. Dazu gehören in unserem Fall u. a. restrict
14.9 Text, Textfelder Wenn Strings erwachsen werden, entwickeln sie sich zu vollwertigen Texten, die in der Autorenumgebung
Abbildung 36: Händisch erstelltes Textfeld
14.9 Text, Textfelder
181
(Festlegung der zulässigen Zeichen), embedFonts (Einbetten eines Schriftfonts) oder variable (Auslesen einer Variablen). Andere Eigenschaften werden mit den von uns verwendeten Werten angezeigt: selectable (auswählbar)
false
multiline (mehrzeilig)
true
border (Rahmen)
true
text (Textinhalt)
Ich bin ein händisch erstellter Text!
type (Art des Textfeldes)
Dynamischer Text
textColor (Farbe des Textes)
0 (schwarz)
Manche Eigenschaften sind aufeinander bezogen. Wenn wir beispielsweise multiline auf true setzen, erhält wordwrap (Wortumbruch) ebenfalls diesen Wert. Wollen wir das nicht, müssen wir explizit die Option „mehrzeilig, ohne Wortumbruch“ auswählen. Legen wir das Anzeigen eines Rahmens fest, wird automatisch die Eigenschaft background auf true gesetzt. Das hat jedoch keinen Einfluss auf die Hintergrundfarbe, die auch bei nicht aktiviertem Rahmen sowie Hintergrund auf den angezeigten Wert fixiert ist. Um die angesprochenen Werte per AS zu ändern, reicht wie gewohnt eine einfache Zuweisung aus. So weist haendTxt.textColor = 0xcc0000 dem Text eine rote Farbe zu. Das funktioniert natürlich genau so bei einem vollständig per Skripting erstellten Textfeld, das wir weiter unten behandeln. Wenn Sie sich die Ausgabe im Nachrichtenfenster noch einmal anschauen, werden Sie feststellen, dass eine Eigenschaft dort nicht aufgeführt wird, obwohl sie von uns explizit festgelegt wurde, nämlich die Schriftauszeichnung fett. Um die Eigenschaften der Schrift verändern zu können, müssen wir die TextFormat-Klasse verwenden, sind also auf zwei Klassen angewiesen, während dies mit Hilfe des Eigenschaftsfensters bei auf der Bühne händisch eingefügten Textfeldern erheblich einfacher gelöst ist. Die TextFormatKlasse wird unten ausführlicher vorgestellt. Wenn wir von dynamischem Text sprechen, bedeutet das natürlich, dass er sich im Laufe der Anwendung ändern kann bzw. anfangs noch nicht bekannt ist. Dazu stehen uns zwei Möglichkeiten zur Verfügung:
• text, die bereits mehrfach angesprochene Eigenschaft eines Textfeldes
• variable (bzw. im Eigenschaftsfenster die Option Var).
5. Löschen Sie den Text im Textfeld. 6. Weisen Sie haendTxt im Eigenschaftsfenster unter der Option Var die noch zu deklarierende Variable sTxt zu. 7. Fügen Sie ganz am Anfang unseres Skriptes folgende Zeile ein: var sTxt:String = „Ich bin ein hän disch erstellter Text!“;
8. Schreiben Sie am Ende des Skriptes in einer neuen Zeile: this.onMouseDown = function(){
sTxt = „Ein Mausklick ändert die Variable sTxt“; }
Wenn Sie testen, zeigt das Textfeld wie bisher den Text an. Bei Mausklick erhält die Variable sTxt einen neuen Inhalt, der zugleich im Textfeld angezeigt wird. Alternativ kann man die Variable direkt per Skripting zuweisen. 9. Löschen Sie im Eigenschaftsfenster den Namen von sTxt, so dass keine Verknüpfung mehr zur Variablen besteht. 10. Fügen Sie im Skript ein:
haendTxt.variable = sTxt;
Gegenüber vorher zeigt sich kein Unterschied, aber wir sind so in der Lage, zur Laufzeit nach Belieben Variablen mit einem Textfeld zu verknüpfen bzw. die Verknüpfung aufzuheben. Mittlerweile üblicher ist allerdings der direkte Zugriff auf die Eigenschaft text anstelle der Verwendung einer Variablen, um den textlichen Inhalt zu definieren. Dann würden wir in gewohnter Weise schreiben: haendTxt.text = „Ich bin ein händisch erstellter Text!„oder haendTxt.text = sTxt. Wollen Sie Text löschen bzw. Ihr Textfeld „leeren“, weisen Sie ihm einfach einen leeren String zu. 11. Schreiben Sie am Ende des bisherigen Codes:
haendTxt.text = „“;
Achten Sie darauf, nicht versehentlich ein Leerzeichen zwischen den Anführungszeichen einzufügen.
182
Ansonsten ist Ihr Textfeld entgegen dem optischen Eindruck nämlich nicht leer, sondern es enthält ein unsichtbares Zeichen, das zu Problemen führen kann, wenn der Textfeldinhalt zu einem späteren Zeitpunkt etwa mit den Methoden des String-Objektes bearbeitet werden soll. Völlig löschen, so wie wir es von der MovieClipKlasse kennen, lässt sich ein derartiges Textfeld nicht. Das funktioniert derzeit nur mit einem per createTextField-Methode (s. u.) erstellten Feld. Der Trick mit einem händisch erstellten MovieClip, dessen Tiefe wir mit swapDepths() auf einen Wert größer oder gleich 0 setzen, um ihn dann mit removeMovieClip() zu löschen, funktioniert hier nicht. Denn die TextFieldKlasse kennt in AS 2.0 nur ein Auslesen der Tiefe über getDepth(), nicht jedoch dessen setzen. Ein in der Autorenumgebung erstelltes Textfeld wird also auf seiner ursprünglich erhaltenen negativen Tiefe kompromisslos verharren. Dieses Problem kann man umgehen, indem das Textfeld in einen MovieClip als Behälter eingefügt wird. Löschen wir den MovieClip, verschwindet automatisch auch das Textfeld. Bei einigen Optionen bietet das Eigenschaftsfenster nur rudimentäre Einstellmöglichkeiten. So kann man z. B. lediglich festlegen, ob ein Rahmen angezeigt werden soll oder nicht. AS dagegen erlaubt es, eine spezifische Farbe für den Rahmen zu definieren. Um etwa einen blauen Rahmen zu erhalten, erweitern Sie das bestehende Skript um folgende Zeile:
Kapitel 14 String, Textfield, Textformat
erzeugen. Selbstverständlich lassen sich diese später modifizieren. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var tText:TextField;
tText = this. createTextField(„txt“,this.getNext HighestDepth(),10,10,200,25); tText.text = „Ich wurde aus dem Nichts erschaffen!“;
Beim Testen erscheint am linken oberen Rand ein Textfeld mit dem von uns definierten Text voll tiefgründiger Philosophie. In der Reihenfolge ihres Auftretens verwendet die createTextField()-Methode folgende Argumente:
• Instanzname • Tiefe • Horizontale Position • Vertikale Position • Breite • Höhe. Das dadurch entstehende Textfeld verfügt über folgende Standardeigenschaften: background
false
border
false
condenseWhite
false
embedFonts
false
Wir setzen die border-Eigenschaft auf true und übergeben die gewünschte Farbe als Hexwert. Ähnlich lässt sich eine Hintergrundfarbe definieren:
html
false
maxChars
null
mouseWheelEnabled
true
haendTxt.background = 0 × 11ccff;
multiline
false
password
false
restrict
null
selectable
true
styleSheet
undefined
tabInded
undefined
type
dynamic
variable
null
wordWrap
false
haendTxt.border = true;
haendTxt.borderColor = 0 × 0000ff;
haendTxt.backgroundColor = 0 × 00aaff;
Zusätzlich bietet AS Zugriff auf Eigenschaften, die ansonsten für MovieClips gelten wie _rotation, _al pha oder _visible. Drehung und Deckkraft setzen jedoch voraus, dass man den verwendeten Schriftfont einbindet. Andernfalls zeigt der Flash-Player schlicht keinen Text mehr an. Wir können sogar noch einen Schritt weiter gehen, indem wir anfangs völlig auf die händische Erstellung eines Textfeldes verzichten. Denn die MovieClipKlasse verfügt über eine entsprechende Methode, um ein Textfeld mit gewissen Standardeigenschaften zu
Soweit das sinnvoll ist, lassen sich diese Eigenschaften, wie oben bereits gesehen, direkt ansprechen und setzen. Um z. B. einen mehrzeiligen Text zu erhal-
14.9 Text, Textfelder
183
ten, sind folgende Änderungen bzw. Ergänzungen im Code nötig (Fettdruck):
3. Schreiben Sie:
tText = this. createTextField(„txt“,this.getNextHigh estDepth(),10,10,200,50);
tText.removeTextField();
tText.text = „Ich wurde aus dem \ nNichts erschaffen!“; tText.multiline = true;
Die Höhe des Feldes muss vergrößert werden, so dass zwei Zeilen darstellbar sind. Die zweite Zeile erhalten wir durch einen per Steuerzeichen (n) erzeugten Zeilenumbruch. Damit dieses Zeichen nicht als Teil des Textes interpretiert wird, markieren wir es durch ein Escape-Zeichen (\). Abschließend weisen wir Flash an, einen mehrzeiligen Text zu akzeptieren. Für die Standardformatierung des im Feld dargestellten Textes sorgt ein TextFormat-Objekt, das über folgende Eigenschaften und Werte verfügt: font
Times New Roman/Times
size
12
color
0 × 000000
bold
false
italic
false
underline
false
url
(leerer String)
target
(leerer String)
align
left
leftMargin
0
rightMargin
0
indent
0
leading
0
blockIndent
0
bullet
false
display
block
tabStops
[]
Darauf können wir leider nicht direkt wie bei den vorhergehenden Eigenschaften zugreifen. Statt dessen benötigen wir eine TextFormat-Instanz, die mit dem Textfeld verknüpft werden muss. Wie damit Formatierungen möglich sind, zeigt der nächste Abschnitt zur TextFormat-Klasse. Um das Textfeld zu löschen, können wir ähnlich wie beim MovieClip auf eine remove-Methode zugreifen.
this.onMouseDown = function(){ }
Ein Mausklick an beliebiger Stelle löscht nun das Textfeld problemlos. Wenn mehrzeiliger Text verwendet werden muss, stellt sich bei einem solchen Textfeld notwendigerweise die Frage nach dessen Größe. Denn bei der Erstellung übergeben wir als Argument u. a. einen Wert für die Höhe des Feldes. Stellen wir einen Rand ein bzw. eine Hintergrundfarbe, dann sollte die Höhe mit der Gesamtlänge des Textes übereinstimmen. Ist das Feld zu groß, entsteht eine unschöne optische Lücke. Glücklicherweise können wir trotz des Höhenparameters die Feldhöhe an den Text über die Eigenschaft autoSize anpassen. 4. Ändern Sie das letzte Argument in der createTextField-Methode() wie folgt (Fettdruck): tText = this.createTextField(„txt“, this.getNextHighestDepth(), 10, 10, 200, 1); tText.border = true;
Wir wählen mit einem Pixel Höhe einen Wert, der zweifelsohne nicht ausreicht, um auch nur eine einzige Textzeile anzeigen zu können. Aus optischen Gründen weisen wir Flash darüber hinaus an, einen Rahmen darzustellen. Falls Sie an dieser Stelle bereits testen, sehen Sie lediglich eine dicke, 202 Pixel breite Linie, bei der es sich um besagten Rahmen handelt. 5. Fügen Sie nach der Initialisierung von multiline ein: tText.autoSize = true;
Trotz der viel zu geringen Höhe des Textfeldes wird alles korrekt angezeigt, da wir Flash durch die Eigenschaft autoSize anweisen, die gesamte Größe des Textes zu berücksichtigen. Diese Eigenschaft kennt folgende Werte:
• true,
was einer linksbündigen Textausrichtung entspricht; • left, linksbündig • center, zentriert • right, rechtsbündig.
184
Kapitel 14 String, Textfield, Textformat
Wird der Text nachträglich geändert, passt sich das Textfeld wie zuvor automatisch an den Inhalt an.
14.10.1 TextFormat
6. Ändern Sie das onMouseDown-Ereignis (Fettdruck):
Betrachten wir zunächst die TextFormat-Klasse, der hier die größte Bedeutung zukommt. Sie verfügt über zahlreiche Optionen, die sehr weitgehende Formatierungen zulassen. Leider ist sie eher kompliziert und umständlich, was sich insbesondere dann als nachteilig erweist, wenn wir innerhalb eines einzigen Textes mit mehreren Schriftauszeichnungen arbeiten wollen. Vor ihrer Verwendung benötigen wir natürlich eine Instanz, die wir wie gewohnt mit einem Konstruktor einrichten.
this.onMouseDown = function(){
tText.text = „Ich wurde aus dem Nichts erschaffen!“; }
Auf Mausklick ersetzt Flash den bisherigen Feldinhalt durch den neuen Text, der mit dem vorhergehenden Inhalt identisch ist, aber über keinen Zeilenumbruch mehr verfügt. Wie am Rahmen zu erkennen, passt sich das Feld an den nunmehr einzeiligen Text mit größerer Zeilenlänge perfekt an.
14.10 Textauszeichnung/ -formatierung Oft ist es notwendig, Text optisch zu gestalten, sei es, um bestimmte Informationen zu betonen, sei es, weil der Text als ästhetisches Element eingesetzt wird. In Flash existieren gleich mehrere Möglichkeiten:
• Eine eigene Textformat-Klasse bietet so ziemlich
alles, wonach das Herz begehrt. • Text kann in html-Form ausgegeben werden, was die Verwendung einiger, aber beileibe nicht aller Tags direkt in Flash erlaubt. • Damit zusammen hängt die Möglichkeit, CSSStile einzusetzen. Auch hier gibt es wie zuvor bei den Tags (noch) ziemliche Einschränkungen, aber in bestimmten Situationen stellt das eine enorme Arbeitserleichterung dar. So können Sie mit dieser Variante eine html- und eine Flashseite erstellen, die auf die gleichen CSS-Formatierungen zugreifen. Damit garantieren Sie eine einheitliche Erscheinungsweise und müssen sich keine doppelte Arbeit hinsichtlich des Designs machen. Die Stylesheet-Klasse ist der Textfield-Klasse zugeordnet. • Last not least erlaubt das TextField-Objekt eine extrem rudimentäre Formatierung, auf die wir aufgrund der beschränkten Möglichkeiten jedoch nicht weiter eingehen wollen.
1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var tComp:TextField = this.createText Field(„computerspiele“,this.getNext HighestDepth(),10,10,200,200); tComp.text = „Computerspiele“;
Es wird ein Textfeld mit Standardeigenschaften erzeugt, positioniert und mit einem Text gefüllt. 3. Erweitern Sie das Bildskript um folgende Zeilen: var tfText:TextFormat = new TextFor mat(); tfText.size = 20;
tfText.color = 0 × 0033ff;
Wir instanziieren die Textformat-Klasse und definieren ihre Eigenschaften Größe mit 20 Pixel und Farbe mit einem Blauton. Wenn Sie testen, bleibt der Text jedoch unverändert schwarz in der voreingestellten Schriftgröße. Das ist durchaus logisch, denn wir haben keinen Bezug zwischen dem neuen Textformat und dem Textfeld hergestellt. Den benötigt Flash, um zu wissen, auf wen die Formatierung anzuwenden ist. Schließlich kann unsere Applikation ja mehr als nur ein einziges einsames Textfeld enthalten. 4. Schreiben Sie weiter: tComp.setTextFormat(tfText);
Ein erneuter Test zeigt einen 20 Pixel großen blauen Text an. Wir können also ähnlich wie bei der TextFieldKlasse unmittelbar auf Eigenschaften zugreifen, um sie zu setzen. Alternativ besteht die Möglichkeit, direkt bei der Erstellung einer Instanz Parameter mitzugeben. Doch seien Sie gewarnt: Wie bei anderen
14.10 Textauszeichnung/-formatierung
185
Klassen, insbesondere der BitmapData- und der FilterKlasse, geht man in der Anzahl der Parameter schnell unter. Im vorliegenden Fall dürfen Sie nämlich bis zu 13 (!) Argumente bei der Instanziierung übergeben. Jeder Entwickler kann dabei seine mnemotechnischen Fähigkeiten unter Beweis stellen.
betreffende Parameter erst an 6. Stelle liegt, müssten Sie beim Instanziieren fünfmal null übergeben und anschließend true. Wer nicht permanent mit der TextFormat-Klasse arbeitet, wird sich ohne Zugriff auf die Hilfe schwerlich vorstellen können, welche Eigenschaften mit diesen Parametern gemeint sind.
5. Löschen Sie die Definitionen von size und color sowie die Initialisierung der Variablen tfText. 6. Fügen Sie unmittelbar vor der Zuweisung des Textformats an das Textfeld folgende Zeile ein:
7. Löschen Sie die neue Zeile wieder. 8. Um dasselbe Ergebnis in einer lesbareren Form zu erhalten, fügen Sie folgende Formatdefintionen ein:
var tfText:TextFormat = new TextFormat(„Georgia“, 20, 0 × 0033ff, true, false, false, null, null, „left“, 20, null, null, null);
var tfText:TextFormat = new TextFor mat();
Als Ergebnis erhalten Sie eine Textdarstellung, die annähernd der vorhergehenden entspricht, allerdings den Text um einige Pixel nach rechts verschiebt und einen anderen Font verwendet. Der ganze Bandwurm stellt eine einzige Befehlszeile dar. Im Einzelnen stehen folgende Argumente zur Verfügung:
• Font (String) • Größe (Pixel) • Farbe (Hexcode) • Fett (Boolean) • Kursiv (Boolean) • Unterstrichen (Boolean) • URL (String, fungiert als Hyperlink) • Target (String) • Ausrichtung (String) • Rand links (Pixel) • Rand rechts (Pixel) • Einrückung (Pixel) • Zeilenabstand (Pixel) Die Reihenfolge ist natürlich wichtig, da AS ansonsten Ihre Werte nicht korrekt zuordnen kann. Wenn Sie wie in unserem Beispiel nicht alle erforderlichen Eigenschaften mit Werten belegen wollen, übergeben Sie einfach null. Offenbar war man sich bei Macromedia bzw. Adobe bewusst, mit so vielen Parameter über das Ziel hinauszuschießen, denn in diesem Fall weigert sich die Flash IDE standhaft, einen Codehinweis auf alle möglichen Argumente einzublenden. Würden Sie tatsächlich auf diese Weise vorgehen, wäre Ihr Code nur schwer lesbar. Stellen Sie sich beispielsweise vor, ein Text sollte unterstrichen dargestellt werden. Da der
tfText.font = „Georgia“; tfText.size = 20;
tfText.color = 0 × 0033ff; tfText.bold = true;
tfText.align = „left“;
tfText.leftMargin = 20;
Wir erhalten nun die gleiche Textdarstellung wie bei unserem flotten Einzeiler, aber hier sind im Gegensatz zu vorher die gesetzten Eigenschaften unmittelbar im Code zu erkennen. Hinweis: Falls sich bei Ihnen plötzlich kein Text mehr zeigen will, kontrollieren Sie, ob nicht aus Versehen die nach wie vor benötigte letzte Zeile mit der Zuweisung des Textformats (s. o. Schritt 4) gelöscht wurde. Ohne auf alle Eigenschaften einzugehen, wollen wir uns ein konkretes Beispiel mit einem längeren Text anschauen, der über das Textformat-Objekt formatiert wird. 1. Erstellen Sie einen neuen Film. 2. Weisen Sie der Ebene actions folgendes Bildskript zu: var tComp:TextField = this.createText Field(„computerspiele“, 1, 100, 50, 400, 200); tComp.text = „Computerspiele\n\nWas heutzutage eine milliardenschwere In dustrie darstellt, die mit der Film branche locker mithalten kann, begann vor Jahrzehnten ganz bescheiden mit einem einsamen Lichtpunkt. Er wan derte über den Bildschirm und konnte mit der Tastatur gesteuert werden.\n\ nHeute, fast 50 Jahre später, glänzen Spiele durch eine schon fast photo
186
realistische Grafik. Und neben die immer noch unvermeidliche Tastatur treten Maus und Joystick.“;
tComp.multiline = true; tComp.wordWrap = true; tComp.border = true;
tComp.background = true;
tComp.backgroundColor = 0xffff99;
var fFliess:TextFormat = new TextFor mat(); fFliess.font = „Georgia“; fFliess.size = 14;
fFliess.color = 0 × 000000; fFliess.align = „left“;
tComp.setTextFormat(fFliess);
Flash zeigt, wie in Abbildung 37 zu sehen, ein hellgelbes Textfeld, bestehend aus drei Absätzen, die per \n definiert werden, also über einen Zeilenumbruch. Fügen wir dieses Zeichen zweimal ein, erhalten wir eine Leerzeile. Die Überschrift besteht aus einem Begriff in der ersten Zeile, der Fließtext steht in den folgenden Absätzen. Da der Text zu sehr am linken und rechten Rand klebt, können wir einen kleinen Abstand einfügen. 3. Erweitern Sie das Skript unmittelbar vor der Zuweisung des Textformats an das Textfeld: fFliess.leftMargin = fFliess.right Margin = 5;
Beim Testen hält der Text nach links und rechts einen kleinen Abstand ein, der die Lesbarkeit erhöht. Sinnvoll wäre es, wenn sich die Überschrift durch irgend eine Auszeichnung vom Fließtext abheben würde. Dazu benötigen wir ein neues Textformat.
Abbildung 37: Textformatierung mit der TextFormat-Klasse
Kapitel 14 String, Textfield, Textformat
4. Ändern und erweitern Sie den Code (Fettdruck): fFliess.leftMargin = fFliess.right Margin=5;
var fUeber:TextFormat = new TextFormat(); fUeber.bold = true; fUeber.size = 16;
tComp.setTextFormat(0,14,fUeber);
tComp.setTextFormat(14, tComp.length, fFliess);
Das neue Format verwendet fett als Auszeichnung und legt eine Schriftgröße von 14 Pixel fest. Da wir nicht den ganzen Text, sondern nur die Überschrift dementsprechend formatieren wollen, übergeben wir der Methode setTextFormat() den Index des ersten Zeichens, ab dem dieses Format gelten soll, und den Index des ersten Zeichens, das nicht mehr formatiert werden soll. Das Überschriften-Format bezieht sich daher nur auf die ersten 13 Buchstaben des Textes. Ab dem 14. Buchstaben kommt das für den Fliesstext bereits vorher definierte Format zur Anwendung. Falls gewünscht, können Sie nun noch die Höhe des Textfeldes an dessen Inhalt anpassen, indem Sie ergänzen: fFliess.align = „left“;. Das Ergebnis zeigt Abbildung 38. Es stehen noch weitere Formatierungsmöglichkeiten zur Verfügung, auf die wir hier aber nicht mehr näher eingehen wollen. Eine Aufzählung einiger Eigenschaften mag zur ersten Orientierung genügen:
• bold, fett (Boolean) • bullett, Aufzählungszeichen (Boolean) • indent, Einzug vom linken Rand (Number) • italic, kursiv (Boolean) • letterSpacing, Abstand zwischen den Zeichen (Number)
Abbildung 38: Erweiterte Textformatierung mit der TextFormat-Klasse
14.10 Textauszeichnung/-formatierung
187
• underline, unterstrichen (Boolean).
tComp.backgroundColor = 0xffff99;
Nicht ganz so glücklich gelöst ist die Darstellung, sobald sich der Textinhalt ändert.
fFliess.font = „Georgia“;
5. Erweitern Sie das Skript um folgendes Ereignis: this.onMouseDown = function(){
tComp.text = „Grob gesehen unter scheidet man zwischen folgenden Spielekategorien: Action, Strate gie, Rollen, Simulation, Glück/Ge schicklichkeit und Logik.“ }
Wenn Sie testen, wird zwar unser neuer Text angezeigt, die Formatierung per Textformat geht aber leider verloren. Das gilt auch für den Fall, dass wir den bestehenden Text nicht ersetzen, sondern um den neuen Text erweitern. 6. Erweitern Sie die Ereignisdefinition mit einer Konkatenation (Fettdruck): this.onMouseDown = function(){
tComp.text += „ \n\nGrob gesehen unterscheidet man zwischen folgen den Spielekategorien: Action, Stra tegie, Rollen, Simulation, Glück/ Geschicklichkeit und Logik.“ }
Das Ergebnis bleibt in Bezug auf die Textformatierung das Gleiche: Sie geht leider verloren. In dem Fall muss die gewünschte Formatierung erneut zugewiesen werden. Einfacher gestaltet sich die Formatierung, wenn wir ein einheitliches Format verwenden, das auf den gesamten Text angewendet werden soll. Dann lässt es sich als Standard definieren, der auch bei Änderungen des Textes erhalten bleibt. 1. Erstellen Sie einen neuen Film. 2. Weisen Sie der Ebene actions folgendes Bildskript zu: var tComp:TextField = this.createText Field(„computerspiele“, 1, 100, 50, 400, 200);
tComp.autoSize = „left“; fFliess.size = 14;
fFliess.color = 0 × 000000; fFliess.align = „left“;
fFliess.leftMargin = fFliess.right Margin=5; tComp.setNewTextFormat(fFliess);
tComp.text = „Computerspiele\n\nWas heutzutage eine milliardenschwere In dustrie darstellt, die mit der Film branche locker mithalten kann, begann vor Jahrzehnten ganz bescheiden mit einem einsamen Lichtpunkt. Er wan derte über den Bildschirm und konnte mit der Tastatur gesteuert werden.\n\ nHeute, fast 50 Jahre später, glänzen Spiele durch eine schon fast photo realistische Grafik. Und neben die immer noch unvermeidliche Tastatur treten Maus und Joystick.“; this.onMouseDown = function() {
tComp.text += „\n\nGrob gesehen un terscheidet man zwischen folgenden Spielekategorien: Action, Strate gie, Rollen, Simulation, Glück/Ge schicklichkeit und Logik.“; };
Der Code entspricht zu wesentlichen Teilen demjenigen des vorhergehenden Beispiels. Allerdings verwenden wir die Methode setNewTextFormat(), die vor der Zuweisung von Text aufgerufen wird. Damit legen wir ein Standardformat für den kompletten Inhalt des Textfeldes fest. Jeder neu hinzukommende Text wird auf genau dieselbe Art formatiert, wie Sie per Mausklick testen können. Benötigen Sie dennoch mehrere Formatierungen in einem Feld mit variablem Inhalt, müssen diejenigen Formate, die nicht der gewünschten Standardformatierung entsprechen, nach jeder Änderung des Inhalts neu zugewiesen werden.
var fFliess:TextFormat = new TextFor mat(); tComp.multiline = true;
3. Erweitern Sie den Code unmittelbar vor der Definition des onMouseDown-Ereignisses um die bereits bekannte Formatierung für eine Überschrift:
tComp.wordWrap = true; tComp.border = true;
var fUeber:TextFormat = new TextFor mat();
tComp.background = true;
fUeber.bold = true;
188
fUeber.size = 16;
tComp.setTextFormat(0, 14, fUeber);
4. Ergänzen Sie das onMouseDown-Ereignis unmittelbar vor dessen schließender Klammer: tComp.setTextFormat(0, 14, fUeber);
Wenn Sie testen, bleiben alle Formatierungen erhalten. Da der Text bei einem Mausklick geändert wird, müssen wir an dieser Stelle dafür sorgen, dass denjenigen Zeichen, die eine besondere Formatierung erhalten sollen, wieder das gewünschte Format zugewiesen wird. Die übrigen Zeichen dagegen behalten automatisch die Standardformatierung bei.
14.10.2 Formatierung mit HTML Die zweite Variante der Textformatierung besteht, wie erwähnt, in der Verwendung von html-Tags. Leider unterstützt Flash nur einen kleinen Teil von HTML, nämlich insbesondere:
• , die verwendete Schriftart • stellt Text fett dar • stellt Text unterstrichen dar • stellt Text kursiv dar. • legt einen Absatz fest und verwendet
das align-Attribut. •
ermöglicht eine Liste mit Aufzählungspunkten. Es wird nicht zwischen den Tags und unterschieden. Listen sind immer unsortiert. • definiert einen Hyperlink. Zulässige Attribute sind href und target • <span>, benötigt, um CSS-Stile zuweisen zu können. Damit Flash unterscheiden kann, ob wir html-formatierten oder „normalen“ Text verwenden wollen, muss explizit auf die Eigenschaften html und htmlText zugegriffen werden. 1. Erstellen Sie einen neuen Film. 2. Weisen Sie der Ebene actions folgendes Bildskript zu: var tComp:TextField = this. createText Field(„computerspiele“, 1, 100, 50, 400, 200);
Kapitel 14 String, Textfield, Textformat
tComp.multiline = true; tComp.wordWrap = true; tComp.border = true;
tComp.background = true;
tComp.backgroundColor = 0xffff99; tComp.autoSize = „left“; tComp.html = true;
tComp.htmlText = „Computerspiele
Was heutzutage eine milliardenschwere Industrie darstellt, die mit der Filmbranche locker mithalten kann, begann vor Jahrzehnten ganz beschei den mit einem einsamen Lichtpunkt. Er wanderte über den Bildschirm und konnte mit der Tastatur gesteuert werden.
Heute, fast 50 Jahre später, glänzen Spiele durch eine schon fast photorealistische Grafik. Und neben die immer noch unvermeid liche Tastatur treten Maus und Joy stick.“;
Wir übernehmen das Skript aus dem vorhergehenden Beispiel, löschen aber jeden Bezug auf die TextFormat-Klasse. An dessen Stelle verwenden wir HTML-Tags genau so, wie man es zu früheren Zeiten in einem klassischen HTML-Dokument machte. So legen wir die Schriftart über das -Tag fest und erreichen eine rudimentäre Formatierung der Absätze mit Hilfe des
-Tags (alternativ kann man das p-Tag nehmen). Bei der Verwendung von Attributen entsteht das Problem, dass einerseits der zugewiesene htmlText einen String darstellt, also in Anführungszeichen stehen muss, andererseits gilt das jedoch für die Attribute ebenfalls. Würden wir jeweils die gleichen Anführungszeichen ohne weitere Markierung einsetzen, wäre Flash nicht mehr in der Lage, zu erkennen, wo der String beginnt und endet. Die beiden das Attribut umschließenden Anführungszeichen müssen also gesondert gekennzeichnet werden. Das funktioniert mit Hilfe des bereits erwähnten Escape-Zeichens. Alternativ kann anstelle des Escape-Zeichens das Hochkomma (auf der Tastatur das Zeichen über der Raute #) genommen werden. Beachten Sie, dass Farbwerte zwar wie in Flash hexadezimal übergeben werden, jedoch anstelle der vorangestellten Zeichen 0x die Raute # verwendet wird.
14.10 Textauszeichnung/-formatierung
3. Erweitern Sie das Skript am Ende um: this.onMouseDown = function() {
tComp.htmlText += „
Grob gesehen unterscheidet man zwischen folgen den Spielekategorien: Action, Stra tegie, Rollen, Simulation, Glück/ Geschicklichkeit und Logik.“; };
Bei einem Mausklick taucht weiterer Text auf, der aber wieder in einer Standardformatierung angezeigt wird. Da wir das geöffnete -Tag bei der erstmaligen Zuweisung des Textes notwendigerweise wieder schließen müssen, verfügt der neu hinzu gefügte Text über keine spezifische Formatierung mehr und wird mit der standardmäßig von Flash vergebenen Formatierung angezeigt. 4. Ergänzen Sie im onMouseDown-Ereignis die Textzuweisung (Fettdruck): tComp.htmlText += „
Grob gesehen un terscheidet man zwischen folgenden Spielekategorien: Action, Strategie, Rollen, Simulation, Glück/Geschick lichkeit und Logik.“;
Nun bleibt die Formatierung auch für den neuen Text erhalten. Zwar ist es möglich, bei der erstmaligen Textzuweisung auf das schließende -Tag zu verzichten, was Flash großzügigerweise so interpretiert, als sollte auch jeder neue Text in der gleichen Weise formatiert werden, aber dieses Verhalten ist formal falsch und wird gegebenenfalls in neueren Versionen des Flash-Players nicht mehr unterstützt. Daher sollte man nicht damit arbeiten. Übrigens wird auch im htmlText der Zeilenumbruch per \n korrekt interpretiert. Dennoch sollten Sie an dieser Stelle auf die in HTML gültigen Tags zugreifen, um einen unübersichtlichen Mischmasch aus verschiedenen Formatierungsanweisungen zu vermeiden. Neben der Textformatierung erlaubt HTML die Definition rudimentärer Interaktionen, zu denen natürlich primär die Verlinkung zählt. Sie folgt prinzipiell denselben Regeln wie ein reguläres HTML-Dokument. 5. Ändern Sie die erstmalige Textzuweisung (Fettdruck): Lichtpunkt
189
6. Löschen Sie das nun nicht mehr benötigte on MouseDown-Ereignis vollständig. Wenn Sie testen, öffnet sich entweder ein Fenster mit einem Sicherheitshinweis, falls Sie lokal testen, oder die gewünschte URL wird aufgerufen. Der Link wird jedoch nicht in der von uns gewohnten Formatierung dargestellt, was sich leicht beheben lässt. 7. Ändern Sie die Textzuweisung (Fettdruck): Lichtpunkt u>
Flash stellt den Link blau und unterstrichen dar. Ähnlich erfolgt der Aufruf des mail-Clients und von Javascript-Funktionen. Interessanterweise wird auch AS per Hyperlink ansprechbar, denn das hrefAttribut akzeptiert als Wert asfunction mit dem Funktionsnamen und gegebenenfalls einem weiteren Wert als Argument. 8. Fügen Sie folgende Funktionsdefinition ein: function anzeige(pWas:String):Void{
trace(„Sie haben auf „+pWas+“ ge klickt.“); }
9. Ändern Sie die Textzuweisung (Fettdruck): Lichtpunkt
Bei Mausklick auf den Textlink zeigt Flash im Nachrichtenfenster die Meldung „Sie haben auf Licht geklickt.“ an. In der Funktion anzeige() rufen wir den trace()-Befehl auf und geben einen Text zusammen mit einem Argument aus, das innerhalb des -Tags übergeben wird. Bei asfunction handelt es sich um ein spezifisches Protokoll, das eine Kommunikation mit AS erlaubt. Dabei können beliebige Funktionen aufgerufen werden, also sowohl in AS vordefinierte wie auch selbst erstellte. Sie lassen sich zwar parametrisieren, doch ist die Anzahl der zulässigen Argumente auf ein einziges beschränkt. Da der Aufruf als Wert eines HTML-Attributs erfolgt, ist das übergebene Argument immer vom Datentyp String. Leider versagt an dieser Stelle die Typisierung von Flash. Wenn Sie beispielsweise den Parameter in der Funktionsdekla-
190
Kapitel 14 String, Textfield, Textformat
ration auf einen anderen Datentyp festlegen, erfolgt nicht die ansonsten auftretende Fehlermeldung bezüglich einer Typdiskrepanz. Unser Beispiel mag nun zugegebenermaßen nicht unbedingt die klügste Anwendung sein, aber sie verdeutlicht die prinzipielle Funktionsweise von as function. So ließe sich beispielsweise per Mausklick ein Textfeld mit Zusatzfunktionen öffnen oder es könnte ein Textlink zum Weiterblättern der Seite definiert werden. Ebenfalls möglich ist die Verwendung des -Tags, das aber nicht in allen Player-Versionen sauber dargestellt wird. Daher sollte man dessen Einsatz sorgfältig testen, um keine unangenehmen Überraschungen zu erleben.
anders, da das in CSS-Attributen wie font-family übliche Minus-Zeichen nicht akzeptiert wird. An dessen Stelle verwendet Flash eine Binnen-Majuskel, was aus dem vorgenannten Attribut fontFamily macht. Die von AS 2.0 unterstützten Attribute listet die Tabelle auf. Bei den Angaben zu Ausdehnung, Größe und Rand wird nicht zwischen Pixel und Punkt unterschieden, alle Werte werden als Pixelangabe ausgeführt. Geben Sie wie in CSS gewohnt eine Maßeinheit mit an, ignoriert sie Flash. Die Definition des Stylesheets kann ebenso innerhalb von Flash wie außerhalb in einer klassischen CSS-Datei erfolgen, wobei sich letztgenannte Variante vor allem dann anbietet, wenn mehrerer unabhängige Dateien auf die gleiche Weise formatiert werden sollen. 1. Erstellen Sie einen neuen Film. 2. Weisen Sie der Ebene actions folgendes Bildskript zu:
14.10.3 Formatierung mit CSS Unter dem Aspekt der Formatierung bietet CSS gegenüber den bisherigen Methoden zumindest grundsätzlich die Möglichkeit, Texte, die mit verschiedenen Techniken wie Flash, HTML und XML erstellt werden, auf die gleiche Weise zu formatieren. Damit kann man die visuelle Darstellung einander angleichen, ohne zweimal vollständig neu entwickeln zu müssen. Allerdings muss man bedenken, dass Flash derzeit nur einen begrenzten Satz an CSS-Eigenschaften unterstützt. Auch ist die Schreibweise in Flash etwas
var tComp:TextField = this.createText Field(„computerspiele“, 1, 100, 50, 400, 200); tComp.multiline = true; tComp.wordWrap = true; tComp.border = true;
tComp.background = true;
tComp.backgroundColor = 0xffff99; tComp.autoSize = „left“;
CSS-Attribut
Flash-Entsprechung
Bedeutung
color
color
Textfarbe (hexadezimal), keine namentlich bezeichneten Farben (z. B. blue) möglich
display
display
Zulässig sind inline, block und none.
font-family
fontFamily
Kommaseparierte Liste von Schriften mit absteigender Präferenz
font-size
fontSize
Größe in Pixel
font-style
fontStyle
Zulässig sind normal und italic.
font-weight
fontWeight
Zulässig sind normal und bold.
kerning
kerning
Unterschneidung bei eingebetteten Schriften mit true und false als zulässige Werte.
letter-spacing
letterSpacing
Leerraum zwischen Zeichen, um eine gleichmäßige Aufteilung zu erreichen.
margin-left
marginLeft
Abstand links.
margin-right
marginRight
Abstand rechts.
text-align
textAlign
Textausrichtung mit den zulässigen Werten left, center, right und justify.
In Flash erlaubte CSS-Attribute
14.10 Textauszeichnung/-formatierung
tComp.html = true;
var cssFormatierung:TextField.Style Sheet = new TextField.StyleSheet();
cssFormatierung.setStyle(„p“, {fontFamily:„Georgia“, fontSize:„12“, color:“#5500ff“}); cssFormatierung.setStyle(„.titel“, {fontSize:„14“,fontWeight:„bold“}); tComp.styleSheet = cssFormatierung;
tComp.htmlText = „<span class=\ „titel\“>Computerspiele. p>
Was heutzutage eine milli ardenschwere Industrie darstellt, die mit der Filmbranche locker mit halten kann, begann vor Jahrzehnten ganz bescheiden mit einem einsamen Lichtpunkt. Er wanderte über den Bildschirm und konnte mit der Tasta tur gesteuert werden.
Heute, fast 50 Jahre später, glänzen Spiele durch eine schon fast photorealisti sche Grafik. Und neben die immer noch unvermeidliche Tastatur treten Maus und Joystick.
“;
Auch hier verwenden wir den gleichen Text und Aufbau wie vorher. Neu dagegen ist die Einrichtung eines Stylesheets mit Formatierungen anstelle der direkt im html-Text vorgenommenen Formatierungen. Zunächst wird ein Stylesheet instanziiert, das keine eigenständige Klasse darstellt, sondern Teil der Textfield-Klasse ist. Jedes zu definierende Element wird mit Hilfe der Methode setStyle() festgelegt, wobei wir den Namen eines Tags (z. B. „p“) oder einer selbst definierten Klasse (z. B. „.titel“), gefolgt von einem Objekt mit den Attributen und gewünschten Eigenschaften, übergeben. Im konkreten Beispiel definieren wir, dass jeder Absatz mit dem Font Georgia in der Größe 12 und einer blauen Farbe angezeigt werden soll. Außerdem legen wir eine eigene Klasse mit dem Namen „titel“ fest, die eine Schriftgröße von 14 und die Auszeichnung fett enthält. Achten Sie darauf, bei der Klassendefinition dem Namen einen Punkt voranzustellen, während der konkrete Aufruf innerhalb des <span>Tags ohne diesen Punkt erfolgt, genau so, wie es auch in HTML geschieht. Fehlt der Punkt in der Definition, wird die dortige Formatierung ignoriert, was ebenfalls geschieht, falls er irrtümlicherweise im <span>-Tag verwendet wird. Während wir in Flash bei der Definition eines Stylesheets die Attribute entsprechend obiger Tabelle
191
anpassen müssen, werden externe Sheets mit den in HTML üblichen Namen korrekt geparst. 3. Erstellen Sie eine CSS-Datei mit folgenden Formatierungen: p{
font-family:Georgia; font-size:12;
color:#5500ff }
.titel{
font-size:14;
font-weight:bold }
4. Speichern Sie die Datei im gleichen Ordner wie ihre Flash-Datei unter dem Namen texte.css. 5. Ersetzen Sie in ActionScript alles nach der Instanziierung des Stylesheets durch: cssFormatierung.onLoad = function() {
tComp.styleSheet = cssFormatierung; tComp.htmlText = „<span class=\ „titel\“>Computerspiele. p>
Was heutzutage eine milli ardenschwere Industrie darstellt, die mit der Filmbranche locker mithalten kann, begann vor Jahr zehnten ganz bescheiden mit ei nem einsamen Lichtpunkt. Er wan derte über den Bildschirm und konnte mit der Tastatur gesteuert werden.
Heute, fast 50 Jahre später, glänzen Spiele durch eine schon fast photorealistische Gra fik. Und neben die immer noch unver meidliche Tastatur treten Maus und Joystick.
“; };
cssFormatierung.load(„texte.css“);
Die Zuweisung des Stylesheets erfolgt erst dann, wenn wir sicher gestellt haben, dass die externe Datei mit den benötigten Definitionen erfolgreich geladen wurde. Damit Flash weiß, welche Datei wir benötigen, wird sie mit der load()-Methode geladen. Die Vorgehensweise ist also ausgesprochen simpel und narrensicher. Auf diese Weise sind Sie in der Lage, die gleiche Formatierung sowohl für Ihre Flash- wie auch Ihre HTML-Seiten vorzunehmen. Das ist insbeson-
192
dere dann interessant, wenn Sie den gleichen Content auf beide Arten darstellen müssen für den Fall, dass Teile Ihrer Zielgruppe keinen Flash-Player verwenden möchten oder dürfen. Eingedenk der enormen Formatierungsmöglichkeiten, die CSS bietet, wäre eine noch bessere Unterstützung durch Flash wünschenswert.
14.11 Darstellung größerer Textmengen In unseren bisherigen Beispielen hielt sich die Textmenge doch recht in Grenzen, so dass wir uns keine Gedanken über den Zugriff darauf machen mussten. Bei erheblich umfangreicherem Text, der über eine einzelne Seite hinausgeht, ist es notwendig, festzulegen, wann welche Teile zu sehen sind. Am häufigsten verwendet man dazu einen Scrollbalken, der Zeilen nach oben bzw. unten verschiebt, um so vorher nicht sichtbaren Text freizulegen. Das Einzige, was für diese Lösung spricht, ist die Einfachheit ihrer Umsetzung. Denn aus dem Blickwinkel der Usability besitzt sie den Nachteil, allenfalls eine grobe Schätzung der gesamten Textmenge zu erlauben. Zudem widerspricht das zeilenweise Verschieben noch den Lesegewohnheiten von Anwendern, die allen Unkerufen zum Trotz noch von einer Buchkultur geprägt sind. Während vertikales Scrollen in Maßen genossen nicht übermäßig ungesund ist, verbietet sich horizontales Scrollen von selbst. Auch Bilder lassen sich scrollen, was jedoch nicht nur ziemlich ungewohnt ist, sondern zudem unschön aussieht. In einem solchen Fall empfiehlt es sich, das Bild verschiebbar zu machen, so dass der Anwender mit gedrückter Maustaste einen beliebigen Ausschnitt betrachten kann. 1. Erstellen Sie eine Standarddatei. 2. Fügen Sie auf objects in der linken oberen Ecke ein dynamisches Textfeld ein (Instanzname info, ca. 250 × 90, Verdana, 12, mehrzeilig, mit Rahmen). 3. Zeichnen Sie auf derselben Ebene links unterhalb des Textfeldes ein Dreieck (14 × 14, Farbe Orange). 4. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcPfeil, Instanzname btUp). 5. Fügen Sie daneben eine vertikal gespiegelte Kopie des Dreiecks ein (Instanzname btDown). Diese Dreiecke dienen später dazu, den Text zu scrollen.
Kapitel 14 String, Textfield, Textformat
6. Weisen Sie actions folgendes Bildskript zu: info.text = „Computerspiele.\n\nWas heutzutage eine milliardenschwere In dustrie darstellt, die mit der Film branche locker mithalten kann, begann vor Jahrzehnten ganz bescheiden mit einem einsamen Lichtpunkt. Er wan derte über den Bildschirm und konnte mit der Tastatur gesteuert werden.\n\ nHeute, fast 50 Jahre später, glänzen Spiele durch eine schon fast photo realistische Grafik. Und neben die immer noch unvermeidliche Tastatur treten Maus und Joystick.“; btUp.onPress = function() { info.scroll++; };
btDown.onRelease = function() { info.scroll--; };
Wenn Sie testen, lässt sich der Text beliebig nach unten und oben scrollen. Ein dynamisches bzw. Eingabetextfeld verfügt über mehrere Eigenschaften, die sich auf das Scrollen beziehen. Die wichtigste ist die Eigenschaft scroll, mit der die Nummer der Zeile angegeben wird, die sich am oberen Textfeldrand befindet. Am Anfang beträgt dieser Wert notwendigerweise 1. Mit jedem Mausklick auf btUp erhöhen wir diesen Wert, wodurch sich automatisch der Text nach oben verschiebt. Ein Klick auf btDown dagegen dekrementiert die genannte Eigenschaft, so dass der sichtbare Text nach unten wandert. Wenn relativ wenig Text auf recht kleinem Raum angezeigt werden soll, kann man eine derartige Lösung wählen. Komfortabler und für den Anwender besser ist die Verwendung einer von Flash zur Verfügung gestellten Komponente. Bei den von der Idee her eigentlich genialen Komponenten handelt es sich um kleinere Applikationen, die einfach in Ihre Anwendung eingebunden werden können und Funktionalitäten anbieten, die man ansonsten mitunter mühsam selber programmieren muss. An eigene Bedürfnisse angepasst werden diese Komponenten über Parameter, die in der Autorenumgebung unter einem gleichnamigen Fenster definiert werden können. 1. Erstellen Sie eine Standarddatei. 2. Fügen Sie auf objects in der linken oberen Ecke ein dynamisches Textfeld ein (Instanzname info,
14.12 Eingabetext
Abbildung 39: Verwendung der Komponente UIScrollBar
ca. 250 × 90, Verdana, 12, mehrzeilig, mit Rahmen). 3. Wählen Sie über <User Interface> die Komponente UIScroll Bar aus und fügen Sie auf objects ein. 4. Positionieren Sie die eben erzeugte Instanz am rechten Rand des Textfeldes wie auf Abbildung 39 zu sehen. 5. Markieren Sie die Komponente und öffnen , um dort unter _targetInstanzName den Namen des Textfeldes einzutragen, falls er nicht schon dort steht. Damit teilen Sie Flash mit, auf wen sich die Komponente bezieht. Normalerweise jedoch reicht es ab Flash CS4 aus, die Komponente an den Rand des textfeldes anzudocken, um automatisch eine Verbindung herzustellen. 6. Weisen Sie actions folgendes Bildskript zu: info.text = „Computerspiele.\n\nWas heutzutage eine milliardenschwere In dustrie darstellt, die mit der Film branche locker mithalten kann, begann vor Jahrzehnten ganz bescheiden mit einem einsamen Lichtpunkt. Er wan derte über den Bildschirm und konnte mit der Tastatur gesteuert werden.\n\ nHeute, fast 50 Jahre später, glänzen Spiele durch eine schon fast photo realistische Grafik. Und neben die immer noch unvermeidliche Tastatur treten Maus und Joystick.“;
Sie können beim Testen den Text beliebig scrollen, wobei Ihnen der Scrollbalken einen Hinweis auf die Gesamtlänge gibt.
193
Zugegebenermaßen ist das verwendete Skript enttäuschend kurz: Wir müssen lediglich den konkreten Text definieren, während die Scroll-Komponente das Verschieben der Textzeilen nach Mausklick automatisch übernimmt, ohne dass ein entsprechendes Schaltflächenereignis festgelegt wird. Diese Funktionalität haben die Entwickler der Komponente bereits vorprogrammiert, was uns einiges an Arbeit abnimmt. Lediglich die Verknüpfung zum Textfeld muss händisch per Parameter vorgenommen werden. Eine Alternative zu den bisherigen Lösungen besteht in der symbolischen Andeutung der Textseiten, etwa durch ein simples Rechteck mit einigen Linien, die Textzeilen darstellen sollen. Die Anzahl dieser Symbole verdeutlicht den Gesamtumfang des Textes, die aktuelle Seite sowie die bereits aufgerufenen Seiten lassen sich farblich hervorheben. Daraus ergibt sich eine bessere Orientierung als beim Scrollen. Gelegentlich findet man auch Textzeilen, die bei Mausklick weiteren Text ausklappen, was zwar als Effekt nett ausschauen kann, aber noch weniger Orientierung zulässt als das Scrollen. Sinn macht diese Variante am ehesten in Tabellen, die auf Mausklick kurze Zusatzinfos bieten möchten, und natürlich bei Menüs. Ein Beispiel finden Sie unter den Workshops zu Interfaces.
14.12 Eingabetext Wir haben bisher mit einem dynamischen Textfeld gearbeitet, dem wir zur Laufzeit Text zuweisen. Doch in vielen Situationen muss der Anwender die Möglichkeit haben, selbst Text einzugeben. Das bekannteste Beispiel stellt ein Formular dar, das sich nur mit Hilfe von Eingabetextfeldern realisieren läst. 1. Erstellen Sie einen neuen Film. 2. Weisen Sie der Ebene actions folgendes Bildskript zu: var tName:TextField = this. createTextField(„anmeldung“, this. getNextHighestDepth(), 20, 20, 100, 20); tName.border = true;
tName.background = true;
tName.backgroundColor = 0xffff99;
194
In der linken oberen Ecke taucht ein 100 Pixel breites und 20 Pixel hohes Textfeld mit gelbem Hintergrund auf. Wir erzeugen wie in einem vorhergehenden Abschnitt gelernt ein Textfeld und positionieren es auf der Bühne. Wenn Sie versuchen, einen Text hinein zu schreiben, wird das Feld allerdings nicht reagieren. Das liegt daran, dass Flash, falls wir nichts anderes definieren, automatisch ein dynamisches Textfeld erzeugt, während wir ein Eingabetextfeld benötigen. Der gewünschte Typ lässt sich einfach festlegen. 3. Erweitern Sie den Code: tName.type = „input“;
Nun können Sie Text eingeben. Über die Eigenschaft type lässt sich der gewünschte Textfeldtyp definieren. Er wird als String übergeben, wobei „input“ für ein Eingabetextfeld und „dynamic“ für ein dynamisches Textfeld möglich sind. Beachten Sie, dass ein Eingabetextfeld nur Sinn macht, wenn zugleich auch ein Rahmen aktiviert wird. Andernfalls hält sich das Feld vielleicht aus falsch verstandener Bescheidenheit dermaßen im Hintergrund, dass es nur durch Zufall gefunden wird, wenn sich der Cursor bei Rollover in den Eingabecursor ändert. Sie können das gerne testen, indem Sie temporär diejenigen Zeilen auskommentieren, die sich auf Rahmen und Hintergrund des Feldes beziehen. Beim Testen ist dann auf der Bühne nichts mehr zu sehen. Bei Eingabetextfeldern ist es mitunter nötig, die möglichen Zeichen einzuschränken, um so zu gewährleisten, dass keine unsinnigen Eingaben erfolgen. So würde es bei einem Feld, in dem man sein Alter eintragen muss, keinen Sinn machen, wenn auch Buchstaben verwendet werden könnten. 4. Erweitern Sie das Skript: var tAlter:TextField = this. createTextField(„alter“, this.get NextHighestDepth(), tName._x + tName._width + 5, 20, 100, 20); tAlter.border = true;
tAlter.background = true;
tAlter.backgroundColor = 0xffff99; tAlter.type = „input“;
tAlter.restrict = „0–9“;
Kapitel 14 String, Textfield, Textformat
Wir erzeugen ein zweites Feld mit denselben Eigenschaften wie das erste. Lediglich die Position wird um die Breite des ersten zuzüglich einem kleinen Abstand von 5 Pixeln nach rechts verschoben. Um die gültigen Zeichen zu definieren, greifen wir auf die Eigenschaft restrict zu, der als String alle Zahlen zwischen 0 und 9 übergeben werden. Eine Eingabe ist damit auf exakt diese Zeichen beschränkt. Auch komplexere Definitionen sind möglich. Nehmen wir der Übung halber an, alle Zahlen mit Ausnahme der vielleicht aufgrund ihrer schiefen Haltung suspekten 7 sollen erlaubt sein. 5. Ändern Sie die Zuweisung für die restrict-Eigenschaft (Fettdruck): tAlter.restrict = „0–9 ^ 7“;
Sie können wie zuvor alle Zahlen eingeben, lediglich bei der 7 streikt das Textfeld. Die nachfolgenden 8 und 9 dagegen machen keine Probleme. Durch das CaretZeichen weisen wir Flash an, alle danach genannten Zeichen zu sperren, selbst wenn sie sich innerhalb des vorher als erlaubt definierten Wertebereichs befinden. Das Gleiche funktioniert natürlich auch mit Buchstaben. Falls das Minus-Zeichen ebenfalls erwünscht ist, kann es nicht einfach eingetragen werden: tAlter.restrict = „0–9 –“;
Diese Definition wäre falsch, da nach dem betreffenden Zeichen ein Wert erwartet wird. Stattdessen müsste man ein Escape-Zeichen verwenden: tAlter.restrict = „0–9 \\-“;
Bei manchen Textfeldern empfiehlt sich darüber hinaus eine Beschränkung der Zeichenanzahl. So macht eine Altersangabe nur Sinn, wenn sie aus maximal drei Zeichen besteht. Ein höheres Alter wird jemand, der unser Textfeld ausfüllen möchte, ohnehin eher nicht erreichen, so dass weitere Zeichen überflüssig sind. 6. Erweitern Sie den Code: tAlter.maxChars = 3;
Die Eigenschaft maxChars erlaubt es, die Länge des Eingabetextes auf eine bestimmte Anzahl zu beschränken. Ist diese erreicht, wird nicht das erste Zeichen automatisch gelöscht, sondern es ist keine weitere Eingabe mehr möglich. Bei Änderungen muss mindestens ein Zeichen entfernt werden.
14.12 Eingabetext
Eine weitere Besonderheit stellen Felder dar, die maskiert werden sollen, so dass die konkrete Eingabe nicht sichtbar wird. Das gilt klassischerweise für ein Passwort. 7. Erweitern Sie den Code: var tPass:TextField = this.create TextField(„alter“, this.getNext HighestDepth(), tName._x, tName._y + tName._height + 5, 100, 20); tPass.border = true;
tPass.background = true;
tPass.backgroundColor = 0xffff99; tPass.type = „input“;
tPass.password = true;
Unterhalb des Textfeldes für den Namen taucht ein weiteres Feld auf, das bei Texteingabe nur Sternchen anzeigt. Wir erstellen in gewohnter Weise ein neues Feld, das 5 Pixel unterhalb des Feldes für den Namen platziert wird. Da wir seine Eigenschaft password auf true setzen, maskiert Flash automatisch alle Eingaben. Natürlich ist per AS trotzdem ein Zugriff auf seinen Inhalt möglich, da ansonsten keine Kontrolle der Richtigkeit erfolgen könnte. In einem Formular besteht eine Reihenfolge der Felder, so das sich einfach per Tabulator-Taste von einem zum nächsten wechseln lässt. Die Reihenfolge der Erzeugung bestimmt auch die Tabulator-Reihenfolge. Sie können das testen, indem Sie in das erste Feld links oben klicken und dann per Tabulator zu dem nächsten wechseln. Das zweite wird das Altersfeld oben rechts, das dritte das Passwort-Feld unten links sein. Unabhängig von der Erzeugung kann die Reihenfolge explizit per AS festgelegt werden. 8. Erweitern Sie den Code: tName.tabIndex = 1; tPass.tabIndex = 2;
tAlter.tabIndex = 3;
Wir beginnen zwar wiederum mit dem Namen, doch als nächstes folgt das Passwort-Feld und zum Schluss erst die Altersangabe. Um diese Reihenfolge festlegen zu können, müssen wir lediglich auf die Eigenschaft tabIndex zugreifen und eine positive Ganzzahl übergeben, deren kleinste das erste Feld festlegt. Wollen wir dagegen den Tabulator unterbinden, genügt:
195
tName.tabEnabled = tPass.tabEnabled = tAlter.tabEnabled = false;
Wir setzen die Eigenschaft tabEnabled einfach auf false, so dass ein Drücken der Tabulator-Taste von keinem Eingabefeld mehr wahrgenommen (oder genauer: an kein Feld mehr weitergeleitet) wird. Mitunter dient der Eingabetext einer gezielten Kommunikation, die in einer bestimmten Form erfolgen muss. Dann ist es notwendig, die eingegebenen Daten auf Korrektheit zu überprüfen. Nicht immer kann man die Korrektheit durch das Aussparen bestimmter Zeichen wie in einem vorhergehenden Beispiel gewährleisten. Bei einer Mail-Adresse beispielsweise sind nahezu alle Zeichen erlaubt, aber manche müssen auf jeden Fall vorkommen und bei manchen ist klar, an welcher Stelle sie auftreten müssen bzw. nicht auftreten dürfen. Wollten wir gerade bei einer Mail eine erschöpfende Analyse durchführen, kämen wir wohl auf ein Dutzend Bedingungen, die erfüllt sein müssen. Da es in unserem Beispiel nur um das Prinzip geht, beschränken wir uns auf einige wenige Kriterien. Betrachten wir eine E-Mail, so sieht die Adresse in allgemeiner Form so aus: [email protected] Damit verfügt sie u. a. über folgende Charakteristika:
• Mindestlänge 8 Zeichen • Exakt ein @-Zeichen • Das @-Zeichen steht weder an erster noch an zwei-
ter Stelle • Mindestens ein Punkt • Dieser Punkt folgt stets irgendwo hinter dem @-Zeichen • Zwischen diesem Punkt und dem @-Zeichen befinden sich mindestens zwei andere Zeichen • Die letzten beiden Zeichen bestehen weder aus einem Punkt noch einem @-Zeichen • Das erste Zeichen ist kein Punkt • Die Adresse darf kein Leerzeichen enthalten. Sicher existieren noch weitere Regeln, aber es würde schon ausreichen, wenn alle genannten Kriterien bei einer Überprüfung Anwendung fänden. Wir wählen beispielhaft die Kriterien Mindestlänge, Position des @-Zeichens sowie des letzten Punktes. 1. Erstellen Sie einen neuen Film.
196
Kapitel 14 String, Textfield, Textformat
2. Weisen Sie der Ebene actions folgendes Bildskript zu: //––---------- vars –--------------- var mBtn:MovieClip;
var aFehler:Array = [];
Abbildung 40: Eingabefeld für die Mail-Adresse
//--------- functions –--------------
schlossener Eingabe die Kontrolle des Feldes auszuführen und gegebenenfalls ein entsprechendes Feedback zu geben oder die Mail-Adresse zu akzeptieren. Wir legen zunächst mehrere Variablen an, um einen aus einem MovieClip bestehenden Button, die dort angezeigte Beschriftung sowie das Eingabetextfeld zu referenzieren. Innerhalb der Funktion init() wird zunächst das Eingabefeld errichtet, wobei wir den Code aus einer vorhergehenden Übung übernommen haben. Danach wird ein MovieClip mit Hilfe der Zeichnungsmethoden der MovieClip-Klasse gezeichnet. Damit der Anwender weiß, was es mit diesem Objekt auf sich hat, erhält es ein eigenes Textfeld, in dem die Beschriftung „Mail“ angezeigt wird. Um zu verhindern, dass jemand diese Beschriftung auswählen kann – was bei einem als Button zu verwendenden Objekt merkwürdig anmuten würde –, setzen wir die selectableEigenschaft des betreffenden Textfeldes auf false. Abschließend rufen wir die Funktion init() auf, um die gewünschten Objekte einzurichten.
var tMail:TextField, tMailBtn:TextField; function init() {
tMail = this. createTextField(„emailAdresse“, this.getNextHighestDepth(), 20, 20, 100, 20); tMail.border = true;
tMail.background = true;
tMail.backgroundColor = 0xffff99; tMail.type = „input“;
mBtn = this.createEmptyMovieClip („btFormular“, this.getNextHighest Depth()); mBtn._x = tMail._x;
mBtn._y = tMail._y+tMail._height+5; mBtn.lineStyle(0.25, 0 × 000000, 100); mBtn.beginFill(0xffffff, 100); mBtn.lineTo(60, 0);
mBtn.lineTo(60, 20);
3. Fügen Sie in der Deklaration der init()-Funktion unmittelbar vor der schließenden Klammer ein:
mBtn.lineTo(0, 0);
mBtn.onPress = kontrolle;
tMailBtn = mBtn. createTextField(„emailButton“, mBtn.getNextHighestDepth(), mBtn._ width/2, 0, 0, 0);
function kontrolle() {
mBtn.lineTo(0, 20); mBtn.endFill();
4. Erweitern Sie den Funktions-Block um folgende Deklaration: aFehler = [];
tMailBtn.autoSize = „center“;
var sTxt:String = tMail.text;
tMailBtn.text = „Mail“;
aFehler.push(„Das Eingabefeld ist leer“);
tMailBtn.selectable = false; }
//------------ start –-------------- init();
Als Ergebnis erhalten Sie, wie in Abbildung 40 gezeigt, ein gelb hinterlegtes Eingabetextfeld sowie darunter einen sehr schlicht gehaltenen Button mit der Beschriftung „Mail“. Er dient später dazu, bei abge-
if (sTxt.length<1) {
} else if (sTxt.length<8) {
aFehler.push(„Die Mail-Adresse muss mindestens 8 Zeichen umfas sen“); }
if (sTxt.charAt(0) == „@“ || sTxt. charAt(1) == „@“) {
14.12 Eingabetext
aFehler.push(„Das @-Zeichen befindet sich an der falschen Stelle“); }
if (sTxt.indexOf(„@“) != sTxt.last IndexOf(„@“)) { aFehler.push(„Es ist nur ein @-Zeichen erlaubt“); }
if (sTxt.lastIndexOf(„.“)<=sTxt. indexOf(„@“)+2) {
aFehler.push(„Der letzte Punkt befindet sich an der falschen Stelle“); }
if (aFehler.length>0) { for (a in aFehler) {
trace(aFehler[a]); }
} else {
trace(„Gültige Angabe“); } }
Wenn Sie testen und sich entgegen der sonstigen Gepflogenheit einmal bemühen, bewusst eine falsche Adresse einzugeben, erhalten Sie eine entsprechende Fehlermeldung im Nachrichtenfenster angezeigt. Wir weisen in init() dem als Button fungierenden MovieClip ein onPress-Ereignis zu, das bei Auftreten eine Funktion zur Kontrolle der Texteingabe aufruft. Diese Funktion wird innerhalb des FunktionsBlocks deklariert und leert zunächst das Array, das alle Fehlermeldungen aufnimmt. So wird sicher gestellt, das bei mehrmaliger fehlerhafter Texteingabe nicht die Fehler aus vorhergehenden Versuchen erneut angemahnt werden. Anschließend speichern wir den Inhalt des Eingabefelds in einer lokalen Variable, so dass wir einfacher darauf zugreifen können, ohne immer tMail.text schreiben zu müssen. Dieses Vorgehen ist nicht unbedingt notwendig, stellt aber eine gewisse Erleichterung dar. Die eigentliche Kontrolle besteht aus einer Anzahl an if-Bedingungen, die jeweils einzelne Zeichen kontrolliert. Entsprechend der Reihenfolge im Skript fragen wir nach, ob:
197
• Das
Eingabefeld weniger als 1 Zeichen enthält. In dem Fall ist es leer, weil jemand, vielleicht aus einer spontanen Scherzhaftigkeit heraus oder schlicht aus Vergesslichkeit den Mail-Button betätigte, ohne seiner Eingabepflicht Genüge zu tun. • Weniger als 8 Zeichen vorliegen, was bedeutet, dass die Länge zu kurz ist, um eine gültige Adresse darzustellen. Diese Bedingung muss mit der vorhergehenden über ein else if verbunden werden, da ansonsten bei Zutreffen der ersten Bedingung (kein Zeichen enthalten) auch diese zweite Bedingung automatisch zutrifft (kein Zeichen ist weniger als 8 Zeichen). • Das erste oder zweite Zeichen ein @-Zeichen enthalten, was nicht zulässig ist. Da wir ein logisches Oder verwenden, trifft diese Bedingung auch dann zu, wenn das erste und das zweite Zeichen einem @ entsprechen (schöne Grüße vom Spaßvogel aus der ersten Bedingung). • Das @ nur einmal auftritt. Das ist dann der Fall, wenn sein erstmaliges Auftreten (indexOf()) identisch ist mit seinem letztmaligen Auftreten (lastIndexOf()). Dieser Bedingung voranstellen könnte man noch eine Bedingung, die abfragt, ob das betreffende Zeichen überhaupt auftritt. Das lässt sich mit indexOf() bewerkstelligen, dessen Rückgabewert –1 beträgt, wenn das gesuchte Zeichen nicht gefunden werden konnte. • Der letzte Punkt mindestens drei Zeichen weit hinter dem @-Zeichen auftritt. • Das Array aFehler Elemente enthält. Das ist der Fall, wenn mindestens eine der vorgenannten Bedingungen zutrifft. Dann speichern wir eine entsprechende Meldung, die nun per trace() im Nachrichtenfenster ausgegeben wird. In einer konkreten Anwendung müsste diese Information in einem gesonderten Textfeld angezeigt werden. Ist das Array leer, handelt es sich um eine gültige Mail-Adresse und Sie müssten diejenige Funktion aufrufen, die beispielsweise die Daten an ein PHPSkript weiterleitet oder in anderweitiger Form auswertet. Hier steht stellvertretend dafür eine bescheidene trace()-Anweisung. In ähnlicher Weise würde man bei anderen Eingabefeldern verfahren, wobei sich dort die Abfrage i. d. R. einfacher gestaltet als bei einer Mail-Adresse.
15
Math-Klasse
Die Math-Klasse umfasst nicht, wie der Name vielleicht vermuten lassen könnte, alle möglichen mathematischen Objekte und Operationen. Einige sind nämlich so grundlegend, dass sie in anderer Weise zur Verfügung gestellt werden. So haben wir bereits die arithmetischen Operatoren mit den Grundrechenarten und den Datentyp Number als Oberbegriff für alle Arten von Zahlen kennen gelernt. Statt dessen bietet die Math-Klasse Methoden für etwas fortgeschrittenere mathematische Operationen wie Wurzelberechnungen, Zufallszahlen etc. Eingedenk einer schulischen Vorbelastung – erfahrungsgemäß gehört der Mathematikunterricht für die meisten Schüler zu den eher unangenehmen Erlebnissen – mögen Sie nun erschaudern und schnell weiter blättern wollen. Das wäre schade, denn gerade die Math-Klasse macht erst viele geskriptete Animationen und Effekte möglich, und der in Sachen Flash geradezu unermüdliche Publisher friendsofed hat dem Thema Mathematik und Flash gleich mehrere wunderbare Bücher gewidmet. Um Ihnen den Zugang zu erleichtern, haben wir das Thema in mehrere Blöcke aufgeteilt:
• Das
vorliegende Kapitel führt in die Funktionsweise der Math-Klasse ein. • Ein eigenständiges Kapitel befasst sich darauf basierend mit Trigonometrie und ermöglicht u. a. Kreisbewegungen, Drehungen etc. • Außerdem beruhen Effekte diverser Workshops auf den hier vermittelten Grundlagen.
15.1 Eigenschaften: Konstanten Flash stellt einige Werte als Konstanten zur Verfügung, von denen Sie wahrscheinlich nur eine kennen, und das ist auch diejenige, die für Anwendungen tatsächlich benötigt wird: Math.PI. Diese Konstante bzw. diese Zahl definiert das Verhältnis von Kreisumfang zu Kreisdurchmesser und beträgt ca. 3,14. Einen genaueren Wert können Sie sich, wenn Sie wollen, mit trace(Math.PI) ausgeben lassen. Diese Konstante ist unverzichtbar, wenn es um die Berechnung kreisförmiger Objekte und Bewegungen geht. Wir werden im Abschnitt über Trigonometrie auf sie zurückkommen. Mit den übrigen Konstanten werden Sie aller Wahrscheinlichkeit nach nicht in Berührung kommen. Sie sind hier nur der Vollständigkeit halber aufgezählt. Konstante
Wert
Math.E
ca. 2.7182
Math.LN10
ca. 2.3025
Math.LN2
ca. 0.6931
Math.LOG10E
ca. 0.4342
Math.LOG2E
ca. 1.4426
Math.PI
ca. 1.1415
Math.SQRT1_2
ca. 0.7071
Math.SQRT2
ca. 1.4142
Konstanten der Math-Klasse
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
199
200
15.2 Methoden Die Math-Klasse bietet zahlreiche Methoden an, die über simple Zufallszahlen und Rundungen bis hin zu Exponentialfunktionen und komplexen logarithmischen Funktionen reichen.
15.2.1 Auf- und Abrunden Berechnungen ergeben nicht zwangsläufig schöne runde Zahlen, also Ganzzahlen. Im Gegenteil, oft erhalten wir Gleitkommazahlen (ein ausführliches Beispiel finden Sie im Abschnitt Zufallszahlen), die aber in dieser Form nicht gut weiterverarbeitet werden können. Wenn wir etwa die Anzahl einzublendender Objekte oder eine Framenummer errechnen müssen, machen solche Fließkommazahlen keinen Sinn. Die Werte müssen daher gerundet werden. Flash stellt dazu drei Methoden zur Verfügung. Mit Math.round() wird abhängig von der vorliegenden Zahl entweder auf- oder abgerundet. Werte zwischen x.1 und x.4 werden auf x abgerundet, Werte ab x.5 werden auf x +1 aufgerundet, wobei x für eine beliebige Ganzzahl steht. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var nZahl1:Number = 1.4; var nZahl2:Number = 1.5;
trace(„Die Zahl „+nZahl1+“ ergibt gerundet: „+Math.round(nZahl1)); trace(„Die Zahl „+nZahl2+“ ergibt gerundet: „+Math.round(nZahl2));
Ausgabe im Nachrichtenfenster: Die Zahl 1.4 ergibt gerundet: 1 Die Zahl 1.5 ergibt gerundet: 2 Bei dieser Rundung überlassen Sie also letzten Endes Flash die Entscheidung, ob die resultierende Zahl durch die Rundung kleiner (vgl. nZahl1) oder größer (vgl. nZahl2) wird. Mit Math.floor() bzw. Math. ceil() können Sie statt dessen eine direkte Vorgabe machen, so dass Ihre Zahl auf jeden Fall ab- bzw. aufgerundet wird.
Kapitel 15 Math-Klasse
3. Erweitern Sie das Skript: trace(„Die Zahl „+nZahl1+“ ergibt abgerundet: „+Math.floor(nZahl1)); trace(„Die Zahl „+nZahl2+“ ergibt abgerundet: „+Math.floor(nZahl2));
Ausgabe im Nachrichtenfenster: Die Zahl 1.4 ergibt gerundet: 1 Die Zahl 1.5 ergibt gerundet: 2 Die Zahl 1.4 ergibt abgerundet: 1 Die Zahl 1.5 ergibt abgerundet: 1 Im Gegensatz zu vorher erhalten Sie unabhängig von der Nachkommastelle eine abgerundete Zahl, denn durch Math.floor() erzwingen wir eine derartige Abrundung auf die nächst kleinere Ganzzahl (vorausgesetzt natürlich, die übergebene Zahl ist ein Gleitkommawert). 4. Erweitern Sie das Skript: trace(„Die Zahl „+nZahl1+“ ergibt aufgerundet: „+Math.ceil(nZahl1)); trace(„Die Zahl „+nZahl2+“ ergibt aufgerundet: „+Math.ceil(nZahl2));
Ausgabe im Nachrichtenfenster: Die Zahl 1.4 ergibt gerundet: 1 Die Zahl 1.5 ergibt gerundet: 2 Die Zahl 1.4 ergibt abgerundet: 1 Die Zahl 1.5 ergibt abgerundet: 1 Die Zahl 1.4 ergibt aufgerundet: 2 Die Zahl 1.5 ergibt aufgerundet: 2 Mit Math.ceil() dagegen runden wir in jedem Fall auf. Diese Methoden helfen uns auch, wenn wir die Anzahl der Nachkommastellen begrenzen wollen. Wollen wir etwa Prozentangeben visualisieren, kann es sinnvoll sein, die Werte bis auf zwei Nachkomma stellen genau anzuzeigen. 5. Erweitern Sie das Skript: var nStellen:Number = 2; var nBasis:Number = 10;
var nZahl3:Number = 5.1856474232;
nZahl3 *= Math.pow(nBasis,nStellen); nZahl3 = Math.floor(nZahl3);
nZahl3 /= Math.pow(nBasis,nStellen); trace(„Zahl mit „ + nStellen + „ Nachkommastellen: „ + nZahl3);
15.2 Methoden
201
Ausgabe im Nachrichtenfenster: Zahl mit 2 Nachkommastellen: 5.18
15.2.2 Zufallszahlen
Mit Math.floor() können wir, wie gesehen, sehr einfach die Stellen nach dem Komma entfernen. Wenn wir eine Zahl mit mehr als zwei Nachkommastellen um zwei Stellen nach links verschieben bzw. mit 100 multiplizieren, können wir problemlos alles, was nun hinter dem Komma steht, entfernen. Aus der Zahl 5.1856474232 wird durch Multiplikation mit 100 518.56474232, durch Math.floor() entsteht daraus 518. Wird dieser Wert wieder durch 100 dividiert (um die ursprüngliche Verschiebung um zwei Stellen rückgängig zu machen), ergibt sich 5.18, was einem Abtrennen aller Nachkommazahlen ab der dritten Stelle entspricht. Der Multiplikator bzw. Divisor 100 ergibt sich aus dem verwendeten dezimalen Zahlensystem, dessen Basis die 10 darstellt. Jede Kommastelle, um die wir verschieben wollen, entspricht einer Multiplikation bzw. Division mit 10. Zwei Kommastellen ergeben eine Multiplikation bzw. – ach, Sie wissen schon – von 10 * 10, drei Kommastellen eine Multiplikation von 10 * 10 * 10 etc. Das lässt sich auch darstellen als 102 und 103 bzw. mit Hilfe der Math-Klasse als Math. pow(10,2) und Math.pow(10,3). Diese Berechnung lässt sich einfach in eine Funktion fassen.
Oft ist es notwendig, mit zufällig verteilten Werten zu arbeiten. Das gilt vor allem für Spiele, KI und geskriptete Animationen, aber auch eine Bildergalerie beispielsweise kann sinnvoll mit Zufallszahlen attraktiver gemacht werden. Flash bietet zwei Funktionen, um solche Zahlen zu erhalten. Die erste Funktion gilt als völlig veraltet und sollte nicht mehr verwendet werden, hat jedoch den Vorteil, dass sie zunächst einfacher zu verstehen ist. Es handelt sich um das simple random().
6. Ergänzen Sie das Skript: function nachkomma(pZahl, pStellen){
1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: this.onMouseDown = function(){ trace(random(10)); }
Mit dieser Funktion erhält man per Mausklick an beliebiger Stelle eine Zufallszahl zwischen 0 und 9. Das Argument in der Klammer gibt an, aus wie vielen verschiedenen Zahlen ein Wert ermittelt werden soll. Der kleinstmögliche Wert entspricht der 0, als größtmöglicher Wert kann dann nur die übergebene Zahl –1, also hier die 9, in Frage kommen. Dementsprechend würde random(1001) Werte zwischen 0 und 1000 errechnen. Die zweite Methode sieht wesentlich umständlicher aus, ist jedoch die empfohlene Variante.
var nPow:Number = Math. pow(10,pStellen);
3. Ersetzen Sie den trace()-Befehl durch:
pZahl = Math.floor(pZahl);
Beim Testen erhalten Sie anstelle der angenehm einfachen Ganzzahlen Fließkommawerte, die irgendwo zwischen 0 und 0.9 Periode rangieren. Der random()Methode der Math-Klasse können wir nicht mehr wie zuvor ein Argument übergeben. Sie verfügt nämlich über einen fest vorgegebenen Wertebereich. Zudem gibt sie Zufallszahlen zurück, die in den meisten Fällen so nicht verwendet werden können. Sie müssen erst bearbeitet werden. Daher bürdet uns diese Version mehr Arbeit auf als die ältere Variante. Sie hat jedoch den Vorteil, dem zu entsprechen (oder genauer: näher an dem zu liegen), was höhere Programmiersprachen bieten. AS nähert sich also einem Standard an, so dass der Entwickler flexibler wird. Dafür muss man mitunter einen gewissen Mehraufwand in Kauf nehmen.
pZahl *= nPow; pZahl /= nPow; return pZahl; }
trace(„Zahl: „+nachkomma(3.14786932,4));
Ausgabe im Nachrichtenfenster: Zahl: 3.1478 Sie müssen beim Aufruf lediglich die gewünschte Zahl sowie die Anzahl der Nachkommastellen angeben und erhalten als Ergebnis den entsprechend gerundeten Wert.
trace(Math.random());
202
Auf welche Weise erhalten wir nun dieselben Zahlen wie mit der vorhergehenden Version? Dazu müssen wir die Zufallszahl mit einem Wert multiplizieren, so dass sie größer 0 wird, und anschließend runden wir sie auf oder ab, um sie in eine Ganzzahl zu verwandeln. 4. Erweitern Sie das onMouseDown-Ereignis um folgende Zeilen: var nZufallFliess:Number = Math.random()*10; var nZufallGanz = Math. floor(nZufallFliess);
trace(„Fliesskommazahl: „+nZufallFliess); trace(„Ganzzahl: „+ nZufallGanz);
Bei jedem Klicken speichert Flash in der Variablen nZufallFliess eine Zufallszahl, z. B. 4.20338441617787. In der Variablen nZufallGanz wird dieser Wert abgerundet, so dass wir als Zufallszahl der gesamten Berechnung 4 erhalten. Die Abrundung findet in jedem Fall statt, selbst wenn der zuerst ermittelte Wert 4.99999999 beträgt. Als kleinstmögliche Zahl gibt Flash 0 und als größtmögliche Zahl 9 (d. h.: 10 –1) zurück, also dieselben Werte wie bei random(10). 5. Um das Skript etwas lesbarer zu gestalten, ersetzen Sie alles innerhalb des onMouseDown-Ereignisses wie folgt: var nZufall = Math.floor(Math.random()*10); trace(„Zufallszahl: „+ nZufall);
Wir erhalten bei Mausklick eine zufällig gewählte Ganzzahl zwischen 0 und 9. Wollten wir, was bei random(10) nicht so einfach möglich ist, die 10 mit einschließen, ohne auf die 0 verzichten zu müssen, würde man schreiben (Fettdruck): var nZufall = Math.round(Math.random()*10);
Nun wird mathematisch korrekt gerundet: Nachkommawerte kleiner 5 werden ab-, alle anderen aufgerundet. Bei 0.2 etc erhalten wir also 0, bei 0.5 etc die 1. Daher ergibt 9.5 den aufgerundeten Wert 10, der zulässige Wertebereich beträgt also 0 bis 10. Natürlich lässt sich die 0 auch vollständig ausschließen, indem man Math.ceil() verwendet und somit nur auf-
Kapitel 15 Math-Klasse
rundet. Ein wichtiger Punkt muss allerdings beachtet werden, wenn wir mit Math.round() arbeiten. Denn diese Methode führt dazu, dass der kleinste und der größte Wert weniger häufig auftreten können als alle anderen Zahlen. Bleiben wir bei dem vorherigen Beispiel. Wann erhalten wir 0, wann 1? Betrachten wir das Ganze unter Beschränkung auf eine einzige Nachkommastelle. Wenn Math.random() 0.0 bzw. 0.4 zurückgibt, runden wir auf 0 ab, bei 0.5 bis 1.4 wird auf 1 gerundet. Fünf verschiedene Werte entsprechen einer 0, zehn dagegen der 1. Die Wahrscheinlichkeit, eine 0 zu erhalten, ist damit halb so hoch wie diejenige einer 1. Gleiches gilt für den höchsten Wert, die 10. Manchmal ist es notwendig, innerhalb eines größeren Wertebereichs Zufallszahlen zu ermitteln, z. B. zwischen 20 und 50. Leider ist das nicht möglich, indem wir einfach entsprechende Argumente an die Math.random()-Methode übergeben. Aber eine einfache Überlegung zeigt die Lösung auf: Zwischen 20 und 50 besteht eine Differenz von 30. Würden wir eine beliebige Zahl zwischen 0 und 30 ermitteln und anschließend 20 hinzu addieren, hätten wir den erwähnten Wertebereich realisiert. 6. Fügen Sie nach den bisherigen trace()-Befehlen ein: var nZufallZwischen = Math. round(Math.random()*30) + 20;
trace(„Zufallszahl zwischen 20 und 50: „+ nZufallZwischen);
Im Grunde genommen ist es sehr einfach, herauszufinden, welche Zahlen hier ermittelt werden können. Im ersten Schritt errechnet man den kleinstmöglichen Wert (hier: 0) und den größtmöglichen Wert (hier: 30). Addiert man dazu die nach der Rundung angegebene Zahl, kennt man den Wertebereich. Natürlich muss nicht zwangsläufig eine Addition erfolgen. Verwendet man statt dessen eine Subtraktion, ist es möglich, negative Werte zu erhalten. 7. Fügen Sie nach den bisherigen trace()-Befehlen ein: var nZufallNegativPositiv = Math. round(Math.random()*30) – 15;
trace(„Zufallszahl negativ oder positiv: „+ nZufallNegativPositiv);
Die ausgegebenen Werte schwanken zwischen –5 und 5, die 0 eingeschlossen. Um prinzipiell die gleiche
15.2 Methoden
Anzahl an positiven wie negativen Werte zu erhalten, müssen wir lediglich den an Math.random() übergebenen Operanden, dividiert durch 2, subtrahieren. Wollen wir also Zufallszahlen zwischen –1000 und 1000, lautet die Formel: Math.round(Math.random()*2000) – 1000.
Es stehen insgesamt 2001 Zahlen zur Verfügung, nämlich 1000 negative (–1000 bis –1), 1000 positive (1 bis 1000), sowie die 0. Die kleinstmögliche abgerundete Zahl beträgt 0, von der 1000 subtrahiert wird, was –1000 ergibt. Die größtmögliche Zahl entspricht 2000, von der wir ebenfalls 1000 subtrahieren und so 1000 erhalten. Es mag vielleicht merkwürdig erscheinen, dass wir negative Zufallszahlen errechnen wollen. Aber sie erweisen sich in bestimmten Situationen als ausgesprochen nützlich, etwa wenn wir Objekte animieren wollen, die sich zufallsbedingt entweder nach rechts oder nach links (bzw. nach oben oder unten) bewegen sollen. Ein derartiges Beispiel finden Sie im Kapitel Geskriptete Animationen. Da wir öfter mit einem spezifischen Wertebereich arbeiten wollen, empfiehlt es sich, eine entsprechende Funktion zu schreiben. 8. Ersetzen Sie das bisherige Skript vollständig durch folgende Zeilen: function zufall(pMin:Number, pMax:Number):Number { var nZuf = pMax-pMin;
return Math.round(Math. random()*nZuf)+pMin; }
this.onMouseDown = function() { trace(zufall(-5, 15)); };
Als Parameter übergeben wir jeweils die kleinst- und die größtmögliche Zahl. Unsere Funktion errechnet zunächst die Differenz zwischen beiden, ermittelt auf dieser Basis eine Zufallszahl und addiert anschließend den gewünschten Minimalwert. Ist dieser negativ wie in unserem Beispiel, wird er automatisch subtrahiert, da die Addition einer negativen Zahl bekanntermaßen einer Subtraktion entspricht. Da wir uns Code erspart haben, müssen wir bei der Übergabe der Parameter darauf achten, dass die erste Zahl tatsächlich kleiner als die zweite ist. Alternativ könnte man mit Math.
203
max() die größere Zahl automatisch ermitteln und
dementsprechend die Berechnung anpassen.
9. Im Code wären folgende Änderungen notwendig (Fettdruck): function zufall(pMin:Number, pMax:Number):Number {
var nMax:Number = Math.max(pMin, pMax); var nMin:Number = Math.min(pMin, pMax); var nZuf:Number = nMax-nMin; return (Math.round(Math. random()*nZuf)+nMin); }
this.onMouseDown = function() { trace(zufall(5, -15)); };
In einer Variablen speichern wir zuerst die größere, anschließend in einer anderen die kleinere der beiden Zahlen. An der Berechnung der konkreten Zufallszahl ändert sich natürlich nichts. Beim Aufruf der Funktion spielt es nun keine Rolle, welche der übergebenen Zahlen größer ist. Wichtig ist nur noch, dass sie voneinander verschieden sind, was wir schlicht voraussetzen, denn andernfalls würde eine Zufallszahl keinen Sinn machen. Ist es allerdings unabdingbar, dass alle Werte mit der gleichen Wahrscheinlichkeit erscheinen sollen, bleibt nur die Verwendung von Math.floor() bzw. Math.ceil(), während Math.round() unser Ergebnis verzerren würde. In Sonderfällen kann es notwendig sein, eine Zufallszahl zu ermitteln, die entweder 1 oder –1 beträgt, aber keine 0. Glücklicherweise lässt sich das einfach realisieren. 10. Ergänzen Sie das onMouseDown-Ereignis um folgende Zeile:
trace(Math.round(Math.random())*2–1);
Beachten Sie, das im Gegensatz zu vorher eine Multiplikation erst nach dem Runden erfolgt. Ohne Multiplikation und ohne Subtraktion erhalten wir Werte zwischen 0 und 1. Bei einer 0 ändert sich nichts durch die Multiplikation mit 2, die Subtraktion dagegen wird durchgeführt, so dass sich als Ergebnis –1 ergibt. Erhalten wir zunächst 1, verdoppelt sich ihr Wert auf
204
Kapitel 15 Math-Klasse
2 durch die Multiplikation. Subtrahieren wir 1, bleibt als Ergebnis 1 übrig. Dasselbe lässt sich natürlich mit beliebigen anderen Werten durchführen, solange vom Operanden, mit dem man multiplizieren möchte, nachher dessen Hälfte wieder subtrahiert wird. So ergibt beispielsweise Math.round(Math.random())*10–5 die Werte 5 bzw. –5. Etwas anders gestaltet sich die Berechnung, wenn Sie einen größeren Wertebereich negativer und positiver Zahlen abdecken und dabei ebenfalls die 0 ausschließen wollen. Das wäre z. B. notwendig, wenn Sie ein Objekt mit einem Zufallstempo bewegen wollen und dabei verhindern müssen, dass es starrsinnig auf seiner Position verharrt – nur weil sowohl sein x- wie auch sein y-Tempo zufälligerweise eben 0 betragen! 11. Erweitern Sie das Skript um folgende Funktionsdeklaration: function zufallOhne (pMax:Number):Number {
var nZufall:Number = Math.round (Math.random()*(pMax*2))-pMax; while (nZufall == 0) {
nZufall = Math.round(Math. random()*(pMax*2))-pMax;
• Das
anschließende Runden macht daraus eine Ganzzahl zwischen 0 und 20. • Durch die zuletzt erfolgende Subtraktion von 10 entspricht das Ergebnis einem Wert zwischen –10, falls vorher eine 0 vorlag, und 10, falls zuvor eine 20 vorlag. Wenn bei dieser Berechnung keine 0 ermittelt wird, führt Flash sofort den return-Befehl aus. Andernfalls tritt die while-Schleife in Aktion, die solange alle vier eben erläuterte Rechenschritte erneut durchführt, wie wir eine 0 als Endergebnis erhalten. Faktisch verhindern wir damit, dass eine 0 per return zurück gegeben wird. Nun könnte man einwenden, dass die while-Schleife zumindest theoretisch permanent eine 0 ermitteln könnte, was zu einer Endlosschleife führen würde. Das trifft zwar zu, ist aber extrem unwahrscheinlich. Wir können noch einen Schritt weiter gehen, indem wir nicht nur die 0, sondern einen ganzen Wertebereich ausschließen. Stellen wir uns noch mal das Beispiel eines zu animierenden Objekts vor. Eine 0 würde eine Animation unmöglich machen, aber andere Werte wie 1 oder –1 könnten so langsam sein, dass wir sie ebenfalls nicht verwenden wollen.
}
13. Ändern Sie die Deklaration von zufallOhne() folgendermaßen (Fettdruck):
function zufallOhne(pMax:Number, pOhneMin:Number, pOhneMax:Number): Number {
return (nZufall); }
12. Ändern Sie das onMouseDown-Ereignis wie folgt:
this.onMouseDown = function() {
};
trace(zufallOhne (10));
Ein beliebiger Mausklick führt zur Ausgabe einer Zufallszahl, die zwischen 10 und –10 liegt. Eine 0 erscheint dabei nicht. Bei jedem Aufruf weisen wir der in der Funktion zufall() eingerichteten lokalen Variablen nZufall einen Wert zu, der sich folgendermaßen errechnet:
• Zuerst multiplizieren wir das übergebene Argument mit 2. Wird eine 10 übergeben, erhalten wir an dieser Stelle 20. • Diesen Wert multiplizieren wir mit einer Zufallszahl zwischen 0 und 0.9, so dass wir als Ergebnis eine Zahl zwischen 0,0 und 19,9 erhalten.
var nZufall:Number = Math.round (Math.random()*(pMax*2))-pMax; while (nZufall>=pOhneMin && nZufall<=pOhneMax) {
nZufall = Math.round(Math. random()*(pMax*2))-pMax; }
return (nZufall);
}
14. Ändern Sie im onMouseDown-Ereignis den Aufruf der Funktion (Fettdruck): this.onMouseDown = function() {
trace(zufallOhne(10, -5, 5));
};
15.2 Methoden
Beim Testen gibt Flash Zahlen aus, die zwischen –10 und –6 sowie 6 und 10 liegen. Wir erweitern die Funktion um zwei Parameter, um eine Unter- und eine Obergrenze für die auszuschließenden Zahlen einzurichten. Die while-Schleife muss dann lediglich prüfen, ob die eine Zeile vorher ermittelte Zufallszahl größer als die Unter- und kleiner als die Obergrenze ist. Trifft das zu, lassen wir uns eine neue Zufallszahl geben, andernfalls kann Flash den Wert direkt zurück geben. Auch hier gehen wir davon aus, dass derjenige, der Argumente übergibt, weiß, was er tut. Ansonsten müsste man die Funktion anpassen, um den höchsten und niedrigsten Wert zu ermitteln, wie wir es schon in einem früheren Beispiel getan haben (s. Schritt 9). Für unseren alltäglichen Gebrauch machen die per Math.random() entstehenden Werte einen angenehm zufälligen Eindruck. Tatsächlich handelt es sich dabei jedoch nur um sogenannte Pseudo-Zufallszahlen, da sich echte Zufallszahlen einer formelmäßigen Berechnung aus prinzipiellen Gründen eher verweigern, um es zurückhaltend zu formulieren.
15.2.3 Weitere Methoden Zu den gebräuchlicheren weiteren Methoden zählen Math.abs(), Math.min(), Math.max() sowie Math.pow(), die bereits z. T. angesprochen wurden. Wenn Sie unabhängig vom konkreten Vorzeichen nur den Wert einer Variablen auslesen wollen, hilft Ihnen Math.abs(). Das ist z. B. nützlich bei einer geskripteten Animation, die zum Stillstand kommen soll, sobald die Entfernung zwischen zwei Objekten einen bestimmten Wert unterschreitet. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: var nZahl1:Number = 5;
var nZahl2:Number = -10;
trace(„Absoluter Wert nZahl1: „ + Math.abs(nZahl1)); trace(„Absoluter Wert nZahl2: „ + Math.abs(nZahl2));
Ausgabe im Nachrichtenfenster: Absoluter Wert nZahl1: 5 Absoluter Wert nZahl2: 10
205
Wir richten zunächst zwei Variablen mit einer positiven bzw. einer negativen Zahl ein. Würden wir die Zahlen unverändert tracen, würde Flash natürlich auch eine positive sowie negative Zahl anzeigen. Durch die Math.abs()-Methode verändern wir allerdings temporär, nämlich nur für den Zeitpunkt der Ausgabe, das Vorzeichen der negativen Zahl und erhalten so ausschließlich positive Zahlen. Unter Berücksichtigung des Vorzeichens können wir ermitteln, welche der Zahlen größer bzw. kleiner ist. 3. Ergänzen Sie das Skript: trace(„Kleinere Zahl: „+Math.min(nZahl1, nZahl2)); trace(„Größere Zahl: „+Math.max(nZahl1, nZahl2));
Ausgabe im Nachrichtenfenster: Kleinere Zahl: -10 Größere Zahl: 5 Negative Zahlen sind immer kleiner als positive Zahlen. Daher wird als kleinere Zahl –10 und als größere Zahl 5 ermittelt. Würden wir den Vergleich der Zahlen mit Hilfe von Math.abs() durchführen, dann würde sich das Verhältnis zwischen den Zahlen natürlich umkehren. 4. Ändern Sie die Zeilen bezüglich des Größenvergleichs (Fettdruck): trace(„Kleinere Zahl: „+Math.min (Math.abs(nZahl1), Math.abs (nZahl2)));
trace(„Größere Zahl: „+Math.max(Math. abs(nZahl1), Math.abs(nZahl2)));
Ausgabe im Nachrichtenfenster: Kleinere Zahl: 5 Größere Zahl: 10 Den Einsatz von Math.pow() haben wir bereits oben im Zusammenhang mit Rundungen kennen gelernt. Diese Methode berechnet nZahl1 hoch nZahl2, wobei nZahl1 dem ersten und nZahl2 dem zweiten übergebenen Parameter entspricht. 5. Ergänzen Sie das Skript: trace(„2 hoch 8 ergibt “ + Math.pow(2, 8));
Ausgabe im Nachrichtenfenster: 2 hoch 8 ergibt 256
206
In unserer Berechnung wird die Zahl 2 sieben mal mit sich selbst multipliziert, was dem Wert 256 entspricht. Zahlreiche Methoden beziehen sich auf trigonometrische Berechnungen wie Winkel und Kreisposi-
Kapitel 15 Math-Klasse
tionen (Math.cos(), Math.asin(), Math.atan(), Math. atan2(), Math.sqrt(), Math.sin(), Math.tan()). Ausführlichere Informationen dazu finden Sie im Kapitel Trigonometrie.
Color-/ColorTransform-Klasse
Für optisch wahrnehmbare Objekte wie MovieClips und Grafiken stellt die Farbe eine zentrale Eigenschaft dar. Denn sie übt großen Einfluss darauf aus, was wir bei der Wahrnehmung empfinden, ja mitunter sogar, ob wir das betreffende Objekt überhaupt bemerken. Ein MovieClip beispielsweise in feurigem Signalrot ruft bei uns andere Empfindungen hervor als eine nüchterne kühl-blaue Einfärbung. ActionScript gibt uns glücklicherweise teilweise recht ausgefeilte Methoden zur Hand, um Farbeffekte zu erzielen bzw. Farben zu definieren. Interessanterweise versteht Flash Farbe nicht einfach als Eigenschaft von MovieClips oder Grafiken, so dass man am je spezifischen Objekt eine direkte Änderung vornehmen könnte. Statt dessen muss man zuerst eine vom grafischen Objekt unabhängige Klasse instanziieren und anschließend einen Bezug zu diesem Objekt herstellen. In Flash-Versionen bis einschließlich MX 2004 handelte es sich dabei um die Color-Klasse, auf die man ab Flash 8 zugunsten der ColorTransformKlasse verzichten sollte (was eigentlich schade ist, da die Color-Klasse wunderbar einfach zu handhaben war). Unabhängig davon, für welche Variante Sie sich entscheiden, bleibt die zugrundeliegende Funktionsweise der Einfärbung vergleichbar und entspricht dem, was Sie im Eigenschaftsfenster bei markiertem MovieClip unter der Option <Erweitert> <Einstellungen> festlegen können. Ausschließlich ab Flash 8 stehen Ihnen zusätzlich Filter wie der ColorMatrix-Filter zur Verfügung, die komplexe Farbeffekte à la Photoshop ermöglichen. Sie werden im Kapitel BitmapData- und Filter-Klasse angesprochen.
16
16.1 Klassenpakete und Instanziierung von Klassen Was Flash bereits mit der Version MX begonnen hat, wurde insbesondere in Flash 8 und 9 perfektioniert: Die Bereitstellung der benötigten Funktionalitäten durch Klassen, die oft in Packages bzw. Paketen zusammengefasst werden. Dabei handelt es sich um Ordner, in denen zumeist mehrere, von der Logik her zusammengehörige einzelne Klassen abgelegt werden. Sie befinden sich in einem dem Autorensystem bekannt gemachten Klassenpfad, so dass man leicht darauf zugreifen kann. Darüber hinaus existiert standardmäßig ein relativer Pfad, der von dem Speicherort der aktuellen Datei ausgeht. Weitere Pfade können eingetragen werden über Der Zugriff auf Klassen erfolgt auf verschiedene Arten:
• Sie geben im Code an der Stelle, an der eine Funk-
tion, Eigenschaft oder Methode verwendet wird, unmittelbar den gesamten Pfad an. Das mag die einfachste Variante sein, aber auf die Dauer wird der Code durch die ständigen Wiederholungen unübersichtlich und fehleranfällig, da man leicht Opfer von Schreibfehlern werden kann. Daher sollte man auf ein derartiges Vorgehen eher verzichten. • Zu Beginn des Frames, in dem die betreffende Funktionalität benötigt wird, importiert man die Klassen. Bei dieser Schreibweise muss der Klassenpfad Flash bekannt sein. Sollte er von der Standardeinstellung abweichen, muss er wie oben erwähnt, im Menü angepasst werden.
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
207
208
Kapitel 16 Color-/ColorTransform-Klasse
• Man verzichtet komplett auf eine Pfadangabe und ruft die Klasse direkt auf. Das setzt voraus, dass Klasse und fla-Datei im selben Ordner liegen.
Dies gilt natürlich nur für die Arbeit in der Autorenumgebung. Auf dem Zielrechner, der die von Ihnen erstellte swf-Datei abspielt, müssen die Klassen nicht mehr vorliegen, denn der benötigte Code wird unmittelbar in die resultierende swf exportiert.
16.2 Vollständiges Einfärben Die einfachste Farbänderung besteht darin, einem beliebigen Objekt unabhängig davon, wie viele verschiedene Farben ursprünglich vorlagen, eine einzige neue Farbe zuzuweisen. 1. Erstellen Sie eine Standarddatei. 2. Zeichnen Sie auf objects an beliebiger Stelle ein Rechteck (150 × 100, Linie 1, schwarz, Füllung 0099ff). 3. Wandeln Sie es in einen MovieClip um (Bibliotheksname mcRecht, Instanzname recht1, Registrierungspunkt links oben). 4. Duplizieren Sie das Rechteck auf der Bühne. 5. Positionieren Sie das Duplikat so, dass es recht1 nicht überdeckt (Instanzname recht2). 6. Weisen Sie actions folgendes Bildskript zu: //------------- import –------------ import flash.geom.*;
//–––---------- vars –-------------- var ctColor:ColorTransform = new ColorTransform(); var trMC:Transform;
//---------- functions –------------ function init():Void {
trMC = new Transform(recht1); ctColor.rgb = 0xffaa00;
trMC.colorTransform = ctColor; }
//–---------- aufruf –-------------- init();
Beim Testen wird sich Ihr blaues Rechteck in ein orangenes verwandeln. Achten Sie auf die Art der Farbzuweisung: Das eingefärbte Objekt besteht nun
Abbildung 41: Einfärben eines MovieClips
aus einer einzigen Farbe, die sowohl Füllung wie auch Randlinien umfasst, während das zum Vergleich nicht eingefärbte Objekt zwei verschiedene Farben anzeigt. Da die ColorTransform- und die TransformKlasse zum geom-Package gehören, müssen wir es zuerst importieren. Anschließend richten wir jeweils eine Variable für eine Instanz dieser Klassen ein. Allgemein werden über die Transform-Klasse Daten gesammelt, die sich auf Farb- sowie Koordinatenänderungen eines MovieClips beziehen. Vor einer Verwendung muss sie mit einem konkreten MovieClip verknüpft werden. Die ColorTransform-Klasse dagegen erlaubt die Manipulation der drei Farbkanäle für Rot, Grün und Blau sowie einen Alphakanal. In der init()-Funktion instanziieren wir die Transform-Klasse und übergeben als Argument den Instanznamen desjenigen MovieClips, den wir einfärben wollen, hier also recht1. Damit speichern wir in der Variablen trMC faktisch die Farbeigenschaften, auf die wir zugreifen würden, wenn wir wie oben erwähnt die erweiterten Farboptionen für unseren MovieClip aufrufen könnten. Um sie zu ändern, müssen wir auf das zuvor instanziierte ColorTransform-Objekt zugreifen, da eine direkte Manipulation der transformEigenschaft eines MovieClips nicht möglich ist. Eine beliebige Farbe lässt sich definieren, wenn wir auf die entsprechende rgb-Eigenschaft der ColorTransform-Instanz zugreifen und ihr einen Wert zuweisen. In unserem konkreten Fall wählen wir besagten Orange-Ton, dessen Hexadezimalwert 0xffaa00 entspricht. Flash erwartet an dieser Stelle ein Wert vom Datentyp Number, der als eher schwer lesbare Ganzzahl oder in Hexadezimalform übergeben werden kann. In letztgenanntem Fall verweist das Präfix „0x“ auf das verwendete Zahlensystem, während jedes nachfolgende Zeichenpaar einem Wert für einen der Farbkanäle in der Reihenfolge Rot, Grün und Blau entspricht. Jedes Zeichenpaar umfasst Werte von 0 (in der Form „00“) bis 255 (in der Form „ff“). Das lässt sich einfach überprüfen, indem Sie nach dem Aufruf von init() testweise einfügen:
16.3 Einfärben mit Hilfe einzelner Farbkanäle
209
trace(0 × 00); trace(0xff);
Ausgabe im Nachrichtenfenster: 0 255 Am Präfix erkennt Flash, dass es sich um Hexadezimalwerte handelt und wandelt sie entsprechend um. Lassen Sie das Präfix weg, gibt Flash im ersten Fall ebenfalls 0 aus (es kann sich bei dem in der Klammer übergebenen Argument aus Sicht von AS um keinen Variablennamen handeln, da diese nicht mit einer Zahl beginnen dürfen; daher geht AS einfach von einer Number aus). Im zweiten Fall erfolgt ein undefined, da Flash nach einer Variablen mit dem Namen ff sucht. Nach diesem Test können Sie die beiden trace()-Befehle wieder löschen, da sie nicht weiter benötigt werden. Damit die Farbänderung wirksam wird, müssen wir das ctColor-Objekt mit dem neuen rgb-Wert der colorTransform–Eigenschaft des trMC–Objekts zuweisen. Erst in diesem letzten Schritt erfolgt das konkrete Einfärben, da ja nur unsere Transform– Instanz einen Bezug zum zu verändernden MovieClip enthält. Abschließend erfolgt der Aufruf der init()Funktion, so dass die definierte Änderung auch wirksam werden kann. Wollen wir die Farbwerte eines Objekts auf ein anderes übertragen, können wir einfach die über die colorTransform-Eigenschaft eines MovieClips erfassten Werte verwenden. 7. Ergänzen init():
Sie
die
Funktionsdeklaration
von
recht1.onPress = function() {
gleicheFarbe(this, recht2);
Wenn Sie beim Testen auf recht1 klicken, erhält recht2 die gleiche Farbe zugewiesen. Wir definieren für den ersten MovieClip ein onPress-Ereignis, das die Funktion gleicheFarbe() aufruft und ihr die Argumente this, also die angeklickte Instanz, sowie recht2, das zu färbende Objekt, übergibt. In der Funktion gleicheFarbe() richten wir eine neue ColorTransform–Instanz ein, um die betreffenden Farbeigenschaften aus recht1 zu kopieren. Wir hätten in diesem speziellen Beispiel zwar auch ctColor verwenden können, da dort die einzige Farbänderung definiert wurde, die wir durchgeführt haben. In einer realen Anwendung wäre es jedoch durchaus möglich, dass wir bei recht1 über weitere Operationen Farbeigenschaften ändern, die eben nicht in dieser einen Instanz der ctColor-Klasse erfasst sind. Daher bietet es sich an, auf die colorTransform–Eigenschaft von recht1 zuzugreifen. In trMC erzeugen wir eine neue Transform-Instanz mit einem Bezug auf das zu färbende Objekt, d. h. recht2. Abschließend übertragen wir auf diese Instanz die zuvor gespeicherten Farbeigenschaften.
16.3 Einfärben mit Hilfe einzelner Farbkanäle Zugegebenermaßen erweist es sich als spannender, wenn man gezielt einzelne Farbkanäle ansprechen und nach Belieben ändern kann. Damit verfügt man über erheblich mehr Optionen als bei einer simplen Einfärbung, wie wir sie eben realisiert haben. Die zulässigen Änderungen entsprechen, wie erwähnt, der erweiterten Farb-Option im Einstellungsfenster, die Abbildung 42 zeigt.
};
8. Fügen Sie im Funktions-Block folgende Deklaration ein: function gleicheFarbe(pOrig:MovieClip, pZiel:MovieClip):Void {
var ctAngeklickt:ColorTransform = pOrig.transform.colorTransform; trMC = new Transform(pZiel);
trMC.colorTransform = ctAngeklickt; }
Abbildung 42: Erweiterte Farboptionen einer MovieClipInstanz
210
Kapitel 16 Color-/ColorTransform-Klasse
Jede Instanz der ColorTransform–Klasse verfügt dementsprechend über folgende Eigenschaften:
Die Grafik ändert sich nicht, da wir von einem nicht vorhandenen Farbton dieselbe Farbe subtrahieren.
ctInstanz.redMultiplier (Werte -1 bis 1)
12. Ändern Sie den Anteil von Grün, indem Sie den ersten Slider in der zweiten Reihe bis auf 0 % ziehen.
ctInstanz.redOffset (Werte -255 bis 255) ctInstanz.greenMultiplier ctInstanz. greenOffset
ctInstanz.blueMultiplier ctInstanz. blueOffset
ctInstanz.alphaMultiplier ctInstanz. alphaOffset
Bezogen auf die vorherige Abbildung bezeichnet die Eigenschaft redMultiplier den erstgenannten Wert, der mit 100 % angezeigt wird, und redOffset den zweitgenannten Wert mit der Zahl 0. Die erste Spalte in der Abbildung bzw. die Eigenschaften redMultiplier, greenMultiplier, blueMultiplier sowie alphaMultiplier beziehen sich auf den Anteil der aktuellen Farbe und des gesamten Alphawerts in der vorliegenden Grafik. Diese Werte betragen, wenn wir keinen Einfluss auf die Farbeigenschaften nehmen, immer 100 %. Nehmen wir als Beispiel unser hübsches blaues recht2, bevor wir auf recht1 geklickt haben. Der Hexadezimalwert für das Rechteck lautet 0 × 0099 ff. Das bedeutet, dass der Anteil des Rotkanals 0 beträgt, denn es ist kein Rot in der Farbe enthalten. Dieser Wert wird als Ausgangswert für redMultiplier genommen, entspricht also dort 100 %. Alle Änderungen des Rotkanals werden in Bezug zu diesem Ausgangswert gesetzt. Der Grünkanal kommt dezimal auf 153 bzw. als Hexadezimalwert auf 99. Auch dieser Wert wird als 100 % genommen. Ähnlich verfährt der Blaukanal, der bei dezimal 255 und hexadezimal ff ebenfalls über 100 % des aktuellen Farbanteils verfügt. Um die Farbkanäle zu ändern, können wir diesen spezifischen Farbanteil reduzieren. Das macht beim ersten Kanal mit der Farbe Rot naturgemäß wenig Sinn, denn dort entsprechen unsere 100 % eben einem Rotwert von 0. 9. Markieren Sie auf der Bühne recht2. 10. Öffnen Sie im Eigenschaftsfenster die Option <Erweitert><Einstellungen>. 11. Ziehen Sie den Slider für den Rot-Ton (erste Option in der ersten Reihe) ganz nach unten, so dass im Eingabefeld -100 % angezeigt werden.
Die Grafik färbt sich sukzessive stärker blau ein, weil der zuvor vorhandene Grünanteil reduziert wird, bis er vollständig entfällt. 13. Ändern Sie den Anteil von Blau, indem Sie den ersten Slider in der dritten Reihe bis auf 0 % ziehen. Übrig bleibt ein schwarzes Rechteck, da wir alle Farb informationen verworfen haben. 14. Klicken Sie auf , um keine der Änderungen zu übernehmen. Wie können wir nun den Anteil einer Farbe erhöhen, die wie Rot in unserem Fall überhaupt nicht vorkommt? Dazu bedienen wir uns der Eigenschaften redOffset, greenOffset, blueOffset und alphaOffset. 15. Markieren Sie auf der Bühne recht2. 16. Öffnen Sie im Eigenschaftsfenster die Option <Erweitert><Einstellungen>. 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<=150/ nDif; j++) {
or (var i:Number = 0; i<=100/nDif; f i++) {
bmBitmap.setPixel(j*nDif, i*nDif, 0xffffff);
}
}
Per Mausklick erstellen wir ein aus weißen Punkten bestehendes Raster, das über den rot eingefärbten Bereich der Grafik gelegt wird, wie Abbildung 44 zeigt. Wir richten eine lokale Variable nDif ein, die mit dem Wert 5 initialisiert wird. Sie dient dazu, den Abstand zwischen einzelnen Punkten festzulegen. In der nachfolgenden Schleife erfolgt das konkrete Setzen der Farbwerte für ausgesuchte Punkte. Dabei verwenden wir eine äußere Schleife für die vertikale und eine innere für die horizontale Position der Punkte. Ihre Gesamtzahl ergibt sich vertikal aus der Höhe des roten Rechteckbereichs dividiert durch den Punkteabstand, horizontal aus dessen Breite, ebenfalls dividiert durch den Abstandswert. Wenn wir darüber hinaus auch eine Transparenz definieren wollen, müssen wir mit der setPixel32()-Methode arbeiten.
var nXPos:Number, nYPos:Number;
or (var i:Number = 0; i<=nAnzahl; f i++) {
var nAnzahl:Number = 600;
nXPos = Math.round(Math.random()*150)+50; nYPos = Math.round(Math.random()*100);
bmBitmap.setPixel32(nXPos, nYPos, 0xaaffffff); }
Unser Code erstellt nun ein zufälliges Muster aus halbtransparenten weißen Punkten innerhalb der rot eingefärbten Fläche, wie auf Abbildung 45 zu sehen. Wir legen zwei lokale Variablen fest, um darin die zufällig ermittelten Positionswerte zu speichern. In nAnzahl definieren wir die maximale Anzahl der verändert dargestellten Punkte. Da wir aus Gründen der Einfachheit nicht kontrollieren, ob manche Punkte mehrfach eingefärbt werden, kann die tatsächliche Anzahl der sichtbaren Punkte davon nach unten abweichen, was aber für unser Beispiel unerheblich ist. Innerhalb der Schleife ermitteln wir entsprechend dieser Anzahl eine zufällige x-Position sowie eine zufällige y-Position. Der Pixel, der sich an den betreffenden Werten befindet, wird dann weiß eingefärbt und halb
18.4 Methoden der BitmapData-Klasse
transparent dargestellt. Dieses Vorgehen erinnert ein bisschen an die weiter unten vorgestellte noise()Methode. Der einzige wirkliche Nachteil von setPixel() bzw. setPixel32() besteht in der außerordentlich großen Anzahl an Rechenoperationen, die notwendig werden, wenn man eine größere Grafik Pixel für Pixel bearbeiten möchte. Wollten wir beispielsweise in einem Bild der Größe 800 × 600 pixelweise Farbänderungen vornehmen, ergäben sich rein rechnerisch 800 × 600 bzw. 480.000 Zugriffe – ein unsinnig hoher Wert, bei dem Flash konsequenterweise die Gefolgschaft verweigert. Um ein angemessenes Arbeiten zu gewährleisten, müssen wir uns daher auf ein sinnvolles Maß beschränken und überlegen, ob nicht durch die Verwendung anderer oder die Kombination verschiedener Methoden ein vergleichbares Ergebnis erzielt werden kann, ohne dass sich unvertretbare Rechenzeiten ergeben. Sollte dennoch kein Weg an einer pixelweisen Bearbeitung vorbeiführen, bleibt nur noch, die Rechenoperationen per setInterval() auf mehrere Durchgänge zu verlagern anstatt sie in einem Rutsch in einer Schleife zu verarbeiten. Den idealen Wert für das Intervall muss man experimentell ermitteln.
229
2. Importieren Sie in die Bibliothek eine beliebige Grafik. Wir verwenden hier beispielhaft eine Grafik aus dem Workshop Galerie (z. B. affe0.jpg). 3. Weisen Sie der Grafik in der Bibliothek den Verknüpfungsnamen affe zu. 4. Weisen Sie actions folgendes Bildskript zu: //------------ import –------------- import flash.display.BitmapData;
//–------------ vars –-------------- var mBild:MovieClip;
var bmBild:BitmapData;
v ar sBildVerknuepfung:String = „affe“;
//----------- functions –----------- function init() {
f or (var i:Number = 0; i<=nAnzahl; i++) { bmBild = BitmapData. loadBitmap(sBildVerknuepfung);
mBild = this. createEmptyMovieClip(„behBild“, this.getNextHighestDepth());
mBild.attachBitmap(bmBild, this. getNextHighestDepth()); }
18.4.4 LoadBitmap(), draw(), copyPixels(), clone() Neben dem direkten Zeichnen in das BitmapDataObjekt kann man auch auf bestehende Bitmapgrafiken zugreifen, um sie teilweise oder vollständig zu übernehmen. Eine Variante besteht in der Verwendung der loadBitmap()-Methode. Analog zur loadMovie()Methode der MovieClip-Klasse erweckt diese Bezeichnung auf den ersten Blick den Eindruck, man könne direkt in ein BitmapData-Objekt eine externe Grafik laden. Tatsächlich verbirgt sich dahinter jedoch lediglich die Möglichkeit, zur Laufzeit aus einer Bibliothek eine Bitmap auf die Bühne einzufügen. Damit gleicht diese Methode der attachMovie()-Methode der MovieClip-Klasse. Da es sich um eine statische Methode handelt, weicht ihre Verwendung von den übrigen hier vorgestellten Methoden ab. 1. Erstellen Sie eine Standarddatei.
//–––---------- start –------------- init();
In der linken oberen Ecke taucht das in die Bibliothek importierte Bild auf. Wir importieren die benötigte Klasse – alternativ: das gesamte Package mit import flash.display.*; – und richten Variablen ein zur Referenzierung der BitmapData-Instanz, des Behälters für diese Instanz und für den Verknüpfungsbezeichner der Grafik. Anders als bisher richten wir nicht explizit ein neues BitmapData-Objekt mit gewissen Ausmaßen ein, sondern laden unmittelbar per loadBitmap()Methode unser Bild, wobei der Verknüpfungsbezeichner als Argument übergeben wird. Die übrigen Zeilen entsprechen wieder der gewohnten Vorgehensweise: Einrichten eines MovieClips als Behälter und Zuweisen der BitmapData-Instanz. Ebenfalls etwas missverständlich ist die Bezeichnung der draw()-Methode, hinter der kein Zeichnungsprogramm steckt, wie man es in Anlehnung an die Zeichnungsmethoden der MovieClip-Klasse an-
230
nehmen könnte. Statt dessen dient draw() dazu, aus einer Vorlage, bei der es sich um eine Bitmap oder einen MovieClip handeln kann, einen beliebigen Ausschnitt zu kopieren. 5. Erweitern Sie den Variablen-Block (Fettdruck): v ar mBild:MovieClip, mKopie:MovieClip;
v ar bmBild:BitmapData, bmKopie:BitmapData;
6. Erweitern Sie im Funktions-Block die Deklaration von init() unmittelbar vor der schließenden Klammer: mBild.onPress = kopieren;
7. Fügen Sie in den Funktions-Block außerhalb der init()-Funktion folgende Deklaration ein: function kopieren() {
bmKopie = new BitmapData(bmBild. width, bmBild.height);
mKopie = this._parent.createEmpty MovieClip(„behBildKopie“, this._parent.getNextHighestDepth()); mKopie._x = this._width + 5; bmKopie.draw(bmBild);
mKopie.attachBitmap(bmKopie, mKopie.getNextHighestDepth()); }
Wenn Sie auf das linke Bild klicken, wird rechts daneben eine exakte Kopie eingeblendet. Um überhaupt eine Kopie erzeugen zu können, benötigen wir Variablen zum Referenzieren einer neuen BitmapData-Instanz und dessen Behälters, die im Variablen-Block eingerichtet werden. In init() weisen wir dem Behälter der aus der Bibliothek eingefügten Grafik ein onPress-Ereignis zu, das in der Funktion kopieren() definiert wird. Dort instanziieren wir die BitmapData-Klasse und übergeben als Argumente die Breite und Höhe der Grafik, die wir kopieren wollen. Als Behälter erstellen wir einen neuen leeren MovieClip, dessen Position der Breite der Grafik zuzüglich eines kleinen Abstands von 5 entspricht. Dadurch liegt der MovieClip rechts neben der Grafik. Würde sich diese nicht im Koordinatenursprung befinden, sondern vom linken Rand entfernt, müssten wir zur Position unseres neuen Behälters zusätzlich die horizontale Position der Grafik hinzu addieren. Die Me-
Kapitel 18 BitmapData- und Filter-Klasse
thode draw() wird für unsere Bitmap-Kopie aufgerufen und erhält als Argument lediglich den Namen des Objektes, das kopiert werden soll, hier also die BitmapData-Instanz mit der aus der Bibliothek eingefügten Grafik. Im letzten Schritt verknüpfen wir die Bitmap-Kopie mit dem MovieClip-Behälter, so dass eine Grafik zu sehen ist. Wenn wir wie im vorliegenden Beispiel lediglich einen Instanznamen bzw. eine Variable mit einer Referenz auf ein Objekt übergeben, wird das ganze Objekt kopiert, vorausgesetzt, die zur Aufnahme der Kopie erzeugte BitmapData-Instanz ist mindestens so groß wie das zu kopierende Objekt. Andernfalls wird einfach der überstehende Bereich abgeschnitten. Anstatt der Bitmap können wir auch den übergeordneten Behälter, also den MovieClip mBild, kopieren. 8. Ändern Sie in der Deklaration von kopieren() den Aufruf der draw()-Methode (Fettdruck): bmKopie.draw(this);
Optisch ergibt sich kein Unterschied beim Testen. Anstelle der BitmapData-Instanz übergeben wir nun das angeklickte Objekt, als der übergeordnete MovieClip-Behälter als Argument zum Zeichnen. Das mag auf den ersten Blick nicht sonderlich aufregend sein, hat jedoch wichtige Konsequenzen: Auf diese Weise sind wir prinzipiell in der Lage, einen Screen shot unserer kompletten Applikation zu erzeugen, denn schließlich stellt die Hauptzeitleiste nur einen MovieClip dar. Wird sie mit draw() gezeichnet, liegt in der dafür verwendeten BitmapData-Instanz eine exakte Kopie vor. Da das mit jedem MovieClip funktioniert, können wir eben auch jedes beliebige Element einer Anwendung auf diese Weise kopieren, solange es sich in einem MovieClip befindet. Gleichzeitig bietet uns diese Methode eine Möglichkeit, externe Grafiken zu laden, indem wir sie per load Movie() in einen MovieClip einfügen und anschließend per draw() kopieren. Nicht immer ist es erwünscht, eine vollständige Kopie zu erstellen. In dem Fall sieht der Aufruf der draw()-Methode etwas komplexer aus. 9. Übergeben Sie in der Deklaration von kopieren() beim Aufruf der draw()-Methode wieder wie zuvor die zu kopierende BitmapData-Instanz (Fettdruck): bmKopie.draw(bmBild);
18.4 Methoden der BitmapData-Klasse
10. Erweitern Sie den Import-Block:
import flash.geom.*;
11. Erweitern Sie den Variablen-Block:
var rKopieAusschnitt:Rectangle;
var mxOriginal:Matrix;
var cOriginal:ColorTransform; var sBlendOriginal:String;
12. Erweitern Sie im Funktions-Block die Deklaration von kopieren() unmittelbar am Anfang:
mxOriginal = new Matrix();
BlendOriginal = String(mBild.blends Mode);
cOriginal = new ColorTransform();
KopieAusschnitt = new Rectangle(0, r 0, bmBild.width/2, bmBild.height/2);
13. Ändern Sie in derselben Deklaration den Aufruf von kopieren() (Fettdruck):
mKopie.draw(bmBild, mxOriginal, cOb riginal, sBlendOriginal, rKopieAusschnitt);
Die Änderungen führen dazu, dass wir nun keine vollständige Kopie mehr erhalten, sondern nur noch das linke obere Viertel wie in Abbildung 46 gezeigt. Dieses in der Tat etwas umständliche Vorgehen ist deshalb erforderlich, weil die draw()-Methode insgesamt 6 Parameter erlaubt, von denen alle bis auf den ersten optional sind, und wir ausgerechnet den 5. Parameter benötigen. In dem Fall müssen die übrigen in irgendeiner Form definiert werden, ansonsten versagt die Methode klammheimlich den Dienst. Im Einzelnen handelt es sich um folgende Para meter:
231
• Quellbild, also das zu kopierende Bild, wobei es sich um eine BitmapData-Instanz oder einen MovieClip handelt; • Gewünschte Änderungen (Größe, Position, Verzerrung) in Form einer Matrix; • Farbänderungen als ColorTransformMatrix; • Ebenenmodus als String; • Ausschnitt als Rectangle; • Glättung als Boolean.
Da sowohl die Matrix- wie die ColorTransformMatrix Teil des geom-Packages sind, wird dieses am Beginn des Skripts importiert. Danach richten wir vier Variablen für die 4 zusätzlich zu übergebenden Argumente ein. In der Funktion kopieren() werden sie mit den benötigten Werten initialisiert. Allerdings ist zu bedenken, dass wir ja nur in Bezug auf den zu kopierenden Bereich eine Änderung durchführen wollen, an anderer Stelle dagegen nicht. Daher erstellen wir zunächst eine Identitätsmatrix, indem wir die benötigte Matrix-Klasse instanziieren, dabei jedoch keine Argumente übergeben. Faktisch generieren wir damit ein Objekt, das keinerlei Änderung bewirkt. Das gleiche erreichen wir, indem in sBlendOriginal derjenige Ebeneneffekt kopiert wird, über den automatisch der Behälter der Originalgrafik verfügt, nämlich „normal“. Lediglich als Rechteck legen wir einen neuen Bereich fest, der in der linken oberen Ecke beginnt und halb so breit sowie halb so hoch wie die Originalgrafik ist. Den letzten Parameter hinsichtlich einer möglichen Glättung können wir getrost ignorieren, da wir ihn nicht benötigen. Die so ermittelten Werte werden abschließend als Argumente beim Aufruf der draw()-Methode übergeben und führen dazu, dass wir ein Ausschnitt als Kopie erhalten. In unserem vorliegenden einfachen Beispiel sieht der Aufruf von draw() nach Overkill aus, was aber
Abbildung 46: Ausschnittweises Kopieren einer Grafik
232
Kapitel 18 BitmapData- und Filter-Klasse
schlicht daran liegt, dass wir nur eine simple Änderung erreichen wollten. Erst bei größeren bzw. komplexeren Änderungen zeigt sich der Vorteil der Parameter. Denn die draw()-Methode hat den Nachteil, in der einfachen Form, in der wir sie anfangs verwendeten, keinerlei Änderungen an der Kopie zuzulassen bzw. keinerlei Änderungen der Originalgrafik beim Kopieren zu übernehmen. Wenn wir also beispielsweise bei Mausklick auf den Behälter der Originalgrafik eine Skalierung durchführen und den MovieClip anschließend kopieren würden, erhielten wir die Originalgrafik unskaliert. Würden wir dagegen die Skalierung über eine Transformationsmatrix erfassen und auf die Kopie übertragen, sähe diese wie das Original aus. Ähnlich können wir verfahren, wenn die Kopie im Gegensatz zum Original beim Kopieren skaliert werden soll, ein Vorgang, den wir uns nun beispielhaft anschauen.
Nun entsprechen die Höhe der BitmapData-Instanz und des kopierten Ausschnitts einander.
14. Fügen Sie im Funktions-Block in der Deklaration von kopieren() unmittelbar nach der Initialisierung von mxOriginal ein:
import flash.display.BitmapData;
mxOriginal.a = 2;
Wenn Sie testen, sehen Sie in der Kopie einen langgezogenen Ausschnitt aus dem Original. 15. Ändern Sie im Funktions-Block in der Deklaration von kopieren() die Initialisierung von rKopieAusschnitt (Fettdruck):
KopieAusschnitt = new Rectangle(0, r 0, bmBild.width, bmBild.height/2);
Bezüglich des Motivs sehen wir nun dasselbe wie vor Anwendung der Transformationsmatrix, aber der Ausschnitt ist genau doppelt so breit, das Motiv wird also horizontal skaliert. Wir verändern in der Matrix schlicht den Wert, der sich auf die x-Position eines Punktes beim Skalieren des betreffenden Objekts ändert. Da der Wert verdoppelt wird, vergrößern wir faktisch um 100 %, was zu einer horizontalen Skalierung führt. Abschließend bleibt noch eine kleine Änderung, denn wir erzeugen für unsere Kopie nach wie vor eine Bitmap von der gleichen Größe wie die zu kopierende Grafik. 16. Ändern Sie im Funktions-Block in der Deklaration von kopieren() die Instanziierung von bmKopie (Fettdruck):
mKopie = new BitmapData(bmBild. b width, bmBild.height/2);
18.4.5 Noise(), perlinNoise() Eher ungewöhnliche Ergebnisse können wir mit der noise() und insbesondere der perlinNoise()Methode erzielen. Mit ihrer Hilfe lassen sich nämlich Störungen in ein Bild hinein rechnen, nahezu genau so, wie wir es aus Photoshop und anderen Bildbearbeitungsprogrammen gewohnt sind. 1. Erstellen Sie eine Standarddatei. 2. Weisen Sie actions folgendes Bildskript zu: //------------- import –------------ //––----------- vars –-------------- var bmRechteck:BitmapData; var mRechteck:MovieClip;
//----------- functions –----------- function init() {
bmRechteck = new BitmapData(150, 150, false, 0x00FF0000); mRechteck = this. createEmptyMovieClip(„mc“, this. getNextHighestDepth());
mRechteck.attachBitmap(bmRechteck, this.getNextHighestDepth()); mRechteck._x = Stage.width/2mRechteck._width/2;
mRechteck.onPress = function() { bmRechteck.noise(100, 0, 255, 1|2|4, false); }; }
//––----------- start –------------- init();
Beim Testen erscheint am oberen Bühnenrand horizontal mittig ein rotfarbenes Rechteck, das bei Mausklick eine beliebige Einfärbung der einzelnen Pixel anzeigt. Wir importieren die benötigte BitmapData-Klasse und richten je eine Variable zur Referenzierung der betreffenden Instanz sowie des übergeordneten Movie
18.4 Methoden der BitmapData-Klasse
Clip-Behälters ein. In der Funktion init() wird eine 150 × 150 Pixel große rotfarbene Bitmap eingerichtet, die wir nach Erstellen eines leeren MovieClips diesem Clip zuweisen, um die Grafik sehen zu können. Der MovieClip wird in der Bühnenmitte um seine halbe Breite nach links verschoben. Da der Registrierungspunkt des Clips in dessen linker oberer Ecke liegt, wird die Bitmap auf diese Weise horizontal mittig positioniert. Anschließend erhält der MovieClip ein onPress-Ereignis, in dem die noise()-Methode mit fünf Parametern aufgerufen wird. Diese üben folgende Funktion aus:
• 100, eine Ganzzahl für den Pseudo-Zufallsgenera-
tor von Flash. Bei derselben Zahl erhalten wir immer dasselbe Ergebnis; • 0, eine Ganzzahl, die den niedrigsten zulässigen Wert pro Farbkanal definiert; • 255, eine Ganzzahl, die den höchsten zulässigen Wert pro Farbkanal definiert; • 1|2|4, die betroffenen Kanäle in der für Nichtprogrammierer gewöhnungsbedürftigen Form einer Verknüpfung per logischem ODER-Operator 1|2|4|8, wobei in der Reihenfolge des Auftretens die Kanäle Rot, Grün, Blau und Alpha gemeint sind. Mindestens ein Kanal muss angegeben werden; • false, eine Boolean, die angibt, ob ein Graustufenbild (true) oder ein Farbbild (false) erzeugt werden soll. In unserem konkreten Beispiel verwenden wir als Startwert die Zahl 100. Diese Zahl wurde völlig willkürlich gewählt und Sie können gerne mit jedem beliebigen anderen Wert arbeiten. Als Minimum für den Farbkanal übergeben wir 0 und als Maximum 255, so dass alle Werte eines 8 Bit-Kanals zulässig sind, also jeder beliebige Farbwert des RGB-Spektrums möglich ist. Dabei werden die drei Farbkanäle, nicht jedoch der Alpha-Kanal verwendet. Schließlich legen wir fest, dass kein Graustufenbild erzeugt werden soll. Wir können die Störung bei Bedarf natürlich auch gezielt auf einen einzelnen Farbkanal oder den Alphakanal anwenden. 3. Ändern Sie den Aufruf der noise()-Methode (Fettdruck): b mRechteck.noise(100, 0, 255, 1, false);
233
Sie erhalten nun nur noch Störungen in verschiedenen Rottönen, da alle anderen Kanäle ausgeschaltet wurden. Wenn Sie den zulässigen Wertebereich anpassen, können Sie zusätzlich beispielsweise ganz dunkle und ganz helle Töne ausschließen. 4. Ändern Sie den Aufruf der noise()-Methode (Fettdruck): b mRechteck.noise(100, 100, 156, 1, false);
Nun steht nur noch im mittleren Spektrum ein Wertebereich zwischen 100 und 156 zur Verfügung, so dass weder ein ganz dunkler noch ein ganz heller Rotton auftreten kann. Wenn Sie mehrfach auf das Rechteck klicken, werden Sie feststellen, dass nur beim erstmaligen onPress eine Änderung eintritt. Alle nachfolgenden Aktionen bleiben ohne visuell wahrnehmbare Auswirkungen. Zwar berechnet Flash die Zufallswerte jedes Mal neu, doch sind sie miteinander identisch, da der Startwert unverändert bleibt. 5. Ändern Sie den Aufruf der noise()-Methode (Fettdruck): b mRechteck.noise(Math.round(Math.random()*100), 0, 255, 1|2|4, false);
Jeder Mausklick erzeugt nun eine neue Zufallsverteilung der Farbwerte, wobei wir wieder wie zu Anfang auf alle Farben sowie alle möglichen Werte zugreifen. Um daraus ein Graustufenbild zu generieren, reicht eine kleine Änderung aus. 6. Ändern Sie den Aufruf der noise()-Methode (Fettdruck): b mRechteck.noise(Math.round(Math.random()*100), 0, 255, 1|2|4, true);
Dadurch, dass wir an letzter Stelle ein true übergeben, werden die Farbinformationen verworfen und wir erhalten ein Graustufenbild. Komplexer, aber insgesamt vielseitiger verwendbar als die vorgestellte noise()-Methode ist die perlinNoise()-Methode, die Ken Perlins berühmten Algorithmus anwendet. Dahinter verbirgt sich die Erzeugung einer in die Fläche wirkenden, je nach verwendeten Argumenten fast schon organisch anmutenden Störung, wie wir sie mit dem Wolken-Filter in Photoshop erzeugen können. Eine derartige Methode ist elementar für die Generierung von Texturen, die in
234
Kapitel 18 BitmapData- und Filter-Klasse
zahlreichen Anwendungen und insbesondere in Spielen zum Einsatz kommen. Damit lassen sich unmittelbar in Flash Effekte wie Wolken, Nebel, Feuer, aber auch Oberflächenstrukturen wie Holz, Marmor, Stein etc. erzeugen. Wir können den bisherigen Code verwenden, da nur wenige Anpassungen notwendig sind. 7. Ändern Sie das Skript (Fettdruck): //------------ import –------------- import flash.display.BitmapData;
//–----------- vars –--------------- var bmRechteck:BitmapData; var mRechteck:MovieClip; var nStart:Number = 100;
Abbildung 47: Erzeugung einer Störung mit Hilfe der perlinNoise()-Methode
var nOktaven:Number = 25;
//----------- functions –----------- function init() {
bmRechteck = new BitmapData(150, 150, false, 0x00FF0000); mRechteck = this. createEmptyMovieClip(„mc“, this. getNextHighestDepth());
mRechteck.attachBitmap(bmRechteck, this.getNextHighestDepth()); mRechteck._x = Stage.width/2mRechteck._width/2;
mRechteck.onPress = function() {
bmRechteck.perlinNoise(150, 150, nOktaven, nStart, true, true, 1 | 2 | 4, false, null); }; }
//----------- start –--------------- init();
Erneut erzeugen wir ein rotes, horizontal positioniertes Rechteck, das diesmal jedoch bei Mausklick eine wolkenartige Struktur anzeigt, wie in Abbildung 47 zu sehen. Im Variablen-Block ergänzen wir zwei Variablen, um einen Startwert für den Zufallsgenerator von Flash und einen Wert für die Anzahl der Störungsfunktionen zu initialisieren. Beim Mausklick wird die perlinNoise()-Methode mit folgenden Argumenten aufgerufen:
• 150, die Störungshäufigkeit in horizontaler Rich-
tung und • 150, die Störungshäufigkeit in vertikaler Richtung. Wenn die Werte nicht miteinander identisch sind, wird, vereinfacht formuliert, die Störung (scheinbar) in die Richtung mit dem größeren Wert skaliert; • nOktaven, Anzahl der verwendeten Störungsfunktionen. Je höher der Wert, desto detaillierter fällt die resultierende Störung aus, die benötigte Rechenleistung steigt jedoch ebenfalls an; • nStart, Startwert oder seed (s. noise()-Methode), um Zufallswerte für die Störung zu generieren; • true, Glättung der Bildränder, um (theoretisch) einen nahtlosen Übergang zu ermöglichen, der bei der Verwendung der Störung als kachelbare Textur notwendig wäre; • true, Erzeugung einer fraktalen Störung mit weichen Übergängen zwischen den Farben; • 1|2|4, betroffene Farbkanäle (optional); • false, Graustufenbild (optional); • null, Array mit Punkten, um die Übergänge zwischen den einzelnen Oktaven zu definieren (optional). Dieses Argument wird i. d. R. nur dann benötigt, wenn eine Animation mit Hilfe der perlinNoise()-Methode erstellt werden soll (Flammen, Wolken, Wellen etc). Das letzte Argument können Sie auch schlicht weg lassen anstatt null zu übergeben. In beiden Fällen erhalten wir dasselbe Ergebnis.
18.6 Filter
Da wie bei der noise()-Methode aufgrund eines übergebenen Startwerts eine Zufallsverteilung errechnet wird, erhalten wir jeweils dann neue Störungen, wenn der Startwert variiert. 8. Ändern Sie den Aufruf der perlinNoise()-Methode (Fettdruck): b mRechteck.perlinNoise(150, 150, nOktaven, Math.round(Math. random()*nStart), true, true, 1 | 2 | 4, false, null);
Jeder Mausklick erzeugt nun eine andere Störung. Wir verwenden den in nStart initialisierten Wert, um eine Zufallszahl zu erzeugen, die zwischen 0 und 100 liegt. Damit variieren wir den von Flash verwendeten Ausgangswert, um die Störung zu berechnen und erhalten so jeweils ein anderes grafisches Ergebnis. Eine andere Art von Änderung erzielen wir, wenn für nOktaven verschiedene Werte vorliegen. 9. Ändern Sie im Variablen-Block die Initialisierung von nOktaven (Fettdruck): var nOktaven:Number = 2;
Als Ergebnis erhalten Sie bei Mausklick ein scheinbar stark weichgezeichnete Störung, die dadurch zustande kommt, dass wir die Anzahl der Störungsfunktionen auf einen sehr niedrigen Wert reduziert haben. Die Form der Störung lässt sich zusätzlich durch die beiden ersten beim Aufruf der Methode übergebenen Argumente festlegen, wobei Sie nicht zwangsläufig wie in unserem Beispiel einen Wert wählen müssen, der den Ausmaßen der betroffenen BitmapData-Instanz entspricht. 10. Initialisieren Sie nOktaven wieder mit dem ursprünglichen Wert (Fettdruck):
var nOktaven:Number = 25;
11. Ändern Sie den Aufruf der perlinNoise()-Methode (Fettdruck):
mRechteck.perlinNoise(15, b 15, nOktaven, Math.round(Math. random()*nStart), true, true, 1 | 2 | 4, false, null);
Die resultierende Störung wirkt kleinflächiger und detaillierter. In einem mit diesen Werten erzeugten Graustufenbild entsteht der Eindruck vieler kleiner Wolken.
235
12. Ändern Sie den Aufruf der perlinNoise()-Methode (Fettdruck):
mRechteck.perlinNoise(150, b 15, nOktaven, Math.round(Math. random()*nStart), true, true, 1 | 2 | 4, false, null);
Da der für die horizontale Richtung übergebene Wert denjenigen für die vertikale Richtung um das 10-fache übersteigt, wird die Störung sehr stark in die Länge gezogen, wodurch wir eine Art horizontaler Verlauf von Linien verschiedener Farbe und Stärke erhalten.
18.5 Optimierung mit Hilfe von BitmapData Da eine Bitmap-Grafik im Gegensatz zur VektorGrafik lediglich aus Daten einer Tabelle erstellt wird, mithin eine prozessorintensive Berechnung entfällt, besteht die Möglichkeit, mit ihrer Hilfe den FlashPlayer beim Rendern zu entlasten. Obwohl es sich hier um eine nur für Fortgeschrittene interessante Technik handelt, sei abschließend ein Hinweis darauf erlaubt. An anderer Stelle wird dieses Thema ausführlicher behandelt. Einer der ersten, der das in dieser Technik steckende Potential bereits mit Flash 8 genial ausschöpfte, war Grant Skinner. Er programmierte eine Art Pflanzengenerator, der trotz der für den damals recht schwachbrüstigen Flash-Player zehrenden Prozessorlast eine exzellente Bildwiederholrate und damit eine flüssige Animation erzielen konnte. Abbildung 48 zeigt einen Ausschnitt aus dieser Animation. Näheres dazu finden Sie auf der Blog-Seite von Skinner sowie auf der Website von Adobe jeweils unter dem Stichwort „Vine-G“ sowie „Varicose“.
18.6 Filter Nicht zuletzt mit den Filtern hält die große Welt der Photoshop-Effekte Einzug in Flash. Sie erleichtern den Workflow, da man nun unmittelbar in Flash jene Effekte realisieren kann, die ansonsten eine externe Bildbearbeitung voraussetzten. Da die Filter geskriptet werden, sind die resultierenden Dateien kleiner als
236
Kapitel 18 BitmapData- und Filter-Klasse
Grundlagen des Skripting befassen (aber es kann ja nicht schaden, mal den Mund wässrig zu machen).
18.6.1 Bevel-Filter Der Bevel-Filter erzeugt einen Geschliffen-Effekt, wie er beispielsweise bei Schaltflächen verwendet wird, um ihnen ein leicht dreidimensionales Aussehen zu verleihen.
Abbildung 48: Skinners „Pflanzengenerator“
wenn sie von vorneherein Bestandteil der Grafiken wären, und der Anwender kann zur Laufzeit der Applikation mit ihnen interagieren. Von den 9 angebotenen Filtern lassen sich 7 per Eigenschaftsfenster in der Autorenumgebung und 2 zusätzliche nur per ActionScript steuern. Es handelt sich dabei um:
• Bevel • Blur • DropShadow • Glow • GradientBevel • GradientGlow • ColorMatrix (nur per AS) • Convolution • DisplacementMap (nur per AS) Da die Filter im filters-Package enthalten sind, muss dieses bzw. die einzelne benötigte Klasse erst am Anfang importiert werden, bevor man die gewünschten Methoden verwenden kann. Sie sind insgesamt recht mächtig und ermöglichen insbesondere in der Kombination miteinander bzw. mit der BitmapData-Klasse beeindruckende Effekte. Um nur ein einziges Highlight zu nennen: Basierend auf dem DisplacementMap-Filter kann man in Flash Kai Krauses unvergleichliches Goo nachbasteln, ein Programm, das die beliebige Verzerrung von Bitmap-Grafiken ermöglichte. Leider müssen solche und weitere Effekte einem anderen Buch vorbehalten bleiben, da wir uns hier nur mit den
1. Erstellen Sie eine Standarddatei. 2. Ändern Sie die Hintergrundfarbe auf ein lebensechtes Grau (0xcccccc). 3. Erstellen Sie auf objects an beliebiger Position ein Rechteck mit leicht abgerundeten Ecken (80 × 25, Füllung 0x0099ff, Rand 1 Pixel und schwarz). 4. Wandeln Sie die Grafik in einen MovieClip um (Bibliotheksname mcSchalt, Instanzname schalt). 5. Weisen Sie actions folgendes Bildskript zu: //-------------- import –----------- import flash.filters.*;
//–––----------- vars –------------- var mObjekt:MovieClip = schalt; var fFilter;
//------------- functions –----------
function init(pFilter:Function):Void { mObjekt._x = Stage.width/2; mObjekt._y = 200; pFilter();
mObjekt.filters = [fFilter]; }
function bevel():Void {
fFilter = new BevelFilter(3, 45, 0x00ccff, 0.7, 0x0033ff, 0.7, 3, 2, 3, 3, „inner“, false); trace(„Bevel-Filter“); }
//------------- start –------------- init(bevel);
Wenn Sie testen, taucht in der Bühnenmitte eine Schaltfläche mit einem leichten 3D-Effekt auf, wie in Abbildung 49 zu sehen.
18.6 Filter
237
Abbildung 49: Bevel-Filter
Abbildung 50: Bevel-Filter mit maximaler Stärke
Wir importieren das filters-Package mit den benötigten Filtern, legen eine Variable zur Referenzierung des konkret verwendeten Filters sowie zur Aufnahme desjenigen Objekts, dem wir den betreffenden Filter zuweisen werden, an. In der Position init() positionieren wir unseren MovieClip horizontal mittig und vertikal auf einen willkürlich gewählten Wert. Um einfach alle in den nachfolgenden Abschnitten eingerichtete Filter verwenden zu können, weisen wir init() den Parameter pFilter zu, bei dem es sich jeweils um eine Funktion handeln wird, die einen konkreten Filter einrichtet. Dieser Parameter wird als Funktion aufgerufen und der infolgedessen definierte Filter unserem MovieClip zugewiesen. In der Funktion bevel() definieren wir den gewünschten Filter und lassen uns eine entsprechende Information im Nachrichtenfenster ausgeben. Die init()-Funktion rufen wir abschließend mit dem Filter als Argument auf. Selbst wenn Sie sich in einem weniger fortgeschrittenen Alter wie der Autor befinden, wird die enorme Anzahl an Argumenten, die man dem Konstruktor des Bevel-Filters mit auf den Weg gibt, Ihre mnemotechnischen Fähigkeiten bis an die Grenzen belasten. Glücklicherweise bietet sich eine andere Schreibweise an, die zwar nach mehr Tipparbeit verlangt, aber erheblich übersichtlicher ist. Die benötigten Argumente lassen sich nämlich direkt als Eigenschaften einer Filter-Instanz festlegen.
fFilter.blurX = 3;
6. Ändern Sie in der Deklaration von bevel() die Festlegung der Filtereigenschaften ab der öffnenden Klammer wie folgt: fFilter = new BevelFilter();
fFilter.blurY = 3;
fFilter.strength = 2; fFilter.quality = 3;
fFilter.type = „inner“;
fFilter.knockout = false;
Das Ergebnis entspricht optisch dem vorhergehenden Beispiel. Allerdings haben wir nun beim Skripten eher eine Vorstellung davon, was ein konkreter Wert bedeutet, den wir für den Filter festlegen. Da alle Parameter optional sind, können Sie beliebige Eigenschaften, die Sie nicht explizit setzen wollen, einfach weg lassen. Flash übernimmt dann festgelegte Standardwerte. Die einzelnen Eigenschaften und die zulässigen Datentypen sind selbstredend, so dass wir sie nicht weiter erläutern wollen. Als Hinweis mag genügen, dass die Werte für die Deckkraft von 0.0 bis 1.0 reichen, wie wir es bereits bei in früheren Kapiteln kennen gelernt haben. Als type sind zulässig „inner“, „outer“ und „full“ (identisch mit „inner“ und „outer“ des Filters im Eigenschaftsfenster). Für strength können Sie Werte von 0 bis 255 vergeben, wobei mit der Stärke natürlich die Ausprägung des Effekts steigt. Wenn Sie im vorliegenden Beispiel dieser Eigenschaft den höchstmöglichen Wert zuweisen, erhält das Rechteck eine comicmäßige Anmutung, wie in Abbildung 50 zu erkennen. In den meisten Fällen – das gilt übrigens auch für die anderen Filter, soweit sie über dieselbe Eigenschaft verfügen – machen sehr niedrige Werte eher Sinn. Der Standardwert lautet daher auch 1.
fFilter.distance = 3; fFilter.angle = 45;
fFilter.highlightColor = 0x00ccff; fFilter.highlightAlpha = 0.7;
fFilter.shadowColor = 0x0033ff; fFilter.shadowAlpha = 0.7;
18.6.2 Blur-Filter Der Weichzeichnungs-Filter dürfte vor allem als Gausscher Blur ein Begriff sein, stellt er doch in Photoshop ein wichtiges Element zahlreicher Effekte dar. Das
238
Flash-Pendant entspricht allerdings nicht wirklich diesem berühmten Namensvetter; er lässt sich jedoch über den Convolution-Filter mit einer 9 × 9 Matrix nachbilden. Dessen ungeachtet bringt der Blur-Filter in Flash in den meisten Fällen gute Ergebnisse. 7. Erweitern Sie den Funktions-Block um folgende Deklaration: function blur(){
fFilter = new BlurFilter(2,2,3); trace(„Blur-Filter“); }
8. Ändern Sie den Aufruf von init() (Fettdruck): init(blur);
Unser Rechteck wird leicht weich gezeichnet. Der Blur-Filter arbeitet recht unspektakulär mit nur drei bescheidenen Argumenten, deren erste beiden sich auf die Weichzeichnung in horizontaler und vertikaler Richtung beziehen, während der letzte die Qualität beim rendern betrifft. Wollen Sie statt der Kurzform mit den betreffenden Eigenschaften arbeiten, könnten Sie diese so ansprechen wie im Beispiel des BevelFilters: fFilter.blurX, fFilter.blurY und fFilter.quality. Das gilt für alle anderen Filter, soweit sie über eine Ausdehnung im Raum und eine Darstellungsqualität verfügen. Angesichts der nur wenigen Argumente, deren Bedeutung sich sogar der Autor auswendig merken kann, wollen wir auf eine Einrichtung des Filters anhand der explizit genannten Eigenschaften verzichten.
Kapitel 18 BitmapData- und Filter-Klasse
10. Ändern Sie den Aufruf von init() (Fettdruck):
init(schatten);
Ein herrlicher Schlagschatten ziert Ihr Rechteck. Da wir es hier wieder mit einer Vielzahl an Argumenten zu tun haben, wollen wir bei der Einrichtung die jeweiligen Eigenschaften explizit nennen. 11. Ändern Sie in der Deklaration von schatten() die Festlegung der Filtereigenschaften ab der öffnenden Klammer wie folgt:
fFilter = new DropShadowFilter();
fFilter.angle = 45;
fFilter.distance = 3;
fFilter.color = 0x666666; fFilter.alpha = 0.8; fFilter.blurX = 3; fFilter.blurY = 3;
fFilter.strength = 2; fFilter.quality = 3;
fFilter.inner = false;
fFilter.knockout = false;
fFilter.hideObject = false; trace(„DropShadow-Filter“);
Wenn Sie die Werte für distance und angle auf 0 setzen, erhalten Sie eine Art Glüh-Effekt, wie er auch mit einem speziellen, nachfolgend beschriebenen Filter realisiert werden kann.
18.6.4 Glow-Filter 18.6.3 DropShadow-Filter Den klassischen Schlagschatten kann man mit dem DropShadow-Filter herstellen. 9. Erweitern Sie den Funktions-Block um folgende Deklaration: function schatten() {
fFilter = new DropShadowFilter (3,45,0x666666,0.8,3,3,2,3, false,false,false); trace(„Blur-Filter“); }
Mit diesem Filter erzeugen Sie ein Glühen um Ihr Objekt herum. 12. Erweitern Sie den Funktions-Block um folgende Deklaration:
function glow() {
trace(„Glow-Filter“);
fFilter = new GlowFilter (0x0000ff,0.8,5,5,2,3,false,false); }
13. Ändern Sie den Aufruf von init() (Fettdruck):
init(glow);
18.6 Filter
239
Ein bläulicher Glanz umhüllt unser Rechteck und erfüllt es mit farblichem Stolz. 14. Ändern Sie in der Deklaration von glow() die Festlegung der Filtereigenschaften ab der öffnenden Klammer wie folgt:
fFilter = new GlowFilter();
fFilter.alpha = 0.8;
fFilter.color = 0x0000ff; fFilter.blurX = 5;
Abbildung 51: GradientBevel-Filter
fFilter.blurY = 5;
fFilter = new GradientBevelFilter();
fFilter.quality = 3;
fFilter.angle = 45;
fFilter.strength = 2;
fFilter.inner = false;
fFilter.knockout = false;
Der GradientBevel-Filter kombiniert den normalen Bevel-Filter mit einem Farbverlauf. 15. Erweitern Sie den Funktions-Block um folgende Deklaration:
Filter.colors = [0x0066ff, f 0x0000ff];
fFilter.alphas = [1, 1];
fFilter.blurX = 5;
18.6.5 GradientBevel-Filter
fFilter.distance = 5;
fFilter.ratios = [0, 255]; fFilter.blurY = 5;
fFilter.strength = 2; fFilter.quality = 3;
fFilter.type = „inner“;
fFilter.knockout = false;
function gradientBevel() {
fFilter = new GradientBevelFilter(5, 45, [0x0066ff, 0x0000ff], [50, 100], [0, 255], 5, 5, 2, 3, „inner“, false); trace(„GradientBevel-Filter“); }
18.6.6 GradientGlow-Filter Bei diesem letzten Filter erhält der Glow-Effekt einen Farbverlauf.
16. Ändern Sie den Aufruf von init() (Fettdruck):
18. Erweitern Sie den Funktions-Block um folgende Deklaration:
function gradientGlow() {
trace(„GradientGlow-Filter“);
init(gradientBevel);
Als Ergebnis erhalten wir recht weiche 3D-Kanten, wie in Abbildung 51 gezeigt. Da es sich hier um einen Verlauf handelt, müssen mehrere Farbwerte, die jeweils zugehörige Deckkraft und die Position des einzelnen Farbwerts im gesamten Verlauf angegeben werden. Dadurch wird die Verwendung von Argumenten leider vollends unleserlich. 17. Ändern Sie in der Deklaration von gradientBevel() die Festlegung der Filtereigenschaften ab der öffnenden Klammer wie folgt:
fFilter = new GradientGlowFilter(0, 45, [0x0033ff,0x0000ff], [0.3, 1], [0, 128], 3, 3, 2, 3, „outer“, false); }
19. Ändern Sie den Aufruf von init() (Fettdruck):
init(gradientGlow);
Das Rechteck glüht nicht zuletzt aufgrund des mäßig attraktiven Farbverlaufs etwas verlegen vor sich hin.
240
Kapitel 18 BitmapData- und Filter-Klasse
20. Ändern Sie in der Deklaration von gradientBevel() die Festlegung der Filtereigenschaften ab der öffnenden Klammer wie folgt:
fFilter = new GradientGlowFilter();
fFilter.angle = 45;
fFilter.distance = 0;
fFilter.colors = [0x0033ff,0x0000ff];
fFilter.alphas = [0.3, 1];
fFilter.blurX = 3;
fFilter.ratios = [0, 128]; fFilter.blurY = 3;
fFilter.strength = 2; fFilter.quality = 3;
fFilter.type = „outer“;
fFilter.knockout = false;
Die Argumente bzw. Eigenschaften entsprechen exakt denjenigen des GradientBevel-Filters.
Blau sowie auf die Deckkraft. Auf der rechten Seite der Abbildung ist eine Identitäts-Matrix erfasst, die zu keinerlei Änderungen führt. Sie kann auch dazu verwendet werden, um durchgeführte Änderungen rückgängig zu machen. 1. Erstellen Sie eine Standarddatei. 2. Ändern Sie die Hintergrundfarbe auf ein lebensechtes Grau (0xcccccc). 3. Importieren Sie in die Bibliothek eine beliebige Grafik. Wir wählen hier beispielhaft erdhorn1.jpg. 4. Fügen Sie die Grafik in der Ebene objects auf der Bühne an beliebiger Position ein. 5. Wandeln Sie die Grafik in einen MovieClip um (Bibliotheksname mcBild, Instanzname bild, Registrierungspunkt links oben). 6. Weisen Sie actions folgendes Bildskript zu: //------------ import –------------- import flash.filters.*;
//–----------- vars –--------------- var mObjekt:MovieClip = bild; var fFilter;
18.6.7 ColorMatrix-Filter Von den bisher vorgestellten Filtern unterscheiden sich die nachfolgenden drei entweder im Aufbau (ColorMatrix und Convolution) und/oder in der Auswirkung (DisplacementMap) deutlich. Der ColorMatrix-Filter ermöglicht es mit Hilfe einer 4 × 5-Matrix verschiedene Transformationen von Farbe und Deckkraft durchzuführen. So lässt sich z. B. die Intensität eines einzelnen Farbtons gezielt steuern. Eine derartige Matrix bezeichnet man auch als Kernel; sie ist in Abbildung 52 dargestellt. Die Zahlenreihen beziehen sich, betrachtet von oben nach unten, auf die Farbkanäle Rot, Grün und
var aColors:Array = [];
//----------- functions –------------
function init(pFilter:Function):Void { mObjekt._x = Stage.width/2-mObjekt. _width/2; mObjekt._y = 100; pFilter();
mObjekt.filters = [fFilter]; }
function colorMatrix() {
aColors = [0.2, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0]; fFilter = new ColorMatrixFilter(aColors);
mObjekt.filters = [fFilter]; }
//------------ start –-------------- init(colorMatrix);
Abbildung 52: 4 × 5-Matrix des ColorMatrix-Filters
Beim Testen taucht horizontal mittig die gewählte Grafik auf. An ihr ist keinerlei Änderung festzustellen, was schlicht daran liegt, dass wir eine IdentitätsMatrix zugewiesen haben. Diese Werte werden mit dem Farbwert der betroffenen Pixel multipliziert. Da
18.6 Filter
241
in den Farbkanälen keine Änderung vorgenommen wurde, ändert sich auch nichts in der Grafik. Bevor wir in der Matrix mit anderen Werten arbeiten, um beispielsweise eine Färbung herbei zu führen, wollen wir die Schreibweise beim Einrichten des Filters vereinfachen.
Ihre Grafik weist nun einen extremen Rotstich auf. Wir haben zwar formal den Rotanteil nicht verändert, aber den Grün- und Blauanteil reduziert, so dass eine extreme Rotfärbung resultiert.
7. Ersetzen Sie in der Funktion colorMatrix() die Einrichtung von aColors durch:
18.6.8 Convolution-Filter
a Colors = aColors.concat([1, 0, 0, 0, 0]);
Über den Convolution-Filter werden einzelne Pixel mit den benachbarten Pixeln kombiniert, um Effekte wie Kantenhervorhebung, Bildschärfung, Weichzeichnung etc. zu erhalten. Er verwendet eine n x m-Matrix, in der die Verrechnung einzelner Pixel miteinander definiert wird. Aufgrund der prinzipiell beliebigen Größe der Matrix bietet dieser Filter eine geradezu überwältigende Fülle an Einstellmöglichkeiten.
a Colors = aColors.concat([0, 1, 0, 0, 0]); a Colors = aColors.concat([0, 0, 1, 0, 0]); a Colors = aColors.concat([0, 0, 0, 1, 0]);
Die gewünschte Matrix wird in einem Array abgebildet, das 20 Elemente umfasst. Schreiben wir sie wie in der vorherigen Version einfach hintereinander, ist nur schwer erkennbar, welches Element welche Funktion hat. Daher haben wir sie im nächsten Schritt über die concat()-Methode in das anfangs eingerichtete Array eingefügt, wobei wir jeweils 5 Elemente pro Farbkanal sowie für die Deckkraft erfassen. Die Farbwerte aller Pixel im betroffenen Bild werden folgendermaßen errechnet:
9. Erweitern Sie den Funktions-Block:
Rotkanal: 1 * Rotanteil Bild + 0 * Grünanteil + 0 * Blauanteil Bild + 0 * Alpha Rot
10. Ändern Sie den Aufruf von init() (Fettdruck):
Grünkanal: 0 * Rotanteil Bild + 1 * Grünanteil + 0 * Blauanteil Bild + 0 * Alpha Grün
Wer Spektakuläres erwartet hat, wird vielleicht ein bisschen enttäuscht – schließlich ändert sich nichts. Wir legen eine 3 × 3-Matrix fest, hier definiert über dreimaligen Aufruf der concat()-Methode. Danach richten wir einen Convolution-Filter ein, dem wir als Parameter die x- und y-Dimension der Matrix bzw. die Anzahl der Spalten und Zeilen, die eigentliche Matrix sowie einen Divisor – dieser Wert ist optional – übergeben. Ohne auf alle Details eingehen zu wollen, sind für uns der Wert in der Mitte (bei einer 3 × 3-Matrix der fünfte Wert), eine gewisse Symmetrie der übrigen Werte und die Summe aller Werte bei einem Divisor von 1 wichtig. Die Zahlen legen fest, mit welchen Farbwerten der Farbwert des in der Mitte der Matrix angesprochenen aktuellen Pixels zu verrechnen ist. In unserem Beispiel bedeutet dies, dass wir den momentanen Farbwert mit 1 multiplizieren, ihn also
Blaukanal: 0 * Rotanteil Bild + 0 * Grünanteil + 1 * Blauanteil Bild + 0 * Alpha Blau Alphakanal: 0 * Rotanteil Bild + 0 * Grünanteil + 0 * Blauanteil Bild + 1 * Alphakanal Der Wert der letzten Spalte wird jeweils hinzu addiert, kommt aber nur in besonderen Fällen zum Tragen und muss uns hier nicht weiter interessieren. 8. Ändern Sie die Werte für den Grün- und Blaukanal im zweiten und dritten Aufruf der concat()-Methode (Fettdruck): a Colors = aColors.concat([0, 0.1, 0, 0, 0]); aColors = aColors.concat([0, 0, 0.1, 0, 0]);
function convolution() {
aColors = aColors.concat([0, 0, 0]); aColors = aColors.concat([0, 1, 0]); aColors = aColors.concat([0, 0, 0]); fFilter = new ConvolutionFilter(3, 3, aColors, 1); }
init(convolution);
242
Kapitel 18 BitmapData- und Filter-Klasse
insofern unverändert übernehmen, und die Farbwerte der umgebenden Pixel mit 0 multiplizieren und mit dem aktuellen Pixel verrechnen. Faktisch ergibt sich daraus keine Änderung. Die konkrete Auswirkung einer Matrix lässt sich bei einem Divisor von 1 partiell abschätzen anhand der Summe aller Zahlen: Liegt sie über 1, resultiert eine hellere, bei unter 1 eine dunklere Grafik. 11. Ändern Sie den mittleren Wert im zweiten Aufruf der concat()-Methode (Fettdruck):
aColors = aColors.concat([0, 2, 0]);
Die Grafik wird nun erheblich heller dargestellt als zuvor, da wir den aktuellen Farbwert des betroffenen Pixels mit 2 multiplizieren. Dasselbe gilt natürlich auch für jeden beliebigen anderen Pixel. 12. Ändern Sie die Werte im zweiten Aufruf der concat()-Methode (Fettdruck):
aColors = aColors.concat([1, 1, 0]);
Wir erhalten wie zuvor eine starke Aufhellung des Bildes. 13. Ändern Sie die Werte im zweiten Aufruf der concat()-Methode (Fettdruck):
aColors = aColors.concat([0, 0, 0]);
Unsere Grafik wird komplett schwarz, da wir alle Farbwerte mit 0 multiplizieren und so als Ergebnis eben 0 erhalten. Möchten Sie zwar eine Änderung bewirken, dabei jedoch die bestehende Helligkeit erhalten, sorgen Sie mit Hilfe des Divisors dafür, dass die Summe der Matrix 1 ergibt. 14. Ändern Sie die Aufrufe der concat()-Methode (Fettdruck):
aColors = aColors.concat([1,2,1]);
aColors = aColors.concat([1,2,1]);
aColors = aColors.concat([0,1,0]);
Flash stellt die Grafik so stark aufgehellt dar, dass nahezu nichts mehr zu erkennen ist. 15. Ändern Sie bei der Einrichtung des Filters den Divisor (Fettdruck):
Filter = new ConvolutionFilter(3, f 3, aColors, 9);
Damit realisieren wir einen leichten Blur-Effekt, ohne jedoch die Luminanz zu ändern. Abschließend ein Beispiel für einen weiteren Effekt, den wir von Bildbearbeitungsprogrammen her gewohnt sind. 16. Ändern Sie die Werte in der Matrix (Fettdruck):
Colors = aColors.concat([-1, -1, a -1]); Colors = aColors.concat([-1, 9, a -1]);
Colors = aColors.concat([-1, -1, a -1]);
17. Setzen Sie den Divisor zurück auf 1. Sie erhalten eine deutliche Hervorhebung der Kanten.
18.6.9 DisplacementMap-Filter Vielleicht der am schwersten zu verstehende Filter ist die DisplacementMap, die, grob gesprochen, Pixel in einem Bild auf der Basis von Farbinformationen oder der Deckkraft in einem anderen Bild verschiebt. Es fehlt hier leider der Raum, um sich näher mit den dahinter liegenden Berechnungen zu beschäftigen. Wer möchte, kann sich in der Flash-Hilfe die recht komplexe Formel anschauen, die dabei zur Anwendung kommt. 1. Erstellen Sie eine Standarddatei. 2. Importieren Sie in die Bibliothek eine beliebige Grafik. Wir wählen hier beispielhaft erdhorn1.jpg. 3. Fügen Sie die Grafik in der Ebene objects auf der Bühne an beliebiger Position ein. 4. Wandeln Sie die Grafik in einen MovieClip um (Bibliotheksname mcBild, Instanzname bild, Registrierungspunkt links oben). 5. Weisen Sie actions folgendes Bildskript zu: //------------- import –------------ import flash.display.*; import flash.filters.*; import flash.geom.*;
//––---------- vars –--------------- var mObjekt:MovieClip = bild; var bmBild:BitmapData; var fFilter;
18.6 Filter
243
//----------- functions –----------- function init():Void {
bmBild = new BitmapData(mObjekt._ width, mObjekt._height); mObjekt._x = Stage.width/2-mObjekt._width/2; mObjekt._y = 100;
mObjekt.onPress = displace; mObjekt.onPress(); }
function displace() {
bmBild.noise(Math.round(Math.ran dom()*100));
fFilter = new DisplacementMapFilter(bmBild, new Point(0, 0), 1, 1, 5, 5); this.filters = [fFilter]; }
//-------------- start –------------ init();
Die einzelnen Pixel werden beim Testen horizontal und vertikal verschoben, so dass ein Verfremdungseffekt auftritt, der in Abbildung 53 zu sehen ist. Mit jedem Klick auf das Bild wird eine neuer zufallsbedingter Verschiebungsfilter zugewiesen. Wir benötigen drei verschiedene Klassen, die wir am Anfang importieren. Neben zwei schon in den vorhergehenden Übungen deklarierte Variablen wird eine weitere erstellt, die dazu dienen wird, eine geskriptete Bitmap aufzunehmen. Die Funktion init() legt diese Bitmap mit der Höhe und Breite des zu ändernden Bildes an. Sie wird benötigt, um als Verschiebungsmatrix für den Filter zu dienen. Da wir sie nicht anzeigen wollen, können wir an dieser Stelle darauf verzichten, einen MovieClip zur Darstellung der BitmapDaten einzurichten, wie wir es oben kennen gelernt haben. Wir positionieren wie gewohnt das zu ändernde Bild und weisen ihm einen Mausklick zu, bei dem die Funktion displace aufgerufen wird. Damit sie bereits direkt vor einer Aktion des Anwenders zur Ausführung kommt, rufen wir das onPress-Ereignis auf. Die Funktion displace() erstellt ein simples farbiges Störungsbild, dem wir als Startwert eine Zufallszahl zwischen 0 und 100 zuweisen. Dadurch generiert Flash bei jedem Funktionsaufruf eine andere
Abbildung 53: DisplacementMap-Filter
zufällige Störung. Anschließend richten wir den Filter ein, der dabei folgende Argumente erhält:
• bmBild, die Bitmap-Instanz, in der die für eine
Verschiebung benötigten Werte (Farbe, Deckkraft) enthalten sind; • new Point(0, 0), der Startpunkt des Effekts auf der davon betroffenen Grafik; • 1, der Kanal in bmBild, der für eine Verschiebung in x-Richtung verwendet wird; • 1, der Kanal in bmBild, der für eine Verschiebung in y-Richtung verwendet wird. Die Zahlen 1, 2 und 4 beziehen sich auf die RGB-Farbkanäle, 8 meint den Alphakanal; • 5, 5, die horizontale und vertikale Skalierung des Verschiebungseffekts. Weitere Parameter sind optional und werden von uns nicht verwendet. Da wir in der noise()-Methode jeweils zufällige Farbpixel erzeugen, aber für die Skalierung einen relativ niedrigen Wert wählen, resultiert eine Verschiebung, die nicht so groß ist, dass wir kein Motiv mehr erkennen können. Ändern lässt sich das, indem entweder gezielt andere Farbwerte gewählt werden – je näher die Werte an 128 liegen, desto geringer die Verschiebung – und/oder die Skalierungsfaktoren erhöht werden. Gerne können Sie mehrere Filter miteinander kombinieren, wobei sie in der Reihenfolge der Zuweisung abgearbeitet werden. Sollen aufgrund einer User-Inter aktion Filtereigenschaften geändert werden, muss der betreffende Filter erneut zugewiesen werden.
19
Sound
Generell unterscheidet man zwischen Musik, Audio und Sprache. Musik dürfte selbsterklärend sein. Ihre Hauptfunktion besteht darin, eine bestimmte Atmosphäre zu schaffen. Insofern stellt sie das auditive Pendant zu ästhetischen Grafiken dar. Audio bezieht sich auf alle Arten von Geräuschen, deren Funktionen sowohl ästhetischen wie auch informativen Charakter haben können. Oft verwendet man derartige Sounds, um dem User bei der Aktivierung von Schaltflächen ein Feedback – etwa das berühmte Klickgeräusch – zu geben. Sie lassen sich jedoch auch einsetzen, um visuelle oder textuell vermittelte Informationen zu untermalen, etwa Maschinengeräusche bei einer Infoseite zu einem technischen Thema. Sprechersound wird in der überwiegenden Mehrzahl der Fälle eingesetzt, um Informationen zu vermitteln. Im Gegensatz zu den übrigen Medien gilt für Sound i. A. die Regel, dass man keine zentrale Information nur auditiv vermittelt, da Sie nie wissen können, ob die Soundausgabe auf dem Zielrechner aktiviert oder deaktiviert ist. Zwar lässt sich herausfinden, ob audiofähige Hardware und Software vorhanden ist. Dazu müssen Sie lediglich eine betreffende Skriptabfrage einfügen: if(System.capabilities.hasAudio){ //Soundaufruf; }
Die Abfrage ergibt abhängig von der Ausstattung des Zielrechners true oder false. Korrespondierend dazu erteilt Flash Auskunft über das Vorhandensein von benötigter Software mit: if(System.capabilities.hasAudioEncoder){ //Soundaufruf; }
bzw. if(System.capabilities.hasMP3){ //Soundaufruf; }
Da heutzutage in der überwiegenden Mehrzahl Geräte mit den entsprechenden Fähigkeiten ausgestattet sind, erscheint diese Abfragemöglichkeit nicht wirklich hilfreich. Denn sie verschweigt, ob die gefundene Hardware auch tatsächlich aktiviert wurde. Ein Lautsprecher nützt wenig, wenn der Anwender ihn aus welchem Grund auch immer ausgeschaltet hat. Was so banal klingt, hat einen ernsthaften Hintergrund, wie der Autor selbst erfahren musste: In einem CBT (Computer Based Training) wurde die Bedienung des recht komplexen Programms ausschließlich auditiv vorgestellt, was sich beim Einsatz in einem Großraumbüro bei ausgeschalteten Lautsprechern und dem Fehlen von Kopfhörern als fatal herausstellte!
19.1 Sound abspielen und stoppen Flash unterscheidet zunächst einmal unabhängig von der konkreten Funktionalität des verwendeten Sounds zwischen Streaming- und Ereignis-Sound. Ersterer wird bereits während des Ladevorgangs abgespielt. Erst lädt Flash einen kleinen Teil in einen Puffer, anschließend wird er wiedergegeben. Die Größe des Puffers lässt sich mit der globalen Eigenschaft _soundbuftime steuern. Der Standardwert beträgt 5 Sekunden, d. h. Flash speichert 5 Sekunden, bevor das Abspielen beginnt. Wer mit einem anderen Wert arbeiten möchte, muss lediglich eine entsprechende Zuweisung definieren, z. B.:
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
245
246
_soundbuftime = 10;
Die Zahl entspricht der gewünschten Sekundenlänge. Selbstverständlich erfolgt diese Zuweisung vor dem Aufruf des Streamings. Sinn eines derartigen Sounds ist es, eine größere Datei verwenden zu können, ohne diese in den Flashfilm integrieren zu müssen. Damit bieten sich vor allem Sprache und Musik als Verwendungszweck an, während unser berühmte Klicksound sowohl aufgrund seiner geringen Größe wie auch aufgrund der häufigeren Verwendung nicht geeignet wäre. Die zweite Kategorie, der Ereignissound, wird erst abgespielt, nachdem alle Daten vollständig geladen sind. Wir haben es geflissentlich und vielleicht unfairerweise bis jetzt verschwiegen, aber Flash gestattet es, Code zusammen zu klicken. Diese Funktionalität versteckt sich hinter dem Menü . Dort stehen zwar nur sehr eingeschränkte Möglichkeiten zur Verfügung. Eine flexible Steuerung etwa von Sounds kann so nicht realisiert werden. Immerhin jedoch können wir Aufgaben wie das Einfügen und Starten eines in der Bibliothek vorliegenden Sounds, Stoppen aller Sounds oder laden einer externen Sounddatei umsetzen. 1. Erstellen Sie eine Standarddatei. 2. Importieren Sie in die Bibliothek eine Sounddatei. Zwar beherrscht Flash die Formate aif, wav und mp3, doch eignen sich aufgrund des hohen Kompressionsgrads bei gleichzeitig guten Qualitätseinstellungen Letztere am besten. 3. Weisen Sie dieser Datei in der Bibliothek den Verknüpfungsnamen sound1 zu. Damit sind wir in
Kapitel 19 Sound
der Lage, ähnlich wie bei MovieClips den Sound zur Laufzeit in die Bühne einzufügen. 4. Rufen Sie das Menü auf. 5. Klicken Sie auf das Plus-Icon am linken Fensterrand, um ein neues Verhalten auszuwählen. 6. Wählen Sie aus der Liste <Sound><Sound aus Bibliothek laden>. 7. Nehmen Sie in dem sich öffnenden Fenster die in den Abbildungen 54 und 55 angegebenen Einstellungen vor. 8. Bestätigen Sie mit . Beim Testen wird der gewählte Sound abgespielt. Wenn Sie sich die Zeitleiste anschauen, werden Sie feststellen, dass sich in der zuvor aktiven Ebene ein neues Bildskript mit folgendem Inhalt befindet: //Play Internal Sound Behavior
if(_global.Behaviors == null)_global. Behaviors = {}; if(_global.Behaviors.Sound == null)_ global.Behaviors.Sound = {}; if(typeof this.createEmptyMovieClip == ‚undefined‘){
this._parent. createEmptyMovieClip(‚BS_ sdSound1‘,new Date().getTime()(Math.floor((new Date().getTime()) /10000)*10000) ); _global.Behaviors.Sound.sdSound1 = new Sound(this._parent.BS_sdSound1); } else {
Abbildung 54: Einstellungen für das Sound-Verhalten
19.1 Sound abspielen und stoppen
247
4. Weisen Sie actions folgendes Bildskript zu: //------------ vars –--------------- var sdSound1:Sound;
//---------- functions –------------ function init() {
sdSound1 = new Sound();
sdSound1.attachSound(„sound1“); sdSound1.start(); } Abbildung 55: Weitere Einstellungen für das Sound-Verhalten
this.createEmptyMovieClip(‚_ sdSound1_‘,new Date().getTime()(Math.floor((new Date().getTime()) /10000)*10000) );
_global.Behaviors.Sound.sdSound1 = new Sound(this.BS_sdSound1); }
_global.Behaviors.Sound.sdSound1. attachSound(„sound1“); if (true) {
_global.Behaviors.Sound.sdSound1. start(0,1); }
//End Behavior
Dieser Code wurde automatisch eingefügt, so dass wir uns nicht weiter um ihn kümmern müssen. Die Skripte liegen pro Verhalten in einer eigenen XMLDatei vor, die sich im Unterordner Behaviors des für jeden einzelnen User automatisch angelegten Stammordners Flash/de/Configuration befindet. An ähnlicher Stelle lagen ja schon die Flash-Klassen vor, wie weiter oben gezeigt. Neugierige dürfen gerne einen Blick in die XML-Datei werfen. An dieser Stelle können sogar eigene Verhaltensweisen eingefügt werden, was allerdings solide Skript-Kenntnisse voraussetzt.. Mehr Flexibilität (und verständlicheren Code) bietet der eigene Zugriff auf die Sound-Klasse. 1. Erstellen Sie eine Standarddatei. 2. Importieren Sie in die Bibliothek zwei Sound dateien. 3. Weisen Sie diesen Dateien in der Bibliothek die Verknüpfungsnamen sound1 und sound2 zu.
//----------–– start –-------------- init();
Wenn Sie testen, gibt Flash den importierten Sound wieder. Wir richten eine Variable ein, um das benötigte Sound-Objekt zu referenzieren. Danach legen wir eine Funktion init() fest, die bei Aufruf die SoundKlasse instanziiert, der Instanz den gewünschten Sound aus der Bibliothek zuordnet und ihn startet. Verzichten wir auf das Starten, wird der Sound zwar geladen, aber nicht abgespielt. Die start()-Methode erlaubt es, zwei Argumente zu übergeben:
• Sekunden,
gibt an, ab welcher Stelle der Sound starten soll. Beachten Sie, dass sich der Wert auf Sekunden bezieht, nicht, wie sonst bei der Zeitmessung, auf Millisekunden; • Zahl, Anzahl der Wiederholungen. Ähnlich einfach gestaltet sich ein Anhalten des Sounds – zumindest prinzipiell. 5. Erweitern Sie die Deklaration von init() unmittelbar vor der schließenden Klammer: this.onMouseDown = function(){ sdSound1.stop(); }
Wenn Sie beim Testen auf eine beliebige Stelle klicken, wird die Audio-Wiedergabe gestoppt. Doch was geschieht, wenn wir gleichzeitig mehrere Sounds verwenden, z. B. eine dezente Hintergrundmusik und ein Sprechersound? 6. Erweitern Sie den Variablen-Block (Fettdruck): var sdSound1:Sound, sdSound2:Sound;
248
Kapitel 19 Sound
7. Fügen Sie in der Deklaration von init() unmittelbar vor der Zuweisung des onMouseDown-Ereignisses ein: sdSound2 = new Sound();
sdSound2.attachSound(„sound2“); sdSound2.start();
Nun werden zwei Sounds gleichzeitig gespielt. Überraschenderweise stoppen allerdings auch beide, wenn Sie irgendwo mit der Maus klicken, obwohl in der Deklaration von init() explizit nur sound1 gestoppt werden soll. Der Grund liegt in der flashinternen Behandlung von Sound. Zwar richten wir pro Sound ein eigenes Objekt ein, doch benötigt jeder zusätzlich eine Zeitleiste, in der er abgelegt werden kann. Denn Flash behandelt geskriptete Audio-Dateien ähnlich den von Hand eingefügten Audio-Dateien, die auf das Vorhandensein einer Zeitleiste angewiesen sind. Verzichten wir wie im vorliegenden Fall auf die Verwendung einer eigenen Zeitleiste, nimmt Flash einfach diejenige, der unsere Sound-Instanz zugeordnet wurde, also hier konkret die Hauptzeitleiste. Ein stop()-Befehl bezieht sich dann auf alle Sounds innerhalb einer spezifischen Zeitleiste. 8. Erweitern Sie den Variablen-Block: var mSound1:MovieClip, mSound2:MovieClip;
9. Ergänzen Sie die Deklaration von init() unmittelbar nach der öffnenden Klammer: mSound1 = this.createEmptyMovieClip („mcSound1“, this.getNextHighest Depth()); mSound2 = this.createEmptyMovieClip („mcSound2“, this.getNextHighest Depth());
10. Ändern Sie in init() jeweils die Deklaration von sdSound1 und sdSound2 (Fettdruck):
sdSound1 = new Sound(mSound1); sdSound2 = new Sound(mSound2);
Nun funktioniert das Anhalten von sdSound1 wie gewünscht. Wir legen zwei Variablen an, um zwei innerhalb von init() erzeugte leere MovieClips referenzieren zu können. Anschließend übergeben wir bei der Instanziierung des Sound-Objekts jeweils eine dieser
Zeitleisten als Argument, so dass jeder Sound einer eigenen Zeitleiste zugeordnet wird. Ein stoppen bezieht sich dann nur noch auf jeweils eine Zeitleiste und damit eben auch nur noch auf einen einzigen Sound. Wollen wir Statt dessen tatsächlich alle Sounds stoppen, genügt die globale Funktion stop AllSounds().
19.2 Einen Sound loopen Wenn wir einen Hintergrundsound verwenden, wählen wir i. d. R. ein möglichst kurzes Musikstück, das sich permanent abspielen lässt. Die Kürze sorgt für eine geringe Dateigröße. Wichtig ist, dass kein Streaming-Sound verwendet wird, denn dann würde ein Loop keinen Sinn machen, da immer nur ein Teil gepuffert, nicht aber die gesamte Datei gespeichert wird. Ein Loop würde dann im ständigen Herunterladen resultieren, was kontraproduktiv wäre. Da bei der start()-Methode des Sound-Objekts keine Loop-Eigenschaft adressierbar ist, behelfen sich viele Entwickler mit einem kleinen Trick. 1. Der Einfachheit halber verwenden wir dieselbe Datei wie im vorhergehenden Abschnitt, die Sie bitte unter einem neuen Namen abspeichern. 2. Ändern Sie in der Deklaration von init() die start()-Methode für sdSound1 (Fettdruck): sdSound1.start(0,999);
3. Löschen Sie jeden Bezug auf sdSound2 sowie mSound2, da es hier ausreicht, mit einem einzigen Sound zu arbeiten. Der resultierende Code müsste jetzt so aussehen: //----------–– vars –--------------- var mSound1:MovieClip; var sdSound1:Sound;
//------------ functions –---------- function init() {
mSound1 = this.createEmptyMovieClip („mcSound1“, this.getNextHighest Depth()); sdSound1 = new Sound(mSound1);
sdSound1.attachSound(„sound1“); sdSound1.start(0, 999);
19.3 Externe Sounds
this.onMouseDown = function() { sdSound1.stop();
249
start()-Methode auf, so dass die Audio-Datei wie-
der von vorne beginnt.
}; }
//–––---------- start –------------- init();
Falls Ihre Audio-Datei nicht zu lang ist, können Sie feststellen, dass der Sound wiederholt abgespielt wird. Um dies zu erreichen, haben wir einfach die Anzahl der Wiederholungen auf einen recht hohen Wert gesetzt, nämlich 999. Dadurch entsteht der Eindruck einer Endlosschleife. Achten Sie darauf, an die start()-Methode zwei Argumente zu übergeben. Denn der erste ist, wie oben gesehen, für den Start offset verantwortlich und nur der zweite definiert die Anzahl der Wiederholungen. Schreiben Sie Statt dessen sdSound1.start(0, 999), meint Flash, Sie wollten den Sound mit einem Offset von 999 Sekunden starten, also erst ab der 999. Sekunde abspielen lassen. Ist Ihr Sound kürzer, was bei einem Ereignissound recht wahrscheinlich sein dürfte, dann werden Sie überhaupt nichts hören. Daher übergeben wir als erstes Argument einen Offset von 0, so dass der Sound von Anfang an spielt, anschließend folgt die Anzahl der Wiederholungen. Ein klein wenig aufwendiger, dafür aber erheblich eleganter ist eine Variante, die sich eines besonderen Ereignisses der Sound-Klasse bedient. 4. Löschen Sie in der Deklaration von init() die an die start()-Methode übergebenen Argumente. 5. Fügen Sie in der Deklaration von init() nach dem Aufruf der start()-Methode ein:
19.3 Externe Sounds Größere Flexibilität bietet der Einsatz externer Dateien. Damit lassen sich sowohl Streaming- wie Ereignis-Sounds wiedergeben. 1. Erstellen Sie eine Standarddatei. 2. Erstellen Sie in dem Ordner, in dem sich diese Datei befindet, einen Unterordner namens sounds. 3. Fügen Sie in diesem Unterordner eine Datei (Format: mp3) unter dem Namen sound1.mp3 ein. 4. Weisen Sie actions folgendes Bildskript zu: //-----------–– vars –-------------- var mSound1:MovieClip; var sdSound1:Sound;
//–---------- functions –----------- function init() {
mSound1 = this.createEmptyMovieClip („mcSound1“, this.getNextHighest Depth()); sdSound1 = new Sound(mSound1);
sdSound1.loadSound(„sounds/sound1. mp3“,true); sdSound1.start();
this.onMouseDown = function() { sdSound1.stop(); };
sdSound1.onSoundComplete = function(){
}
this.start();
//––----------- start –--------------
}
init();
Das Ergebnis entspricht beim Testen dem vorhergehenden. Statt jedoch die Anzahl der Wiederholungen auf einen zwar hohen, aber dennoch endlichen Wert zu setzen, greifen wir auf das Ereignis onSoundComplete zurück. Das tritt dann auf, wenn, wie der Name schon andeutet, der angesprochene Sound vollständig abgespielt wurde. Dann rufen wir einfach erneut die
Beim Testen startet der Sound wie zuvor. Da wir den Sound extern laden, ist die resultierende fla-Datei erheblich kleiner geworden; zudem können wir, ohne sie öffnen zu müssen, den Sound beliebig austauschen. Die Vorgehensweise gleicht der vorhergehenden; lediglich wird hier anstelle eines attachSound() ein externer Ladevorgang mit loadSound() ausgeführt.
Externe Assets
Zu den Vorteilen von Flash zählt die große Flexibilität im Umgang mit Medienformaten. Wir können alle bekannten und manche weniger gängigen Dateien von Text über Grafiken bis hin zu Sound und Video recht problemlos integrieren. Dabei sind wir nicht darauf angewiesen, die betreffenden Medien in eine fla-Datei hinein zu stopfen, denn Flash erlaubt es, sie zur Laufzeit bei Bedarf zu laden. Zugriff auf extern vorliegende Daten ist in folgenden Fällen möglich:
• Text
(loadVariables(), mc.loadVariables (), loadVariableNum(), LoadVars(), XML, SharedObject). Texte bieten sich immer dann an, wenn Sie entweder Informationen für einen späteren Zugriff speichern oder eine Applikation konfigurieren möchten. So könnte man in einer einfachen Textdatei festhalten, wie viele Bilder in einer Galerie vorhanden sind und in welchem Ordner sie sich befinden. Oder die anzuzeigenden Texte werden aus externen Files eingelesen, so dass sie sich komfortabel ändern lassen, ohne dass man deswegen unmittelbar in den Flash-Code eingreifen müsste. User-Informationen wie etwa der erreichte Level und Punktestand in einem Spiel lassen sich ebenfalls einfach in einer derartigen Datei erfassen. Neben den veralteten Methoden loadVariables() und loadVariableNum() kommen dabei die LoadVars- und SharedObject-Klasse zum Einsatz. Während sich mit allen genannten Methoden lokale Daten laden lassen, erlaubt nur Letztere auch lokales Speichern, wobei der Speicherort von Flash vorgegeben wird. Das SharedObject gerät mittlerweile etwas in Verruf, weil es gerne als Cookie verwendet wird, das ein Browser nicht mehr
20
automatisiert löscht. Speichern auf einem Server dagegen ist innerhalb der Domain, aus der der Film aufgerufen wird, auch mit den übrigen Methoden möglich. • Grafiken (loadMovieNum(), mc.loadMovie(), MovieClipLoader). Wie im Fall von Textinhalten kann es sich bei Grafiken anbieten, diese als externe Assets zu verwenden anstatt sie unmittelbar in die Anwendung bzw. Site zu integrieren. Damit gewinnt man einerseits die Flexibilität, Grafiken relativ einfach nach Bedarf austauschen zu können. Andererseits werden je nach Konzeption nur diejenigen Bilddaten auf den Client übertragen, die aktuell benötigt werden. So kann man in einer Bildergalerie die vorhandenen Grafiken als Thumbnail in einer Vorschau anzeigen, während erst ein Mausklick die größere Originalversion in die Anwendung lädt. • Sound (loadSound()) Im Prinzip gilt hier dasselbe wie für die Grafiken, wobei allerdings die Notwendigkeit mit externen Medien zu arbeiten, bei Sound noch in einem höheren Maße besteht. Denn derartige Dateien verfügen über eine im Vergleich zu Bildern und erst recht zu Text erhebliche Datenmenge. Lädt man Sound, verfügt man neben der erwähnten Flexibilität auch über die Möglichkeit des Streamings: Noch während des Ladevorgangs spielt bereits der auf dem Client vorhandene Teil ab, wodurch sich subjektiv der Ladevorgang verkürzt. • Video (NetStream/attachVideo()) Hinsichtlich Datenmenge und Streaming gleichen sich Sound und Video. • Beliebige Inhalte (loadMovieNum(), mc.loadMovie(), MovieClipLoader).
A. Kohl, ActionScript 2, doi:10.1007/978-3-540-35182-5, © Springer 2011
251
252
Gerade umfangreichere Applikationen umfassen, ähnlich, wie wir es von html-Seiten gewohnt sind, eine Vielzahl an swf-Dateien, in denen die konkret zu verwendenden Inhalte vorliegen (oder aufgerufen werden). Durch den modularen Aufbau gewinnt man eine größere Flexibilität, die es zulässt, nur Teile der Applikation im Fall der Fälle auszutauschen bzw. zu aktualisieren, ohne dadurch die gesamte Anwendung komplett neu aufsetzen zu müssen. • Actionscript-Dateien (include, import) Es empfiehlt sich, Code zu modularisieren, indem häufig vorkommende Blöcke in externe as-Dateien ausgelagert werden. Sie lassen sich per includeoder import-Anweisung in jede beliebige Anwendung laden, was natürlich den Workflow erheblich vereinfacht. Einige der erwähnten Fälle wurden bereits an anderer Stelle kurz vorgestellt, ohne dabei jedoch näher auf den Ladevorgang und insbesondere seine Kontrolle einzugehen. Wir wollen uns daher die konkrete Vorgehensweise am Beispiel von swf- und Bilddateien näher anschauen.
20.1 Laden externer swf-Dateien Wie erwähnt, werden in Flash größere Applikationen, insbesondere Websites, modular aufgebaut. Die benötigten Assets laden wir nach Notwendigkeit in Form externer swf-Dateien (bzw. in den oben vorgestellten Formaten von Text bis Video). In der einfachsten Variante reicht ein simpler Aufruf der loadMovie()Methode, um die externe Datei einzubinden. 1. Erstellen Sie einen Ordner namens modular. 2. Erstellen Sie eine Standarddatei und speichern sie in dem genannten Ordner unter dem Namen in dex. Alle weiteren Dateien werden, soweit nicht anders angegeben, in diesen Ordner abgelegt. 3. Erstellen Sie eine weitere Standarddatei und speichern sie unter dem Namen main. 4. Importieren Sie in main auf der Ebene objects horizontal und vertikal mittig eine beliebige Grafik. Wir verwenden hier beispielhaft gepard7.jpg. 5. Wandeln Sie main per <Strg><Enter> 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ü <56 K>, 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
mBtn = mNav.attachMovie(„mcBtn“, „btn“+i, mNav.getNextHighest Depth()); mBtn._y = i*(mBtn._ height+nAbstand);
mBtn.info.autoSize = „center“;
mBtn.info.text = „Bild „+(i+1);
272
aBtns.push(mBtn); }
mNav._x = mNav._y=mNav._width;
mLaden = this.createEmptyMovieClip („behExterneassets“, this.getNext HighestDepth()); mBild1 = mLaden.createEmptyMovie Clip(„behBild1“, mLaden.getNext HighestDepth()); mBild2 = mLaden.createEmptyMovie Clip(„behBild2“, mLaden.getNext HighestDepth()); mLaden._x = nLadeX;
Kapitel 20 Externe Assets
10. Ergänzen Sie die Deklaration von init() in der Schleife unmittelbar nach Aufruf der push()Methode:
mBtn.onPress = function() {
var nWas = Number(this._name.sub string(3));
mLadeNeu.loadMovie(sBildPfad+aBild er[nWas]); mLadeNeu._alpha = 0;
mLaden.onEnterFrame = ladeKont rolle;
};
mLaden._y = nLadeY;
11. Rufen Sie am Ende wie gewohnt die init()Funktion auf:
mLadeAlt = mBild1;
//-------------- start –-----------
mLadeNeu = mBild2; }
Die init()-Funktion muss alle benötigten leeren Clips initialisieren. Der erste Code-Block bezieht sich dabei auf die gesamte Navigation sowie innerhalb der Schleife auf alle Buttons. Da wir so viele Buttons verwenden wollen, wie Bilder geladen werden können, richten wir die Schaltflächen entsprechend der Anzahl der in aBilder erfassten Elemente ein. Dem in jedem eingefügten MovieClip enthaltenen Textfeld wird ein String, bestehend aus „Bild“ sowie der Zählvariable i zuzüglich 1, zugewiesen, so dass sie die Beschriftungen „Bild 1“ bis „Bild 3“ tragen. Die Addition von 1 ist notwendig, da i mit 0 beginnt, der erste Button aber die Beschriftung „Bild 1“ zeigen soll. Der Einfachheit halber wird die Navigation so positioniert, dass sie zum linken und oberen Bühnenrand einen Abstand einhält, der der Breite eines Buttons (bzw. der gesamten Navigation) entspricht. Hier sind natürlich beliebig andere Werte möglich. Danach erzeugen wir drei leere MovieClips, von denen einer als übergeordneter Behälter, die übrigen als Behälter für die externen Elemente fungieren. Dem übergeordneten Behälter weisen wir eine willkürliche Position zu. Alternativ können Sie, wie in den vorhergehenden Übungen gesehen, eine Position in Abhängigkeit von der Größe der geladenen Elemente errechnen. Abschließend werden in zwei Variablen die leeren Behälter referenziert. An den Namen kann man bereits erkennen, welche Variable dabei auf denjenigen Behälter verweist, in den wir laden werden, nämlich mLadeNeu.
init();
Bei Mausklick wird jeweils ein Bild geladen, abhängig vom angeklickten Button. Abbildung 66 zeigt den Aufbau des Beispiels. Jede Schaltfläche erhält ein onPress-Ereignis, das alle Zeichen ab dem vierten Zeichen im Namen des angeklickten Elements ermittelt und in eine Zahl umwandelt. Da die zugewiesenen Instanznamen btn0 bis btn2 lauten, können wir das so ermittelte Zeichen einfach als Index des Arrays aBilder verwenden, um eine Verknüpfung zu einer externen Datei herzustellen. Dementsprechend laden wir in mLadeNeu die so gefundene Datei. Dem Zielort wird eine Deckkraft von 0 zugewiesen, da ansonsten ein sukzessives Einblenden nicht möglich wäre. In der letzten Anweisung rufen wir permanent eine Funktion zur Kontrolle des Ladevorgangs auf. Die ist hier insofern wichtig, als wir feststellen müssen, ab wann alle Daten zur Verfügung stehen, so dass der Zielort mit dem Einblenden beginnen kann. 12. Erweitern Sie den Funktions-Block um folgende Deklaration:
function ladeKontrolle() {
if (mLadeNeu._width>=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 Tex