Durch die Einführunf von Variablen haben wir die Möglichkeit kennen gelernt unterschiedlich Formen von Daten zu speichern, auszulesen und zu verändern. Folgende Datentypen kennen wir bereits:
int number = 10;
float number = 12.5819;
char a = 'a';
true oder “nicht wahr” - false beinhalten. Wir treffen diesen Datentyp meist bei if-Bedingungen an. siehe Referenzboolean nice = true;
color pink = color(255, 0, 255);
Diese Datentypen werden auch primitiv genannt, da ihr Vorkommen, ihre Struktur und ihre Größe in der verwendeten Programmiersprache fest verankert sind. Beispielsweise kann eine Variable vom Typ int immer nur einen ganzzahligen Wert beinhalten. Dieser Fakt hat in unseren Programmen bisher kein Hindernis dargestellt – erlaubte uns aber, speziell im Bereich der Animation, nur eine stark begrenzte Anzahl von Elementen zu bearbeiten. Neben den primitiven Datentypen gibt es noch die sogenannten zusammengesetzten Datentypen. Ihre Größe und ihr Umfang ist nicht vorgegeben. Ihre Struktur kann in einigen Fällen verändert werden, da sie im Grunde aus primitiven Datentypen zusammengesetzt sind. Einen zusammengesetzten Datentypen haben wir bereits bei der Arbeit mit Text kennengelernt:
String text = "too much!";
Variablen sind das Gedächtnis unserer Programme. Wir legen dort Informationen (Texte, Zahlen, Ja/Nein) ab um sie im weiteren Ablauf wiederverwenden zu können bzw. zu modifizieren. Die primitiven Variablen haben uns erlaubt eine Information pro deklarierte Variable zu speichern – dies ist gut, aber sehr wenig. Um Programme flexibel zu gestalten, beschäftigen wir uns nun mit dem Erstellen und Verarbeiten von sogenannten Arrays. Arrays sind Listen, Reihen von Daten (Array = engl. Reihe). Ein Array besteht aus einer von uns bestimmten Anzahl von Feldern, welche alle einen Wert des gleichen Datentyps speichern können. Beispielsweise die Positionen von fünf Quadraten:
// Sketchsettings fill(0); stroke(0); // Erstellen und Befüllen der Variablen float rSize = 20.0; float pos1 = 0.0; float pos2 = 20.0; float pos3 = 40.0; float pos4 = 60.0; float pos5 = 80.0; // Zeichnen der Rechtecke nach den // Werten der Variablen rect(pos1, pos1, rSize, rSize); rect(pos2, pos2, rSize, rSize); rect(pos3, pos3, rSize, rSize); rect(pos4, pos4, rSize, rSize); rect(pos5, pos5, rSize, rSize);
Diese Schreibweise mag für fünf Elemente noch erträglich sein, ist aber beispielsweise bei 100 Elementen unbrauchbar (erst recht wenn wir die Positionen über die Zeit hinweg verändern wollen!). Hier kommen Arrays ins Spiel und verkürzen den Code beachtlich.
Arrays werden in folgender Form notiert:
// deklariere ein float Array float[] pos;
Die beiden eckigen Klammern hinter der Datentypbezeichnung markieren, dass es sich hierbei um ein Array von float-Werten handelt. Als nächstes muss das Array initialisiert werden, d.h. wir bestimmen wie viele Werte das Array umfassen soll. Dies geschieht mithilfe eines neuen Begriffs in Processing, dem new. Mithilfe von new erzeugen wir ein 'neues' Array an float-Werten, in das wir ab sofort Werte speichern können. Als letztes geben wir noch die Anzahl an Werten an, die in dem Array gespeichert werden sollen. Die gesamte Zeile sieht dann wiefolgt aus:
// deklariere und erzeuge ein float-Array mit 5 Werten float[] pos = new float[5];
Die '5' innerhalb der zweiten eckigen Klammern bezieht sich also auf die 5 float-Werte die ab sofort in dem Array gespeichert werden können.
In einem so initialisierten Array können also ab sofort Werte gespeichert und ausgelesen werden. Dabei hat jeder Wert einen eigenen Index – beginnend bei 0 – über den auf den Wert an der jeweiligen Stelle zugegriffen werden kann. Beim Zugriff auf das Array werden wieder die eckigen Klammern benutzt:
/* Deklarieren und Erzeugen eines float-Array mit * der Möglichkeit 5 Werte ablegen zu können */ float[] pos = new float[5]; /* Befüllen des Arrays mit Werten. Beginnend bei 0. * Das letzte Feld im Array sprechen wir deswegen * mit Länge-1 an. Array mit 3 Feldern --> 3-1 = 2 */ pos[0] = 1.2; // speichere den Wert '1.2' an der ersten Stelle pos[1] = -2.5; // speichere den Wert '-2.5' an die zweite Stelle pos[2] = 12.5; // speichere den Wert '12.5' an die dritte Stelle // Auslesen des ersten Wertes, Ausgabe in der Konsole println (pos[0]);
Ausserdem gibt es die Mölichkeit Arrays mit festen Werten zu erzeugen. Dazu werden sie nach der Deklaration in geschweiften Klammern, getrennt durch Kommata, geschrieben. Damit ersparen wir uns die Zeilen für das Befüllen des Arrays, können unseren Quelltext aber schlechter lesen:
/* Erstelle ein float-Array mit drei Feldern und befülle * diese sofort mit den Werten '1.2', '-2.5' und '12.5'. */ float[] pos = {1.2, -2.5, 12.5}; // gibt '12.5' in der Konsole aus println (pos[2]);
Unter Verwendung dieser Schreibweise sieht das obere Beispiel nun wiefolgt aus:
// Zeicheneinstellungen fill (0); stroke (0); // Varaibel für die Quadratgröße float rSize = 20.0; // float-Array mit Position der Rechtecke float[] pos = {0.0, 20.0, 40.0, 60.0, 80.0}; /* Zeichnen der Rechtecke, x- und y-Position * wird dabei dem Array 'pos' entnommen und * jeweils für beide Achsen eingesetzt. */ rect(pos[0], pos[0], rSize, rSize); rect(pos[1], pos[1], rSize, rSize); rect(pos[2], pos[2], rSize, rSize); rect(pos[3], pos[3], rSize, rSize); rect(pos[4], pos[4], rSize, rSize);
Jedes Array bietet die Möglichkeit über die Eigenschaft length seine Größe abzufragen. Die Schreibweise dazu sieht wiefolgt aus:
float[] pos = {0.0, 20.0, 40.0, 60.0, 80.0}; println (pos.length); // gibt '5' in der Konsole aus
Die Länge eines Arrays ist demnach eine Eigenschaft auf die mit der Punkt-Schreibweise zugegriffen werden kann (wie z.B. width oder height von Bildern, als img.width).
Modifizieren und Neudefinieren von Werten erfolgt bei Werten innerhalb eines Arrays wie bei den uns bekannten einfachen Variablen. Links des Istgleich (
befindet sich das Feld, wessen Wert wir ändern wollen. Bei einer Variable war dies die Nennung dieser durch den Variablennamen. Um ein Feld in einem Array anzusprechen, bedienen wir uns, wie oberhalb bereits erwähnt, der eckigen Klammern.
float[] pos = {0.0, 20.0, 40.0}; /* legt die Summe aus Feld zwei * und drei in Feld eins ab. */ pos[0] = pos[1] + pos[2]; /* definiert den Wert des dritten Feldes * mit dem Wert '91.41' neu */ pos[2] = 91.41; /* definiert den Wert des zweiten Feldes * mit einem Viertel des dritten Feldes neu */ pos[1] = pos[2] / 4;
Die häufigste Verwendung dieser Eigenschaft findet sich bei der Bearbeitung von Arrays mit Hilfe von for-Schleifen. Um ein Array, unabhänig vom Datentyp, einmal vollständig von Beginn bis Ende durchzulaufen sieht diese Schleife vom Aufbau immer gleich aus:
i bei 0i kleiner als die Anzahl ist. Unseren letzten Wert erreichen wir mit Arraylänge - 1.Alle drei Regeln kombiniert ergeben folgende Vorlage:
int[] array = {"Alles", "aus", "der", "Liste"};
for (int i=0; i < array.length; i = i + 1) {
print (array[i] + " ");
}
Im folgenden Beispiel wenden wir das Muster in zwei Formen an. Zuerst füllen wir jedes Feld des Arrays mit einem zufälligen Wert zwischen 0 und der Breite unseres Sketchfensters. Danach durchlaufen wir es ein zweites Mal und Positionieren unsere Quadrate an den durch random() festgelegten Positionen.
fill(0); stroke(0); float rSize = 20.0; // deklariere und initialisiere das Array float[] pos = new float[5]; /* Gehe mit Hilfe einer for-Schleife durch jede einzelne * Position des Arrays. Das Ende der for-Schleife wird * über die Länge-Eigenschaft des Arrays bestimmt. */ for (int i=0; i < pos.length; i++) { pos[i] = random(width); } // zeichne die Rechtecke nach dem selben Prinzip for (int i=0; i < pos.length; i++) { rect(pos[i], pos[i], rSize, rSize); }
In Lesson 8 haben wir uns mit Animationen beschäftigt. Eines der ersten Beispiele war die lineare Animation, ohne jegliche Beschleunigung, auf der x-Achse von links nach rechts. Dieses kleine Projekt dient uns als Basis um 150 Kreise die selbe Aktion ausführen zu lassen.
Wir übernehmen also den kompletten Quelltext und modifizieren als Erstes die Deklaration unserer globalen Variable xpos zum Speichern der Kreisposition. float xpos wird zu float[] xpos. Dieses Array ermöglicht uns 150 Werte in einer Variable ablegen zu können – jeweils eine pro Kreis. Gleiche Form, andere Name für die y-Achse, damit sich die Kreise nicht überlagern.
Im setup()-Block durchlaufen wir in der einer for-Schleife beide Arrays und legen mittels random() einen zufälligen Wert für die x- und y-Position jedes Kreises fest. Die Struktur des draw() bleibt bestehen. Eine weitere for-Schleife bewirkt die Positionsabfragen und Änderungen auf alle Kreise anzuwenden.
// gloable Arrays für die Ablage der Position float xpos[] = new float[150]; float ypos[] = new float[150]; float durchmesser = 30; void setup () { size(500, 180); smooth (); // Einmaliges Festlegen der x- und y-Position for (int i=0; i < xpos.length; i++) { xpos[i] = random (width); ypos[i] = random (durchmesser / 2, height - durchmesser); } } void draw () { // Hintergrund leeren background (255); // Führe die Schleife für jeden einzelnen // Kreis aus, angegeben durch die Länge von 'xpos' for (int i=0; i < xpos.length; i++) { // Position modifiziere xpos[i] = xpos[i] + 1; // Zurücksetzen der Kreisposition auf die linke // Bildschirmseite wenn die Position größer als // Bildschrimbreite + Durchmesser ist if (xpos[i] > width) { xpos[i] = 0; } fill (random (210, 255), random (25, 60), 0); // Zeichnen des Kreises an die aktuelle Position ellipse (xpos[i], ypos[i], durchmesser, durchmesser); } }
Beispiel Nummer zwei treibt das oben Durchgeführte auf die Spitze. Wir legen für alle Attribute: Position, Geschwindigkeit, Durchmesser und Farbe jeweils ein Array mit 100 Feldern an. Jedem Kreis gehört demnach ein Wert aus jedem Array. Die Startposition ist bei allen die Selbe (Sketchmitte). Durch die unterschiedlichen Geschwindigkeiten (können auch negativ sein) springt die Gruppe sofort auseinander und beginnt sich auf der Zeichenfläche zu verteilen. Wir setzen den Hintergrund nie mit background() zurück und erhalt deshalb diese komplex anmutende Collage.
// gloable Variable für // die Ablage aller Positionen float[] xpos = new float[100]; float[] ypos = new float[100]; // Positionsänderung pro Einzelbild float[] xGeschwindigkeit = new float[100]; float[] yGeschwindigkeit = new float[100]; // Farbe der Kreise color[] farbe = new color[100]; // Kreisdurchmesser der Kreise float[] durchmesser = new float[100]; void setup () { size(500, 240); smooth (); noStroke (); // einmaliges Festlegen aller benötigten // Werte zu Beginn des Programms for (int i=0; i < xpos.length; i++) { xpos[i] = width / 2; ypos[i] = height / 2; xGeschwindigkeit[i] = random (-1, 1); yGeschwindigkeit[i] = random (-1, 1); durchmesser[i] = random (10, 40); farbe[i] = color (random (100, 255), 20); } } void draw () { // Führe die Schleife für jeden einzelnen // Kreis aus, angegeben durch die Länge von 'xpos' for (int i=0; i < xpos.length; i++) { // rechter Fensterrand if (xpos[i] > width - durchmesser[i] / 2) { xGeschwindigkeit[i] *= -1; } // linker Fensterrand if (xpos[i] < durchmesser[i] / 2) { xGeschwindigkeit[i] *= -1; } // unterer Fensterrand if (ypos[i] > height - durchmesser[i] / 2) { yGeschwindigkeit[i] *= -1; } // oberer Fensterrand if (ypos[i] < durchmesser[i] / 2) { yGeschwindigkeit[i] *= -1; } // Position modifizieren, 'geschwindigkeit' // kann positiv oder negativ sein (siehe: * -1) xpos[i] = xpos[i] + xGeschwindigkeit[i]; ypos[i] = ypos[i] + yGeschwindigkeit[i]; // Zeichnen des Kreises an die aktuelle Position fill (farbe[i]); ellipse (xpos[i], ypos[i], durchmesser[i], durchmesser[i]); } }
Ähnlichen Effekt kennen wir schon aus vergangenen Kursstunden. Bei dem Spielen mit der Mausposition hatten wir statt den Hintergrund komplett vollfarbig zu füllen ein semitransparentes Rechteck über die gesamte Zeichenfläche gelegt. Nun, in Kombination mit background(), speichern wir die letzten 70 Koordinaten der Maus und positionieren an ihnen unsere Kreise. Zwei Arrays vom Typ float beinhalten wieder die x- und y-Koordinaten. Bei jedem draw()-Durchlauf verschieben wir alle gespeicherten Werte, in den Arrays xpos und ypos, um einen Index an den Anfang der Liste (Richtung 0). Dadurch vergessen wir die Informationen über die älteste Position. An das Ende der Listen kommen die aktuellen Koordinaten der Maus.
// globale Variablen zum Ablegen // der Mausposition(en) int[] xpos = new int[70]; int[] ypos = new int[70]; void setup(){ size(500, 200); smooth(); noStroke(); } void draw(){ background (255); /* wir verschieben in jedem Einzelbild * alle bisher gespeicherten Mauspositionen * um eins nach vorn in unserem Array. In * das letzte Feld kommt später die * aktuelle Position der Maus. */ for (int i=0; i< xpos.length - 1; i++) { xpos[i] = xpos[i + 1]; ypos[i] = ypos[i + 1]; } // aktuelle Mausposition in das letzte Feld speichern xpos[xpos.length - 1] = mouseX; ypos[xpos.length - 1] = mouseY; // Zeichnen aller Kreise for (int i=0; i < xpos.length; i++) { fill (0, 0, 0, 25); ellipse (xpos[i], ypos[i], i / 0.75, i / 0.75); } }