Behandelte Konzepte/Konstrukte: int, float, byte, short, long, double, boolean, char, mouseX, mouseY, pmouseX, pmouseY, width, height, frameRate, frameCount, keyPressed, key, keyCode, mousePressed, mouseButton, displayHeight, displayWidth

Lernziele

  • Sie können die Unterschiede zwischen statischem und aktivem Modus erklären
  • Sie können Interaktionen mit Mausbewegung, Maustasten und Tastatur-Tasten programmieren
  • Sie können Zufallszahlen einsetzen, z.B. beim Zeichnen
  • Sie können Text ins Grafikfenster zeichnen
  • Sie können Farben auch als Hexadezimalzahl angeben
  • Sie kennen die Funktionsweise von Variablen und können Informationen unterschiedlicher Typen (Zahl, Text etc.) speichern
  • Sie kennen die Datentypen in Processing und können diese erklären
  • Sie können Zuweisungen, inklusive enthaltener Variablen und Rechenoperationen, auflösen
  • Sie können mit Hilfe von Variablen eine Animation programmieren
  • Sie können den Unterschied zwischen einer globalen und einer lokalen Variable mit Hilfe des jeweiligen Skopus erkären
  • Sie können das Problem des Überschattens erklären
  • Sie kennen die verschiedenen Operatoren (Division, Modulo, Inkrement), können die Funktionsweise erklären (insbes. auch den Unterschied zwischen ganzzahliger und Float-Division) und können für jeden ein Anwendungsbeispiel angeben
  • Sie kennen die wichtigsten Systemvariablen (z.B. width und height) und setzen diese immer dann ein, wenn dies sinnvoll ist

Voraussetzungen

Sie sollten sicher im Umgang mit der Processing-Programmierumgebung sein und einfache Programme zum Zeichnen von Formen schreiben können Kapitel 1.

Neueste Aktualisierungen (zuletzt 03.10.2021)
  • 03.10.2021: Lernziele angepasst
  • 02.08.2021: Neue Kapitelnummerierung

Im letzten Kapitel haben wir bereits Grafik produziert. Jetzt bringen wir Bewegung ins Spiel. Und noch besser: Wir können die Bewegung beeinflussen, während sie stattfindet. Das nennen wir Interaktion. Ein wichtiges Werkzeug sind Variablen, die wir in der zweiten Hälfte des Kapitels kennen lernen.

2.1 Interaktion

Ein Programm wird normalerweise Zeile für Zeile abgearbeitet, aber in Processing gibt es noch einen Kniff, der es erlaubt, sehr bequem Animation und Interaktion einzubauen. In diesem Kapitel werden wir ersten Formen der Interaktion kennen lernen.

Statischer und aktiver Modus

Zunächst mal sollte man als Programmierer immer genau wissen, wie Processing den Code abarbeitet. Das schauen wir uns jetzt genauer an.

Video: Wie wird der Code abgearbeitet? (7:46)

Statischer Modus

In Processing können Sie einfach Befehle untereinander hinschreiben und das Ganze starten. Processing arbeitet dann alles zeilenweise ab. Das nennt man den statischen Modus. Ein Beispiel für ein Programm im statischen Modus:

println("Hello, world!");
ellipse(50, 50, 40, 40);

Noch ein Beispiel:

println("1");
println("2");
println("3");

Sie sehen auf der Konsole:

1
2
3

Hier wird nochmal deutlich, dass die Code-Zeilen von oben nach unten abgearbeitet werden. Wenn das Programm ganz unten ankommt, hält es an.

Aktiver Modus

Sobald sich irgendetwas bewegen soll oder Sie gern Interaktion einbauen möchten, müssen Sie den aktiven Modus verwenden. Dazu schreiben Sie zunächst mal diese Codezeilen:

void setup()
{
   println("und");
}

void draw()
{
   println("hopp");
}

Diese Zeilen definieren zwei so genannte Funktionen: setup und draw. Sobald Processing mindestens eine davon sieht, schaltet es in den aktiven Modus.

Sobald Sie entweder ein setup() oder ein draw() im Code haben, wird das Programm im aktiven Modus ausgeführt.

Was heißt hier aktiver Modus? Ganz einfach: Sie schreiben Ihren Code zwischen die geschweiften Klammern von jeweils setup und draw. Processing arbeitet dann Ihren Code in folgender Weise ab:

  1. zunächst alle Zeilen in setup
  2. dann alle Zeilen in draw
  3. dann alle Zeilen in draw
  4. dann alle Zeilen in draw
  5. usw.

Processing wiederholt also die Abarbeitung der draw-Zeilen und zwar so lange, bis Sie den Stop-Knopf drücken. Das ist es letztlich, was Animation und Interaktion möglich macht. Warum? Wenn Sie einen Kreis von links nach rechts wandern lassen möchten (Animation), dann tun Sie das, indem Sie den Kreis immer wieder zeichnen, jedes mal ein Stückchen weiter rechts. Das passiert dann eben in diesen draw-Zeilen. Ein Beispiel für Interaktion ist im Video zu sehen.

Code-Block

Der Teil, der von den geschweiften Klammern eingeschlossen wird, nennt man einen Code-Block. So ein Code-Block können Sie sich so vorstellen, dass die enthaltenen Zeilen zusammengepackt werden und wie eine Einheit "gesehen" werden. Alle Zeilen, die zum aktuellen Code-Block gehören, sind eingerückt, damit man den Code besser lesen kann.

void setup()
{  // hier beginnt der Code-Block
   println("wir"); // alles eingerückt!
   println("sind");
   println("ein Block");
}  // hier endet er

Verwenden Sie die Auto Format Funktion (Menu Edit) von Processing häufig, dann werden die Zeilen automatisch korrekt eingerückt (Tastenkürzel STRG-T bzw. CMD-T).

Achten Sie immer darauf, dass Ihre Code-Blöcke richtig eingerückt sind. Wenn der Code komplexer wird, macht fehlerhafte Einrückung den Code teils unleserlich.

Frame Rate

Wie häufig wird draw abgespielt? Wir messen dazu, wie oft draw pro Sekunde aufgerufen wird. In Analogie zum Film und zur Animation spricht man hier von frame rate, ein Aufruf/Abspielen von draw entspricht also einem "frame". Es wird auch von den frames per second (fps) gesprochen, das ist synonym zu frame rate.

Von Haus aus versucht Processing, das draw() 60 Mal pro Sekunde aufzurufen, d.h. frame rate = 60. Sie können das mit dem Befehl frameRate() ändern. Der folgende Code schreibt ein Mal pro Sekunde etwas auf die Konsole:

void setup()
{
   frameRate(1);
   println("und");
}

void draw()
{
   println("hopp");
}

Bedenken Sie, dass es auch immer sein kann, dass der Rechner langsamer agiert, d.h. es kann immer sein, dass die tatsächlich erreichte frame rate niedriger ist, weil der Rechner ausgelastet ist und es schlichtweg nicht schafft, schneller zu arbeiten. Die angegebene frame rate ist also eine Obergrenze für die tatsächliche frame rate.

Aktiv und statisch nicht mischen

Man darf aktiven und statischen Modus nicht mischen. Konkret heißt das: sobald eines der beiden Konstrukte (setup oder draw) hingeschrieben ist, darf man keine Befehle außerhalb von setup und draw setzen. Dies hier z.B. funktioniert nicht:

// aktiver Modus?
void setup()
{
   println("so geht das");
}

println("eher nicht"); // statischer Modus!

Stattdessen schreiben Sie Ihren Code entweder im statischen Modus:

println("so geht das");
println("richtig!");

Oder im aktiven Modus, wo nur ein setup() steht. Auch hier wird der Code nur einmal ausgeführt:

void setup()
{
   println("so geht das");
   println("richtig!");
}

Interaktion mit der Maus

Interaktion bedeutet, dass der Benutzer während des Programmlaufs - also nach dem Drücken von START und vor dem Drücken von STOP - einen Einfluss auf das Programm nehmen kann. Das passiert über Eingabegeräte, also Tastatur und Maus, später vielleicht auch über Sprache, Kamera, Spielecontroller und andere Sensorik (Kinect, Wiimote etc.). In diesem Abschnitt beschäftigen wir uns mit der Maus.

Processing ist so freundlich, uns zwei Platzhalter zur Verfügung zu stellen, die immer durch die aktuellen Mausposition (x und y) ersetzt werden. Dieses Konzept nennt man Variablen, das lernen Sie im nächsten Kapitel richtig kennen. Jetzt müssen Sie nur wissen, dass die Wörter mouseX und mouseY im Code immer durch die Mausposition ersetzt werden. So wie hier:

void setup() {
  size(200,200);
}

void draw() {
  ellipse(mouseX, mouseY, 20, 20);
}

Sie sehen, dass der Kreis immer der Maus folgt.

Interaktives Feld:

Wenn wir die Illusion erwecken wollen, dass der Kreis sich bewegt und nicht immer neu gezeichnet wird, müssen wir den Hintergrund immer neu befüllen und somit den "alten" Kreis (aus dem vorigen draw()- Aufruf) übermalen.

void setup() {
  size(200,200);
}

void draw() {
  background(200); // Bild löschen durch Übermalen
  ellipse(mouseX, mouseY, 20, 20);
}

Jetzt sehen wir immer nur einen Kreis:

Interaktives Feld:

Jetzt malen wir nur Punkte, ohne das Bild zu löschen:

void setup() {
  strokeWeight(2);
}

void draw() {
  point(mouseX, mouseY);
}

Interaktives Feld:

Wenn wir statt Punkten Linien zeichnen wollen, damit keine Lücken entstehen, brauchen wir noch mehr Infos, nämlich die Mausposition aus der jeweils vorherigen draw()-Runde. Die stellt uns Processing netterweise auch zur Verfügung und zwar mit pmouseX und pmouseY.

void setup() {
  strokeWeight(2);
}

void draw() {
  line(pmouseX, pmouseY, mouseX, mouseY);
}

Interaktives Feld:

Diese Art der Interaktion ist "kontinuierlich", da Ihr Programm im Grund ständig auf Ihren Input reagiert. Eine zeitlich punktuelle Art der Interaktion lernen Sie im nächsten Abschnitt kennen.

Interaktion mit Tasten

Die beiden Konstrukte setup() und draw() sind ganz praktisch. Sie erlauben mir, jeweils ein Stückchen Code zu formulieren, der unter bestimmten Bedingungen ausgeführt werden soll: Der Code in setup nur ganz am Anfang, der Code in draw immer wieder.

Wie reagieren wir auf den Druck einer Taste der Tastatur oder der Maustaste? Processing erlaubt Ihnen dazu, folgendes in Ihren Code zu schreiben (wir haben draw() hinzugefügt, damit wir im aktiven Modus sind):

    void draw() {} // nichts tun

    // Auf Taste reagieren (egal welche)
    void keyPressed() 
    {
       background(0);
    }
    

Der Teil "void keyPressed() ..." sieht so ähnlich aus wie setup() und draw(). Es handelt sich ebenfalls um eine Funktion. Auch hier wird wieder ein Code-Block definiert. Dieser Code-Block wird jedesmal aufgerufen, wenn eine Tastatur-Taste gedrückt wird. Im Bespiel wird der Hintergrund auf schwarz gestellt.

Analog können Sie folgenden Code für die Maustaste einbauen. Fügen Sie dazu folgendes hinzu:

    void mousePressed() 
    {
       background(255, 0, 0);
    }
    

Der Bespielcode stellt den Hintergrund auf rot, wenn die Maus gedrückt wird.

Diese beiden "Ereignisse", also dass eine Taste oder Maustaste gedrückt werden, nennt man auch Events (engl. für Ereignis). Beachten Sie, dass Events zeitlich klar definiert sind und genau einmal vorkommen. Im Gegensatz dazu sind z.B. die Informationen aus mouseX und mouseY immer vorhanden und werden oft in einen kontinuierlichen Zusammenhang gestellt, z.B. mit der Position eines Kreises etc.

Damit Processing auf Tasten-Events reagiert, müssen Sie ein draw() im Code haben. Wenn nur setup() vorhanden ist, bleibt die Codeausführung nach Abarbeiten von setup() stehen.

Übungsaufgaben

2.1 a) Zeitmuster

Schreiben Sie ein Programm, dass folgende Ausgabe auf der Konsole erzeugt. Dabei sollen auf der zweiten Zeile die Zeichen im Halb-Sekunden-Takt erscheinen.

Start
Tick.Tick.Tick.Tick.
			
Tipp
Verwenden Sie println() und print(). Beachten Sie den Unterschied zwischen den zwei Befehlen. Verwenden Sie frameRate(), um die Taktung richtig einzustellen.

2.1 b) Mauskoordinaten

Schreiben Sie ein Programm, dass ständig die aktuelle x-Koordinate der Maus auf der Konsole ausgibt. Das sieht z.B. so aus:

69
71
72
72
72

2.1 c) Interaktive Linie

Programmieren Sie eine Linie, bei der ein Endpunkt immer der Maus folgt.

2.1 d) Rechteck-Quiz

Welchem Prinzip folgt das Quadrat in dem interaktiven Feld?

Finden Sie es heraus und programmieren Sie es nach.

2.1 e) Fadenkreuz

Zeichnen Sie zwei Linien, die parallel zur x- bzw. y-Achse sind und immer der Maus folgen. Der Schnittpunkt liegt genau auf dem Mauszeiger.

Tipp: Programmieren Sie zunächst eine der beiden Linien, dann wird es Ihnen leicht fallen, die zweite hinzuzufügen.

2.1 f) Linie und Kreis

Zeichnen Sie eine Linie, die parallel zur y-Achse verläuft und immer der Maus folgt. Im Bereich zwischen der Linie und dem linken Rand soll mittig ein Kreis gezeichnet werden, der links und rechts genau anstößt.

2.1 g) Radiergummi

Schreiben Sie ein Programm, wo ein schwarzes Quadrat in der Mitte steht und die Maus als Radiergummi (Durchmesser 15) fungiert.

2.1 h) Symmetrische Linie

Programmieren Sie eine Linie, deren eines Ende der Maus folgt, das andere Ende liegt immer punktgespiegelt auf der anderen Seite des Mittelpunkts.

Tipp
Sie benötigen nur einen einzigen line()-Befehl. Welches ist der richtige Startpunkt?

2.1 i) Rechteck-Ecke

Zeichnen Sie ein Rechteck in die Mitte des Fensters, wo immer eine Ecke der Maus folgt.

Tipp
Überlegen Sie, welcher Zeichenmodus für Rechtecke hier am besten geeignet ist (es ist nicht CENTER).

2.1 j) Kreisrand *

Programmieren Sie einen Kreis, dessen Mittelpunkt links oben (bei 0, 0) sitzt und dessen Rand immer auf dem Mauszeiger liegt.

Tipp

Verwenden Sie den Satz von Pythagoras. Verdeutlichen Sie sich mit Hilfe einer Zeichnung, wo der Kreis ist, welche Informationen Sie haben (Mausposition) und welche Information Sie berechnen müssen.

Die Wurzel ziehen Sie mit dem Befehl sqrt(), engl. für square root.

2.1 k) Position zu Graustufe

Ein mittig positioniertes Quadrat soll seinen Grauwert ändern, je nachdem, wo sich die Maus befindet. Ist die Maus ganz links, ist das Quadrat schwarz. Ist sie recht, wird das Quadrat weiß.

Hinweis: Bei einer Division mit einer ganzen Zahl (z.B. 10) müssen Sie 10.0 schreiben, um zu signalisieren, dass das Ergebnis eine Kommazahl sein soll (sonst wird das Ergebnis zu einer ganzen Zahl gerundet).

Tipp
Der Grauwert muss in einem Bereich von 0 bis 255 liegen. In welchem Bereich liegt der Wert der Maus-Position? Um die Position auf den Grauwert zu übertragen (man spricht hier auch von Mapping), benötigen Sie den Dreisatz.

2.1 l) Kreissegment

Programmieren Sie das folgende Verhalten eines Kreisrands.

Tipp
Verwenden Sie den Befehl arc. Testen Sie, welche Richtung (x oder y) ausschlaggebend für die Interaktion ist. Als nächstes müssen Sie einen Winkel errechnen, der von 0 bis 2*PI läuft.

2.1 m) Zeiger *

Programmieren Sie folgenden Zeiger. Versuchen Sie erst herauszufinden, wie genau der Zeiger "funktioniert".

Tipp
Sie müssen hier mal wieder sin() und cos() bemühen. Wie in der vorigen Aufgabe müssen Sie einen Winkel im Bereich 0 ... 2*PI aus einer anderen Größe herleiten.

2.1 n) Einfacher Stempel

Schreiben Sie ein Programm, wo das Fenster zunächst leer ist. Sobald Sie eine Taste der Tastatur drücken, erscheint ein Quadrat in der Mitte.

2.1 o) Löschen

Schreiben Sie ein Programm, wo zunächst ein Kreis in der Mitte zu sehen ist. Sobald Sie eine Maustaste drücken, wird der Kreis gelöscht.

2.1 p) Mausstempel

Schreiben Sie ein Programm, wo bei jedem Mausklick ein Quadrat (20x20) und bei jedem Tastaturdruck ein Kreis (Durchmesser 20) gezeichnet wird - jeweils an der aktuellen Mausposition.

(Interaktives Feld. Erst anklicken, dann interagieren.)

Tipp
Ihr draw() muss leer sein, damit Ihre Formen nicht ständig überzeichnet werden.

2.1 q) Ampel

Schreiben Sie ein Programm, das ein graues Feld zeigt. Bei Tastendruck wird das Feld rot, bei Mausklick grün.

(Interaktives Feld. Erst anklicken, dann interagieren.)

Tipp
Verwenden Sie keyPressed() und mousePressed().

Zusammenfassung

  • Statischer Modus: Code wird zeilenweise von oben nach unten abgearbeitet. Ist die letzte Zeile verarbeitet, ist das Programm zu Ende.
  • Aktiver Modus: Sobald Sie eines der Konstrukte setup() oder draw() hinzufügen, startet Processing das Programm im aktiven Modus. Das bedeutet, dass zunächst der Code in setup() 1x abgearbeitet wird (von oben nach unten). Dann wird der Code in draw() immer wieder abgearbeitet, auch jeweils von oben nach unten. Die Häufigkeit, mit der draw() pro Sekunde durchlaufen wird, nennt man Frame Rate und lässt sich mit dem Befehl frameRate() ändern.
  • Modus nicht mischen: Wenn Sie den aktiven Modus anstreben, darf außerhalb der Code-Blöcke von setup() und draw() kein Code stehen.
  • Interaktion mit der Maus: Die aktuelle Mausposition steht, sofern die Maus sich über dem Grafikfenster befindet in den Variablen mouseX und mouseY. Man muss sich vorstellen, dass die beiden Wörter in dem Moment, wo die Code-Zeile verarbeitet wird, durch den aktuellen Zahlenwert ersetzt werden. Die beiden Variablen erlauben es, interaktive Zeichnungen zu programmieren, wo die Mausposition beispielsweise zur Bestimmung von Position, Größe oder Farbe eingesetzt wird.
  • Interaktion mit Tasten: Ein punktuelles Ereignis wie das Herunterdrücken einer Taste nennt man ein Event. In Processing kann man auf ein Event reagieren, indem man spezielle Funktionen schreibt. Möchten Sie auf den Druck einer beliebigen Taste auf der Tastatur reagieren, dann schreiben Sie die Funktion keyPressed(). Der Code dieser Funktion wird immer dann ausgeführt, wenn eine Taste gedrückt wird. In gleicher Weise können Sie die Funktion mousePressed() schreiben. Dieser Code wird immer dann ausgeführt, wenn eine Maustaste gedrückt wird. Sie können noch nicht zwischen verschiedenen Tastatur- oder Maustasten unterscheiden. Dazu benötigen Sie die If-Anweisung.

2.2 Zufall, Text, Farben II

Zufall

Sie können sich nicht entscheiden, welche Graustufe Sie für Ihr Objekt wählen sollen? Fragen Sie einfach den Zufall! In Processing gibt es dafür das Kommando random(). An der Stelle, wo Sie diesen Befehl hinschreiben, wird eine Zufallszahl eingesetzt.

fill(random(256)); // zufällige Zahl aus {0,1,...,255}
ellipse(50,50,50,50);

Bei jedem Neustart sehen Sie eine andere Graustufe.

Wenn Sie nur random() schreiben, bekommen Sie eine float-Zahl zwischen 0 und 1 (exklusive der 1). Schreiben Sie zwei Zahlen, z.B. random(-10, 10) , so bekommen Sie eine Zufallszahl dazwischen (auch float), inklusive der -10 und exklusive der 10.

Text in der Grafik

Damit Sie auch über Text mit Ihrem Benutzer kommunizieren können, gibt es den Befehl text . Dieser setzt einen Text (nennt man auch String) an die angegebene Stelle.

size(200, 200);
fill(0); // Textfarbe schwarz
text("sei gegrüßt!", 100, 100);

// Orientierungskreuz
stroke(255);
line(100,0,100,200);
line(0,100,200,100);

Ergibt:

Allgemein lautet der Befehl also:

text(string, basisX, basisY);

Beachten Sie, dass die Textfarbe mit fill() gesetzt wird (nicht etwa mit stroke).

Sie sehen im Beispiel, wo die Positionsangabe genau die Mitte des Fensters ist, dass sich diese Koordinatenangabe auf den linken Startpunkt der Grundlinie bezieht.

Diese Ausrichtung (Alignment) kann man mit dem Befehl textAlign in Kombination mit den Werten CENTER, LEFT, RIGHT ändern, z.B. mittig mit:

textAlign(CENTER);
text("sei gegrüßt!", 100, 100);

Mit textSize gibt man die Punktgröße an:

textSize(32);
textAlign(CENTER);
text("sei gegrüßt!", 100, 100);

Farben II

Im ersten Kapitel haben Sie gelernt, wie man mit drei Werten für Rot, Grün und Blau im RGB-Farbraum eine Farbe benennt. Hier wollen wir zwei weitere Methoden beschreiben, Farben zu verwenden.

HSB-Farbraum

Neben RGB gibt weitere Farbräume, die für manche Situationen besser geeignet sind. Denn im RGB-Raum lassen sich viele Farben nicht intuitiv erschließen, z.B. wie man von Rot=(255, 0, 0) zu Orange=(255, 150, 0) kommt - denn dies sind doch "ähnliche" Farben, aber dass man dafür den Grünwert hochdrehen muss, ist vielleicht nicht jedem direkt klar.

Der sogenannt HSB-Farbraum versucht, besser "lesbar" zu sein, d.h. ähnliche Farben sollten dicht beieinander sein. HSB steht für Hue (Farbton), Saturation (Sättigung) und Brightness (Helligkeit). Der wichtigste Wert ist zunächst mal Hue: Mit einem einzigen Wert legt man den prinzipiellen Farbton anhand einer Skala von 0...360 fest (man sieht, dass Rot und Orange tatsächlich nah beieinander liegen):

Man stellt sich den Farbton auch als Kreis vor, daher 0° bis 360°, um eine Umdrehung zu machen. In einer Darstellung als Kreisscheibe nimmt die Sättigung zur Mitte hin ab. Üblich ist eine Darstellung als 3D-Kegel. Dabei wird die Helligkeit als Achse nach unten dargestellt (Abb. aus Wikipedia):

In der Abbildung steht V (Value) statt B, aber die Konzepte sind vergleichbar.

In Processing werden die Wert H, S und B wieder mal im Bereich 0..255 dargestellt. Um den HSB-Farbraum zu verwenden, schreiben Sie colorMode(HSB);. Hier ein Beispiel, wie Sie interaktiv die Farben explorieren können. Da wir den HSB-Farbraum verwenden, können wir mit einem Wert (x-Achse) den kompletten Farbton-Umfang durchlaufen:

void setup() {
  size(255, 100);
  colorMode(HSB);
}

void draw() {
  background(mouseX, 255, 255);
}

(Fahren Sie mit der Maus über das Feld)

Farbwähler und Hexadezimal

Viele Farbwähler in Grafikprogrammen (Photoshop etc.) verwenden das HSB-Modell. Auch Processing bietet im Menüpunkt Tools > Color Selector einen Farbwähler an:

Der vertikale Farbstreifen zeigt den Hue-Wert und das linke Farbfeld zeigt den gewählten Farbton an und lässt anhand zweier Dimensionen Sättigung (x-Achse) und Helligkeit (y-Achse) einstellen.

Zusätzlich sehen Sie die numerischen Werte als HSB (obere drei Felder) und RGB (untere drei Felder). Wichtig: Die RGB-Werte sehen Sie auch als Hexadezimalzahl, gekennzeichnet durch den #-Tag.

Den Hexadezimalwert können Sie direkt in Processing verwenden!

background(#3B3A9B);
fill(#FFC917);
rect(25,25,50,50);

Dies ist sicherlich die schnellste und genaueste Art, in Processing Farben zu wählen und einzusetzen.

Hexadezimalsystem

Das Hexadezimalsystem ist ein alternatives Zahlensystem. Statt 10 Ziffern wie im Dezimalsystem hat man im Hexadezimalsystem 16 Ziffern. Man zählt wie folgt: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F.

Wenn mann bei F ankommt, zählt man die nächste Stelle hoch und kommt also bei 10 an. Weiter geht es mit 11, 12, 13, 14, 15, 16, 17, 18, 19, 1A, 1B, 1C, 1D, 1E, 1F, 20, 21 und so fort. Eine "10" im Hexadezimalsystem ist also eine "16" im Dezimalsystem. Um hier nicht durcheinander zu geraten, kann man das Zahlensystem kennzeichnen, z.B. #10 für die Hexadezimalzehn und 10 für die Dezimalzehn.

Lassen Sie uns nochmal durchzählen:

hexadezimal dezimal
#0 0
#1 1
#2 2
#3 3
#4 4
#5 5
#6 6
#7 7
#8 8
#9 9
#A 10
#B 11
#C 12
#D 13
#E 14
#F 15
#10 16
#11 17
... ...

Warum soll man überhaupt ein anderes Zahlensystem wählen als das Dezimalsystem? Auf der niedrigsten Ebene arbeiten Computer mit sogennanten Bits. Diese haben nur zwei Werte, nämlich 0 und 1. Es handelt sich wiederum um ein alternatives Zahlensystem (Binärsystem) mit den Ziffern 0 und 1. Man zählt hier wie folgt: 0, 1, 10, 11, 100, 101, 110 und so fort.

binär dezimal
0 0
1 1
10 2
11 3
100 4
101 5
110 6
... ...

Der Computerspeicher setzt sich aus vielen solcher Bits zusammen. Man gruppiert nun immer 8 Bits zu einem Byte. Mit 8 Bits bedeutet im Dezimalsystem den Bereich 0, 1, ..., 255. Im Hexdezimalsystem sieht dieser Bereich so aus: 0, 1, ..., FE, FF. Das heißt zwei Hexadezimalstellen decken genau den Wertebereich von einem Byte ab.

binär dezimal hexadezimal
00000000 0 #00
00000001 1 #01
00000010 2 #02
... ... ...
11111110 254 #FE
11111111 255 #FF

Für eine RGB-Farbe benötigen Sie drei Byte, also z.B. drei Dezimalzahlen zwischen 0 und 255. Im Hexadezimalsystem liegen diese drei Zahlen zwischen #0 und #FF. Deshalb kann man mit einer sechs-stelligen Hexadezimalzahl jeden RGB-Wert ausdrücken: die erste zwei Stellen sind der R-Wert, die zweiten zwei der G-Wert, die letzten zwei der B-Wert.

Übungsaufgaben

2.2 a) Zufall: Kästchen

Bei Mausklick soll an zufälliger Position ein Kasten erscheinen, mit zufälliger Breite und Höhe (je zwischen 5 und 40 Pixel)

(Interaktives Feld. Erst anklicken, dann interagieren.)

2.2 b) Zufall: Farbe

Schreiben Sie ein Programm, das ein graues Feld zeigt. Bei Mausklick wird das Feld mit einer zufälligen Farbe eingefärbt.

(Interaktives Feld. Erst anklicken, dann interagieren.)

2.2 c) Text: Textmauszeiger

An der aktuellen Mausposition soll der Schriftzug "maus" erscheinen (mittig über Maus, Schriftgröße 20).

(Interaktives Feld. Erst anklicken, dann interagieren.)

2.2 d) Zufall + Text

Bei Mausklick soll an einer zufälligen Position, mit zufälliger Größe (zwischen 5 und 30), mit zufälligem Grauton, die Schrift "hallo" erscheinen.

(Interaktives Feld. Erst anklicken, dann interagieren.)

2.2 e) ColorMode: Farbe und Intensität

Auf einer Fläche von 255x100 soll folgendes passieren: auf der Achse von links nach rechts soll sich die Farbe ändern (Rot, Orange, Gelb usw.), auf der Achse von oben nach unten soll sich die Sättigung ändern (oben = ungesättigt, unten = gesättigt).

(Interaktives Feld.)

2.2 f) ColorMode: Zufallsfarbe

Bei Mausklick soll die Hintergrundfarbe zufällig neu gesetzt werden. Die Sättigung der Farbe hängt allerdings von der Mausposition (links-rechts) zum Zeitpunkt des Klicks ab (links = ungesättigt, rechts = gesättigt).

(Interaktives Feld. Erst anklicken, dann interagieren.)

Tipp
Bei der Sättigung müssen Sie die Mausposition auf den Bereich 0..255 umrechnen (Maus geht ja von 0..99). Verwenden Sie dazu einen Dreisatz.

2.3 Variablen: Basics

Einführung in die Variablen

Wir definieren zunächst, was wir im Kontext von Computerprogrammen mit einer Variablen meinen.

Variable

Eine Variable in einem Computerprogramm ist ein Behälter für Werte, zum Beispiel für eine Zahl oder für ein Stück Text.

Die Variable hat einen Namen, der diesen Wert repräsentiert. Der Wert einer Variable kann sich - während das Programm läuft - ändern.

Video: Einführung in Variablen (8:23)

Variablen können Daten speichern. Um dies sinnvoll zu tun, benötigen wir für jede Variable:

  • einen Typ, der bestimmt, was Sie in der Variable speichern können, z.B. eine ganze Zahl, eine Kommazahl oder einen Buchstaben.
  • einen Namen, damit Sie später darauf zurückgreifen können und verschiedene Variablen auseinander halten können.

Deklaration

Um eine Variable zu verwenden, müssen Sie sie zunächst die Variable erstellen. Das nennt man deklarieren. Hier deklarieren wir eine Variable mit Namen foo vom Typ int (steht für integer, engl. für "ganze Zahl"):

int foo; // Deklaration

Für Kommazahlen oder genauer Gleitkommazahlen (engl. floating point numbers), verwenden wir den Typ float:

float krumm; // Deklaration

Allgemein sieht eine Deklaration wie folgt aus:

TYP NAME;

Die Deklaration reserviert Platz im Speicher, um den Wert einer Variablen abzulegen. Das ist auch einer der Gründe, warum ein Typ angegeben wird: unterschiedliche Typen verbrauchen unterschiedlichen Speicherplatz.

Variablenamen werden immer klein geschrieben.

Zuweisung

Im nächsten Schritt legen Sie den Inhalt der Variable fest, das nennt man Zuweisung. Dazu verwendet man das Gleichheitszeichen. Wichtig: Es handelt sich nicht um die Gleichheit aus der Mathematik! Bei der Zuweisung steht links vom Gleichheitszeichen immer ein Variablenname und rechts steht z.B. eine Zahl oder ein mathematischer Ausdruck wie 3 + 2. Die rechte Seite wird ausgewertet und das Ergebnis in die Variable hineingeschrieben.

foo = 5; // einfache Zuweisung

Gleitkommazahlen werden im englischsprachigen Raum mit Punkt geschrieben:

krumm = 9.3;

Auf der rechten Seite kann ein beliebig komplexer arithmetischer Ausdruck stehen.

foo = 5 + 2 * (10 - 5);

Dieser wird zunächst gemäß der bekannten Regeln (Punkt vor Strich, Klammerung) ausgewertet.

foo = 15

Erst wenn die rechte Seite fertig ausgewertet ist, wird der Variablen foo der Wert, hier also 15, zugewiesen.

Eine Zuweisung mit Gleichheitszeichen bedeutet nicht mathematische Gleichheit. Stattdessen ist folgender Prozess zu beachten: Zuerst die rechte Seite auswerten, erst dann das Result in die Variable (linke Seite) schreiben.

Verwendung

Schließlich kann man die Variabe verwenden. Hier wird der Variablenname durch den Inhalt der Variable ersetzt, das nennt man auch Substitution.

println(foo); // Verwendung im Befehl println
rect(foo, 40, 30, 30); // Verwendung im Befehl rect

Stellen Sie sich vor, dass Processing vor der Verarbeitung einer Zeile zunächst alle Variablen durch den jeweils aktuellen Wert der Variable ersetzt.

In unserem Bespiele hat die Variable foo den Wert 5. Aus der Zeile

rect(foo, 40, 30, 30);

macht Processing also zunächst

rect(5, 40, 30, 30);

Jetzt wird die Zeile "normal" ausgewertet.

Zuweisung mit Variablen

Variablen können auch auf der rechten Seite von Zuweisungen stehen:

boo = foo + 10;

Bevor die Addition (oder andere Operationen) ausgewertet wird, werden alle Variablen auf der rechten Seite ersetzt (substituiert) durch ihre jeweils aktuellen Werte.

boo = 5 + 10;

Im nächsten Schritt wird der arithmetische Ausdruck auf der rechten Seite ausgewertet:

boo = 15;

Jetzt kann die Zuweisung erfolgen: boo erhält den Wert 15.

Variablenwert erhöhen

Wie Sie später noch sehen werden, kommt es sehr häufig vor, dass Sie den Wert einer Variablen um einen Betrag erhöhen wollen. Zum Beispiel: Variable x speichert eine x-Koordinate und hat den Wert 10, diesen möchten Sie um 1 erhöhen. Zunächst deklarieren wir die Variable:

int x;
x = 10;

Häufiger Fehler: Sie schreiben einfach

x + 1; // falsch

In diesem Code wird aber der Wert der Variable x nicht verändert. Stattdessen müssen Sie schreiben:

x = x + 1; // richtig

Hier passiert folgendes. Im ersten Schritt wird der Wert von x eingesetzt:

x = 10 + 1;

Im nächsten Schritt wird die Addition aufgelöst:

x = 11;

Erst jetzt wird das Gleichheitszeichen, also die Zuweisung, ausgeführt, und x erhält den neuen Wert 11.

Deklaration und Zuweisung auf einer Zeile

Sie können Deklaration und Zuweisung auch verkürzt auf eine Zeile schreiben. Beachten Sie dennoch, dass hier zwei Dinge passieren:

int bla = 50; // zwei auf einen Streich
float blubb = 1.5;

Die allererste Zuweisung bei einer Variable nennt man auch die Initialisierung der Variable.

Globale Variablen

Wo steht die Deklaration der Variablen im Code? Wir beschäftigen uns hier mit sogenannten globalen Variablen. Auch wenn die Bedeutung der Begriffe "global" und "sichtbar" erst später klar wird, definieren wir hier schonmal den Begriff der globalen Variable.

Globale Variable

Eine globale Variable ist eine Variable, die im gesamten Computerprogramm sichtbar ist.

Alle (globalen) Variablen sollten im aktiven Modus immer ganz oben im Code stehen.

// Alle Variablen ganz oben,
// also vor setup und draw
int x = 0;
int y = 0;

void setup() {}

void draw() {
   ellipse(x, y, 20, 20);
   x++;
   y++;
}

Globale Variablen stehen im aktiven Modus immer ganz oben im Code, außerhalb von setup, draw und anderen Funktionen.

Welche Namen sind erlaubt?

Wenn Sie eigene Variablen definieren, müssen Sie ein paar Regeln beachten, harte und weiche. Wichtig ist zunächst, dass Groß- und Kleinschreibung unterschieden wird, d.h. dass meineVariable und meinevariable zwei unterschiedliche Variablen sind, die auch gern im selben Code ko-existieren dürfen.

Verwenden Sie im Code keine Umlaute (ü, ö, ä) und kein scharfes S (ß). Verwenden Sie keine Akzente aus anderen Sprachen wie Französisch oder Spanisch. Schreiben Sie also zaehler statt zähler und fussPosition statt fußPosition.

Harte Regeln (Syntax)

Die Verletzung einer harten Regel führt zu einer Fehlermeldung, d.h. diese Regeln sind Teil der Syntax von Processing bzw. Java:

Variablennamen...

  • dürfen nicht mit einem Schlüsselwort der Sprache Java identisch sein, z.B. dürfen Sie nicht int oder float als Variablenname verwenden
  • bestehen aus Ziffern (0-9), Buchstaben (a-z, A-Z) und zwei weiteren Zeichen (Unterstrich und $), die aber besser nicht verwendet werden
  • dürfen nicht mit einer Ziffer beginnen
  • dürfen keine Leerzeichen enthalten und auch keinen Bindestrich (würde als Minus interpretiert werden)

Beispiele:

// Syntaktisch falsch:

007
meine variable
float
meine-variable

// Syntaktisch korrekt:

take5
this_is_for_you
iam007
MEINEVARIABLE

Weiche Regeln (Konventionen)

Neben den harten Regeln gibt es einige Konventionen, die Sie einhalten sollten, um nicht direkt als Programmieranfänger ertappt zu werden.

Variablennamen...

  • beginnen immer mit Kleinbuchstaben
  • folgen der CamelBack-Regelung, wenn Einzelwörter verbunden sind (z.B. meineNeueVariable)
  • sind möglichst aussagekräftig (z.B. hintergrundFarbe statt hg)

Beispiele:

// verletzt Konventionen (obwohl syntaktisch korrekt):

MeineVariable // groß geschrieben!
MEINEVARIABLE // GROSS GESCHRIEBEN!
meine_variable
mv

// entspricht Konventionen:

take5
thisIsForYou
iAm007

Datentypen

Der (Daten-)Typ einer Variable bestimmt, welche Inhalte die Variable speichern kann.

Die meisten der folgenden Datentypen werden primitive Datentypen genannt (Ausnahme: String). Die primitiven Typen erkennt man daran, dass sie klein geschrieben sind (int, float, ...), wohingegen nicht-primitive Datentypen (hier: String) groß geschrieben werden. Den genauen Unterschied lernen wir erst später kennen. Für Sie liegt der Unterschied lediglich in der Groß-/Kleinschreibung.

Alles über primitive Variablen von Oracle (englisch): Primitive Data Types

Zahlen

In Programmiersprachen unterscheidet man zwischen ganzen Zahlen und Kommazahlen (auch: Gleitkommazahlen).

Ganze Zahlen

Ganze Zahlen sind Zahlen ohne Nachkommestellen, im Englischen integer genannt. Es gibt mehrere Typen, die ganze Zahlen speichern, je nachdem wie groß/klein die Zahlen sind, die man speichern möchte. In der Regel verwendet man int .

TypBeschreibungWertebereich
byte sehr kleine Zahl (8 Bit)-128 bis 127
short kleine Zahl (16 Bit)-32768 bis 32767
int große Zahl (32 Bit)-2 147 483 648 bis 2 147 483 647
long noch größere Zahl (64 Bit)ca. -9 * 10^18 bis 9 * 10^18

Beispiel:

int foo;
foo = 50;
rect(boo, boo, 20, 20);

Bei Zahlen muss Processing manchmal "raten", ob Sie eine ganze Zahl meinen oder eine Gleitkommazahl. Prinzipiell gilt: Wenn Sie eine Zahl ohne Komma (bzw. Punkt) schreiben, interpretiert Processing die Zahl als ganze Zahl vom Typ int.

Gleitkommazahlen

Gleitkommazahlen sind Zahlen mit Nachkommastellen (auch Dezimalzahlen genannt). In Processing verwenden Sie meistens float , später in Java werden Sie meistens double verwenden.

Schreiben Sie Gleitkommazahlen - so wie in England und USA üblich - mit einem Punkt. Also 1.5 statt 1,5.

Bei Gleitkommazahlen unterscheiden sich die beiden Typen nicht darin, dass der Wertebereich größer ist, sondern in der Anzahl der Dezimalstellen, die gespeichert werden können. Stellen Sie sich die Rechnung 10/3 vor. Der Rechner kann nicht "3 Komma Periode 3" darstellen, indem er unendlich viele Nachkommastellen speichert. In der Tabelle sehen Sie, wie viele Stellen insgesamt gespeichert werden können.

TypBeschreibungWertebereichStellen
float Zahl mit Nachkommastellen (32 Bit) z.B. -0.5, 0, 109.333 7 bis 8
double Zahl mit mehr Nachkommastellen (64 Bit)wie oben 15 bis 16

Beispiel:

float x;
x = 75.0 / 2;
ellipse(x, 50, 30, 30);

Auch hier muss Processing oft "raten", ob Sie eine ganze oder eine Gleitkommazahl meinen. Prinzipiell gilt: Wenn Sie eine Zahl mit Nachkommastellen schreiben, interpretiert Processing die Zahl als Gleitkommazahl vom Typ float.

Wenn Sie z.B. die Zahl 5 als Gleitkommazahl interpretiert haben möchten, müssen Sie 5.0 schreiben.

Eine weitere Schreibweise ist, ein "f" für float oder ein "d" für double an die Zahl anzuhängen. Zum Beispiel:

println(10/3f); // 3 als float
println(10/3d); // 3 als double
3.3333333
3.3333333333333335

Hier sieht man auch die unterschiedliche Präzision. Java hält sich an die IEEE 754 Standarddarstellungen für binäre Gleitkommazahlen in Computern.

Umwandlung zwischen int und float

Es gibt Situationen, da wollen Sie aus einer ganzen Zahl (5) eine Gleitkommazahl (5.0) machen und umgekehrt. Processing bietet dazu zwei Befehle int() und float() an.

Zum Beispiel liefert der Befehl random() eine float-Zahl zurück. Wenn Sie aber lieber eine int-Zahl hätten, schreiben Sie:

int x = int(random(100));

Dabei wird der ganzzahlige Anteil genommen, d.h. bei 3.9 wird das Ergebnis von int(3.9) eine 3 sein. Es wird also nicht gerundet.

Umgekehrt können Sie mit float(5) aus der 5 eine Gleitkommazahl (5.0) machen. Das ist natürlich hauptsächlich dann sinnvoll, wenn Sie eine int-Variable z haben, die Sie mit float(z) in einen Float-Wert umwandeln. Dies kann z.B. bei Division sinnvoll bzw. notwendig sein (siehe Abschnitt zu ganzzahliger Division).

Wahrheitswerte

Wahrheitswerte lernen Sie in Zusammenhang mit If-Anweisungen kennen. Ein Wahrheitswert ist entweder wahr (true) oder falsch (false). Es gibt also zwei mögliche Werte für eine solche Variable. Der Typ heißt boolean in Hommage an den englischen Logiker George Boole (1815-1864). Dabei ist boolean ein Adjektiv, also wie im Deutschen "eine boole'sche Variable".

TypBeschreibungWertebereich
boolean Wahrheitswert true und false

Beispiele:

boolean iLikeJava;
boolean iLikeC;
iLikeJava = true;
iLikeC = false;
println("Java: " + iLikeJava);
println("C: " + iLikeC);

Zeichen

In Processing gibt es einen Datentyp für einzelne Zeichen. Ein Zeichen ist z.B. ein Buchstabe wie a, A, m, Z oder auch ein Sonderzeichen wie #, ! oder /. Im Englischen heißt Zeichen character, daher heißt der Datentyp char.

TypBeschreibungWertebereich
char einzelnes Zeichen (z.B. Buchstabe) z.B. 'a' oder '%'

Sie können ein einzelnes Zeichen in einer Variable abspeichern. Das Zeichen wird immer mit einfachen Anführungszeichen umgeben:

char c1 = 'a';
char c2 = '5';
char c3 = '%';
println(c1 + c2 + c3);

Intern wird ein Zeichen als ganze Zahl repräsentiert. Diese Zahl entspricht einer standardisierten Tabelle. Für die handelsüblichen Buchstaben ist das zum Beispiel der ASCII-Code (ohne Umlaute). ASCII steht für "American Standard Code for Information Interchange" und ist ganz einfach eine Tabelle, wo für jedem Buchstaben/Zeichen eine Zahl zugeordnet wird. Heutzutage verwendet man das Unicode-System, welches für fast alle Sprachen der Welt entsprechende Buchstaben-/Zeicheneinträge hat. Eine konkrete Enkodierung von Unicode ist z.B. UTF-8.

Wir werden diesen Datentypen selten benutzen, aber wir sehen ihn im nächsten Kapitel wieder, wenn es darum geht, auf verschiedene Tasten zu reagieren.

Strings (Text)

String ist englisch für "Schnur" oder "Kette". Das deutet darauf hin, dass ein String eine Aneinanderreihung von Zeichen (engl. character) ist. Man sagt deshalb auch Zeichenkette. Der Typ String wird im Gegensatz zu int, float, char ... groß geschrieben. Warum das so ist, erfahren Sie, sobald Sie Klassen und Objekte kennenlernen.

In String-Variablen können Texte ablegt werden. Der Text wird mit doppelten Anführungszeichen gekennzeichnet:

String foo = "Dies ist ein Text!";

Sie haben Strings bereits im Zusammenhang mit println() gesehen:

println("hallo");
hallo

Genauso könnten Sie schreiben:

String message = "hallo";
println(message);

String-Konkatenation

Sie können Strings "zusammenkleben", dies nennt man String-Konkatenation und funktioniert mit dem normalen Plus-Zeichen:

String message = "hallo";
println(message + " bob");
hallo bob

Das Plus-Zeichen bekommt also im Zusammenhang mit Strings eine andere Bedeutung. Wenn Sie eine Zahl mit einem String "addieren", wird die Zahl in einen String verwandelt:

String message = "high" + 5;
println(message);
high5
    

Sie können Strings auch direkt in das print schreiben:

println("high" + 5);

Wenn Sie mit einem Befehl, der eigentlich einen String erwartet, eine Zahl ausgeben wollen, führt das zu Problemen:

text(42, 50, 50); // Fehler: String an erster Stelle!

Hier gibt es den Trick, einen leeren String per Konkatenation mit der Zahl zu verknüpfen. Java macht dann die Zahl zum String und verbindet die zwei Zeichenketten:

text("" + 42, 50, 50);

Das brauchen Sie erst dann, wenn die Zahl in einer Variablen steht. Hier könnten Sie ja einfach "42" schreiben.

Fingerübungen

a) Koordinaten

Erzeugen Sie zwei Variablen x und y vom Typ int. Setzen Sie diese auf die Werte 30 und 50. Benutzen Sie die Variablen, um einen Kreis mit Durchmesser 30 und Mittelpunkt (x, y) zu zeichnen.

Lösung
	int x = 30;
	int y = 50;
	ellipse(x, y, 30, 30);
	

b) Durchschnitt

Erzeugen Sie drei Variablen a, b und c vom Typ float. Setzen Sie a und b auf beliebige Werte, z.B. auf 1 und 2. In Variable c soll der Durchschnitt von a und b berechnet werden.

Geben Sie c mit println() auf der Konsole aus.

Lösung
	float a = 1;
	float b = 2;
	float c = (a + b) / 2;
	println(c);
	

c) Variablen für alle Typen

Legen Sie folgende Variablen an und geben Sie sie auf der Konsole aus:

  • Variable a vom Typ int mit Wert -5
  • Variable b vom Typ float mit Wert 32.01
  • Variable c vom Typ boolean soll "wahr" sein
  • Variable d vom Typ char soll ein Leerzeichen enthalten

Geben Sie das Leerzeichen so aus, dass man auf der Konsole erkennen kann, dass ein Leerzeichen ausgegeben wurde (z.B. indem Sie vor und hinter das Zeichen etwas ausgeben).

Lösung
int a = -5;
float b = 32.01;
boolean c = true;
char d = ' ';

println(a);
println(b);
println(c);
println("_" + d + "_");

Übungsaufgaben

2.3 a) Variablen für Position

Erstellen Sie zwei Variablen x und y und belegen Sie sie mit jeweils 10. Zeichnen Sie ein 30x30-Quadrat an ebendiese Position. Zeichnen Sie ein zweites Quadrat an eine Position, die 50 Pixel weiter rechts und 50 Pixel weiter unten liegt. Benutzen Sie für das zweite Quadrat auch die beiden Variablen!

2.3 b) Smiley

Erstellen Sie zwei Variablen x und y und belegen Sie sie mit jeweils 50. Zeichnen Sie einen Smiley aus entsprechenden Grundformen. Verwenden Sie für die Positionen der Formen immer die Variablen x und y zusammen mit Addition/Subtraktion. Ändern Sie aber nie die Werte von x und y.

Ersetzen Sie jetzt die Variablen x und y gegen mouseX und mouseY und überführen Sie das Programm in den aktiven Modus (mit setup und draw). Der Smiley sollte der Maus folgen.

(Interaktives Feld - erst anklicken.)

2.3 c) Dreisatz

In dem folgenden Programm haben Sie Preis (in Euro) und Größe (in Gramm) einer Müslipackung gegeben und (nur) die Größe einer zweiten Müslipackung. Unter der Voraussetzung, dass beide Müsli gleich viel kosten, ergänzen Sie den Code so, dass der Preis für die zweite Packung ausgerechnet und ausgegeben wird.

float muesliGroesse1 = 700;
float muesliPreis1 = 3.99;

float muesliGroesse2 = 500;
float muesliPreis2 = 0; // Ersetzen Sie die 0 durch eine Rechnung

println(muesliPreis2);

Um zu sehen, ob Ihr Programm korrekt ist, testen Sie es mit Werten mit offensichtlichem Ergebnis, z.B.

float muesliGroesse1 = 1000;
float muesliPreis1 = 2;

float muesliGroesse2 = 500;

2.3 d) Pythagoras

Erstellen Sie zwei Variablen x und y, jeweils vom Typ float und belegen Sie sie mit beliebigen Werten (z.B. x = 20, y = 30). Ihr Programm soll eine Linie zwischen (x, y) und der aktuellen Mausposition zeichnen und auf der Konsole ständig die Distanz (in Pixeln) zwischen (x, y) und der Maus ausgeben.

Die Funktion sqrt() gibt Ihnen zu einer beliebigen Zahl die Wurzel zurück. Mit der Funktion pow() können Sie Potenzen bilden. Zum Beispiel gibt pow(3, 2) das Ergebnis von "3 hoch 2" zurück.

2.3 e) Mausposition ausgeben

Schreiben Sie ein Programm, das in jedem draw-Zyklus die aktuelle Mausposition in folgender Weise ausgibt (eine Zeile pro Zyklus):

x=48 y=47
x=48 y=52
x=48 y=56
x=48 y=60
x=52 y=65
x=52 y=65
x=55 y=70
Tipp
Verwenden Sie println zusammen mit mouseX und mouseY (ein einziger Befehl). Lesen Sie den Abschnitt oben über "Zeichenketten".

2.3 f) Halbe Pixel?

Kopieren Sie folgendes Programm

float x = 0;

void draw() {
  background(255);
  ellipse(x, 50, 30, 30);
}

Ergänzen Sie den Code, so dass x bei jedem Mausklick um 0.5 erhöht wird.

Wie verhält sich der Kreis?

Tipp
Verwenden Sie mousePressed() und schauen Sie in Kap. 3 nach, wenn Sie vergessen haben, wie das funktioniert.

2.3 g) Zeichenverkettung

Kopieren Sie folgendes Programm

String message = "la";

void setup() {
  println(message);
}

void draw() {
}

Ergänzen Sie den Code, so dass bei jedem Mausklick genau eine neue Zeile auf die Konsole geschrieben wird, so dass die Konsole nach 3 Klicks wie folgt aussieht:

la
la la
la la la
la la la la

2.3 h) Schwarzweiß

Der Hintergrund soll weiß eingefärbt werden, wenn Sie eine Tastatur-Taste drücken, und schwarz, wenn Sie eine Maustaste drücken.

Verwenden Sie folgenden Basiscode und achten Sie darauf, dass das Rechteck nicht übermalt wird, wenn Sie den Hintergrund ändern.

Hinweis: Verwenden Sie keine Zeichenbefehle (background, rect) in keyPressed() oder mousePressed().

void draw() {
  rectMode(CENTER);
  rect(50, 50, 40, 40);
}

Wichtig: Verwenden Sie eine Variable, die Sie in keyPressed bzw. mousePressed verändern (sonst funktioniert die Folge-Aufgabe nicht).

(Interaktives Feld - erst anklicken.)

2.3 i) Schwarzweiß 2

Ersetzen Sie im obigen Programm das Quadrat durch einen Kreis, der komplementär zum Hintergrund schwarz oder weiß ist. Verwenden Sie KEINE Zeichenbefehle (background, ellipse) in keyPressed() oder mousePressed().

(Interaktives Feld - erst anklicken.)

2.3 j) Zufällige Position

Zeichnen Sie einen schwarzen Punkt. Bei jedem Mausklick soll der Punkt an eine zufällige Position springen.

(Interaktives Feld - anklicken.)

Tipp
Verwenden Sie Variablen vom Typ float für x und y. Lassen Sie den Code zum Zeichnen immer in draw und verwenden mousePressed lediglich, um Variablen zu ändern.

Zusammenfassung

Wir haben zunächst folgende Basics zu Variablen gelernt:

  • Variable als Container (Speicher): Eine Variable speichert etwas, z.B. einen Wert zur späteren Verwendung. Was genau sie speichern kann, sagt ihr Datentyp (kurz: Typ).
  • Erzeugen: Eine neue Variable erzeugt man mit TYP NAME;
  • Zuweisung: Hat man eine Variable foo, so kann man mit foo = 5 den Wert 5 dort ablegen.
  • Verwendung: Steht eine Variable wie foo im Code - z.B. in dem Befehl rect(foo, foo, 10, 10) - so wird "foo" durch den aktuellen Wert von foo ersetzt (Substitution).

Sie haben die folgenden primitiven Datentypen kennengelernt:

TypBeschreibungWertebereich
byte sehr kleine ganze Zahl (8 Bit)-128 bis 127
short kleine ganze Zahl (16 Bit)-32768 bis 32767
int große ganze Zahl (32 Bit)-2 147 483 648 bis 2 147 483 647
long noch größere ganze Zahl (64 Bit)ca. -9 * 10^18 bis 9 * 10^18
float Zahl mit Nachkommastellen (32 Bit)z.B. -0.5, 0, 109.333
double Zahl mit mehr Nachkommastellen (64 Bit)wie oben
boolean Wahrheitswert true und false
char einzelnes Zeichen (z.B. Buchstabe)z.B. 'a' oder '%'

Desweiteren haben Sie den Typ String kennengelernt, der es erlaubt, Zeichenketten (= Text) abzuspeichern.

Strings können mit dem Plus-Zeichen verbunden werden. Diese Operation nennt man String-Konkatenation.

2.4 Variablen: Verarbeitung

Code wird Zeile für Zeile verarbeitet, von oben nach unten. Processing hat zwei Bereiche für die Verarbeitung:

  • einen Bereich, wo die aktuelle Zeile gespeichert und verarbeitet wird
  • einen Bereich, wo alle aktuell gültigen Variablen abgelegt werden

Damit Sie verstehen, wie Processing funktioniert, schauen wir uns an, wie genau eine einzelne Zeile verarbeitet wird. Nach derzeitigem Stand können folgende Dinge in einer Zeile passieren:

  • Eine Variable wird deklariert
  • Einer Variablen wird ein Wert zugewiesen
  • Eine Anweisung wird ausgeführt

Wird eine Variable deklariert, wird ein neuer "Behälter" im Variablenbereich für diese Variable angelegt. Im weiteren schauen wir uns die anderen zwei Fälle an.

Video: Variablen 2 - Verarbeitung (5:38)

Verarbeitung einer Zuweisung

Allgemein sieht eine Variablenzuweisung immer so aus:

VARIABLE = AUSDRUCK;

Beispiel:

int x;
x = 10 + 5; // rechte Seite wird zuerst ausgewertet

Wichtig ist jetzt, dass Processing immer zuerst die rechte Seite auswertet und anschließend das Ergebnis in die Variable x schreibt.

Ist die Variable eine Zahl (int, float, double), so kann der Ausdruck die verschiedenen mathematischen Operatoren +, -, /, *, % und Klammern beinhalten.

int b;
b = 20 + 4 / 2 + (5 - 2);

Mathematische Ausdrücke werden nach den bekannten Regeln ausgewertet, d.h. * und / haben Priorität, geklammerte Teile ebenso. Im Beispiel ist b also 25.

Die rechte Seite einer Zuweisung kann auch Variablen enthalten. In diesem Fall werden bei der Auswertung als erstes alle Variablen durch Ihre Werte ersetzt (Substitution).

int q = 33;
int ergo;
ergo = 22 + q + 5; // zuerst wird q eingesetzt

Oft möchte man den Wert einer Variablen um einen bestimmten Betrag erhöhen oder erniedrigen. Das funktioniert wie folgt:

int foo = 100;
foo = foo + 10;
  

Warum muss das so aussehen? In dieser Zeile wird zunächst die Variable durch ihren Wert ersetzt:

foo = foo + 10;
  

Das sieht dann so aus:

  foo = 100 + 10;
  

Jetzt wird gerechnet:

  foo = 110;
  

Erst jetzt wird der Wert von foo auf 110 angepasst. Unser Ziel, den Wert um 10 zu erhöhen, ist erreicht.

Variablen innerhalb einer Anweisung

Allgemein sieht eine Befehlszeile so aus

ANWEISUNG(PARAMETER-1, PARAMETER-2, ...);

Trifft Processing auf eine Anweisung, so können auch dort Variablen auftauchen. Auch hier werden zuerst alle Variablenwerte eingesetzt (Substitution), erst dann wird die Anweisung ausgeführt.

Beispiele:

int x = 50;
int y = 100;
ellipse(x, y, 30, 30);
rect(x, y, 30, 20);
println("x ist " + x);

Ausführungsreihenfolge im Aktiven Modus

Sie wissen aus dem letzten Kapitel, dass im aktiven Modus zuerst setup() genau ein Mal ausgeführt wird und anschließend immer wieder draw() ausgeführt wird.

Es kommt eine neue wichtige Regel hinzu: Noch bevor setup() ausgeführt wird, werden alle Codezeilen ausgeführt, in denen globale Variablen deklariert/initialisiert werden.

Beachten Sie die Ausführungsreihenfolge. Zuerst werden globalen Variablen initialisiert, dann wird setup() ausgeführt, dann wird draw() wiederholt ausgeführt.

Ein Beispiel, um die Ausführungsreihenfolge zu prüfen:

int foo = 0;

void setup() {
  println(foo);
  foo = foo + 1;
}

void draw() {
  println(foo);
  foo = foo + 100;
}
0
1
101
201
301

Animation

Wir können jetzt eine Variable verwenden, um etwas zu animieren, indem wir die Variable (z.B. die x-Koordinate einer Form) immer wieder verändern:

int x = 0;

void draw() {
  ellipse(x, 50, 20, 20);
  x = x + 1;
}

Wenn wir keine "Schlieren" sehen möchten, müssen wir den Hintergrund in jedem Durchlauf von draw() erneuern.

int x = 0;

void draw() {
  background(200);
  ellipse(x, 50, 20, 20);
  x = x + 1;
}

Ausführungsreihenfolge: Ein Beispiel

Wir haben oben gesagt, dass der Programmteil mit den Variablen ganz oben als erstes ausgeführt wird. Das sollte man beachten, wenn man will, dass Variablen immer wieder neu gesetzt werden. Nehmen wir an, wir möchten immer wieder einen zufällig gewählten Punkt malen, so dass sich langsam der Bildschirm füllt.

// Wird als erstes ausgeführt:

float x = random(100);
float y = random(100);

// Wird als zweites ausgeführt:

void setup() {
  frameRate(10); // 10x draw() pro Sekunde
  strokeWeight(5); // Punktgröße = 5 Pixel
}

// Wird als 3., 4., 5. etc. ausgeführt:

void draw() {
  point(x, y); // male Punkt
}

Warum sehen wir nur einen Punkt? Ganz einfach: wegen der Ausführungsreihenfolge. Der Teil mit den globalen Variablen x und y wird als erstes ausgeführt. Hier werden zwei Zufallszahlen bestimmt. Als nächstes wird 1x setup() ausgeführt. Anschließend wird immer wieder draw() ausgeführt. Nur die Werte für x und y ändern sich nie mehr. Also wird immer wieder der gleiche Punkt gemalt.

Wir müssen also das Beziehen der Zufallszahlen mit random() in das draw() ziehen:

float x;
float y;

void setup() {
  frameRate(10);
  strokeWeight(5);
}

void draw() {
  x = random(100);
  y = random(100);
  point(x, y);
}

Typische Anwendungen für Variablen

Zwei typische Anwendungen von Variablen sind

Eine zentrale Stelle einzurichten, wo häufig verwendete Werte abgelegt werden, zum Beispiel der Durchmesser von mehreren Kreisen, die Geschwindigkeit oder Farbe von verschiedenen Objekten. Der Vorteil liegt darin, dass dieser Wert immer nur an einer Stelle geändert werden muss.

Einen Wert während des Programmlaufs ändern zu können. Der Name Variable kommt natürlich von variabel. Bei einer Animation ändern sich ständig z.B. den Wert der x-Koordinate und können das nur tun, indem Sie die x-Koordinate in einer Variablen speichern und ständig anpassen (z.B. 1 addieren).

Ein letztes Beispiel: Sie kennen aus dem Kapitel über Interaktion die Möglichkeit mit keyPressed() auf Tastendruck zu reagieren.

Können Sie die Hintergrundfarbe in folgendem Beispiel per Tastendruck verstellen?

int x = 0;

void setup() {
  size(200, 200);
}

void draw() {
  background(255);
  rect(x, 100, 20, 20);
  x++;
}

void keyPressed() {
  background(0, 255, 0);
}

Im Beispiel sehen Sie nur ein kurzes Aufflackern der Farbe Grün. Warum? Weil das background(255) immer wieder den Bildschirm mit Weiß befüllt. Um das Grün nachhaltig zu etablieren, müssen Sie Variablen einführen:

int x = 0;
int rot = 255;
int gruen = 255;
int blau = 255;

void setup() {
  size(200, 200);
}

void draw() {
  background(rot, gruen, blau);
  rect(x, 100, 20, 20);
  x++;
}

void keyPressed() {
  rot = 0;
  gruen = 255;
  blau = 0;
}

Da jetzt die Variablen rot, gruen, blau umgesetzt werden, sobald eine Taste gedrückt wird, ändert sich das background()-Kommando in draw(). Es ergibt sich für alle folgenden draw()-Zyklen das Kommando background(0,255,0) - also grün.

Video: Anwendungsbeispiele (10:14)

Übungsaufgaben

2.4 a) Ball nach rechts

Schreiben Sie ein Programm, bei dem sich ein Ball, der zu Beginn an Position (0, 50) steht, bei jedem Mausklick um 5 Pixel nach rechts bewegt.

(Interaktives Feld - erst anklicken.)

2.4 b) Quadrat wächst/schrumpft

Schreiben Sie ein Programm, bei dem sich ein Quadrat, das in der Mitte steht, bei jedem Mausklick um 5 Pixel vergrößert, und bei jedem Tastenanschlag um 5 Pixel verkleinert.

(Interaktives Feld - erst anklicken.)

Hinweis: Wenn das Quadrat so klein wird, dass es verschwindet, und Sie weiter auf eine Taste drücken, dann beginnt es wieder zu wachsen. Das liegt daran, dass Processing auch eine negative Breite/Höhe interpretiert (scheinbar wird das Vorzeichen ignoriert). Sie sollten solche Anomalien zur Kenntnis nehmen, aber nicht systematisch in Ihre Programmierung einbauen, da man nie weiß, wie so etwas nach dem nächsten Versionsupdate von Processing umgedeutet wird.

2.4 c) Ball fliegt nach rechts

Schreiben Sie ein Programm, bei dem sich ein Ball, der zu Beginn an Position (0, 50) steht, nach rechts fliegt.

Im folgenden Feld sehen Sie, dass der Ball immer wieder von links eintritt. Das können Sie noch nicht realisieren - es sei denn, Sie kennen bereits If-Anweisungen, dann können Sie es gern probieren. (Alternativ lesen Sie unten den Abschnitt über den "Modulo-Operator".)

2.4 d) Hintergrund-Farbwechsel

Programmieren Sie wieder den fliegenden Ball (diesmal einen roten Ball). Erweitern Sie das Programm so, dass bei Mausdruck der Hintergrund weiß wird und bei Tastendruck wieder schwarz.

(Interaktives Feld - erst anklicken.)

Tipp
Sie müssen die Funktionen mousePressed() und keyPressed() einrichten. Es reicht nicht, dort den Befehl background() aufzurufen. Stattdessen müssen Sie eine Variable verwenden.

2.4 e) Ball hoch/runter

Erweitern Sie das Programm aus der vorherigen Aufgaben. Wenn Sie mit der Maus klicken, soll der Ball 5 Pixel weiter unten fliegen. Wenn Sie auf eine Taste drücken, soll er 5 Pixel weiter oben fliegen.

(Interaktives Feld - erst anklicken.)

2.4 f) Animation auf Kreisbahn *

Lassen Sie einen Ball im Kreis fliegen.

Tipp
Sie brauchen hier die Funktionen sin() und cos(). Zeichnen Sie sich die Situation am besten auf. Sie benötigen dann noch eine Variable für den Winkel; entweder im Bogenmaß oder als Grad mit der Funktion radians().

2.4 g) Need for Speed

Ihr Ball fliegt (mit Hilfe des Modulo-Operators) von links nach rechts über den Bildschirm.

Wenn man die Maustaste drückt, soll der Ball schneller fliegen. Beim nächsten Klick wieder schneller etc.

Mit einem Tastendruck auf dem Keyboard soll die Geschwindigkeit wieder auf den ursprünglichen Wert zurückgesetzt werden.

Tipp
Überlegen Sie, an welcher Stelle im Code die "Geschwindigkeit" (normalerweise 1 Pixel pro Aufruf von draw) definiert ist. Ersetzen Sie die "1" durch eine Variable, die Sie dann per Maustaste manipulieren können.

2.5 Lokale Variablen

Bislang kennen Sie ausschließlich globale Variablen. Diese werden zu Beginn des Programms deklariert und können dann überall verwendet werden, z.B. in den Funktionen setup() und draw(), aber auch in keyPressed():

int x; // neue globale Variable

// Anfangsposition festlegen
void setup() {
  x = 0;
}

// Ball animieren
void draw() {
  background(255);
  ellipse(x, 50, 20, 20);
  x++;
}

// Ball zurücksetzen
void keyPressed() {
  x = 0;
}

Variable x nennt man eine globale Variable, weil überall, also global, im Programm verwendet werden kann. In unserem Beispiel wird x in drei verschiedenen Funktionen (setup, draw und keyPressed) verwendet.

Skopus

Manchmal möchte man gar nicht, dass eine Variable überall verwendbar ist. Wie sich bei größeren Programmen herausstellt, wird der Code durch viele globale Variablen unübersichtlich. Daher gibt es lokale Variablen, die nur eine eingeschränkte Lebensdauer haben, d.h. sie sind nur in einem bestimmten Bereich, dem sogenannten Skopus, gültig.

Skopus

Der Skopus (engl. scope) einer Variablen ist der Bereich im Code, innerhalb dessen die Variable sichtbar ist, d.h. in diesem Bereich, kann der Wert der Variablen verwendet werden oder die Variable geändert werden.

Daraus ergibt sich die Bedeutung einer lokalen Variable:

Lokale Variable

Eine lokale Variable ist eine Variable, die nur in einem eingeschränkten Teil eines Computerprogramms sichtbar ist, d.h. deren Skopus kleiner ist als das gesamte Programm.

Wir werden in späteren Kapiteln noch viele Beispiele sehen, wo Variablen einen eingeschränkten Skopus haben (u.a. Schleifen, If-Anweisungen und Funktionen).

Nehmen wir an, Sie möchten einen Ball animieren. Der Durchmesser des Balls soll wachsen, also nehmen wir einfach den x-Wert auch als Durchmesser. Wir möchten aber, dass der Durchmesser nicht bei 0, sondern bei 10 beginnt. Also nehmen wir x + 10 als Durchmesser:

float x = 0;

void draw() {
  ellipse(x, 50, x + 10, x + 10);
  x++;
}

Der Übersichtlichkeit willen möchten wir eine Variable für den Durchmesser einführen. Diese wird aber lediglich in draw() benötigt (nicht z.B. in setup). Daher definieren wir eine lokale Variable:

float x = 0;

void draw() {
  float durchmesser = x + 10; // Neue lokale Variable
  ellipse(x, 50, durchmesser, durchmesser);
  x++;
}

Eine lokale Variable ist nur im eigenen Code-Block gültig, angefangen von der Zeile, wo die Variable deklariert wird bis zur schließenden geschweiften Klammer des aktuellen Code-Blocks. Im oberen Beispiel hört die lokale Variable durchmesser auf zu leben, sobald draw abgearbeitet ist.

Der Skopus - also die Gültigkeit - einer lokalen Variable beginnt in der Zeile, in der sie deklariert wird und endet am Ende des eigenen Code-Blocks.

Wollten Sie z.B. im folgenden Code auf die Variable durchmesser in der Funktion setup() zugreifen, dann bekommen Sie einen Fehler:

float x = 0;

void setup() {
  durchmesser = 10; // FEHLER! VARIABLE UNBEKANNT
}

void draw() {
  float durchmesser = x + 10; // Neue lokale Variable
  ellipse(x, 50, durchmesser, durchmesser);
  x++;
}

Überschatten

Wichtig ist, dass nur das Wörtchen float (oder int oder boolean ...) darüber entscheidet, ob Sie eine neue Variable anlegen oder eine alte Variable setzen. Wie in folgendem Beispiel, wo Sie den x-Startpunkt des Balls zufällig setzen wollen:

float x = 0; // Globales x

void setup() {
  float x = random(100); // Startpunkt für x zufällig
}

void draw() {
  background(255);
  ellipse(x, 50, 20, 20);
  x++;
}

Ihr Ball startet immer bei Null! Warum? Weil in setup() eine neue Variable mit Namen x erzeugt wird. Diese neue Variable ist lokal in setup() und hat nichts mit dem globalen x zu tun. Das heißt, das globale x wird nicht verändert, bleibt also Null, und in draw() wird natürlich das globale x verwendet.

Im obigen Beispiel sagt man auch, dass die lokale Variable x die globale Variable überschattet. Um das zu vermeiden, verwenden Sie nie den gleichen Variablennamen zweimal und achten Sie darauf, dass Sie nicht aus Versehen einen Typ wie "float" vor eine Variable setzen, obwohl Sie nur eine Zuweisung durchführen wollen.

Übungsaufgaben

Sie können bei den folgenden Aufgaben lokale Variablen nutzen (müssen Sie aber nicht).

2.5 a) Zwillinge

Programmieren Sie folgendes:

(Interaktives Feld - erst anklicken.)

2.5 b) Mitte

Programmieren Sie folgendes:

(Interaktives Feld - erst anklicken.)

2.5 c) World in Miniature

Programmieren Sie das kleine Rechteck in der Ecke. Der Punkt dort zeigt die Position der Maus im Gesamtfenster an.

(Interaktives Feld - erst anklicken.)

Sie können folgenden Code als Ausgangspunkt nehmen:

// Position/Größe des Rechtecks
float x = 130;
float y = 100;
float w = 60;
float h = 40;

void setup() {
  size(200, 150);
}

void draw() {
  background(0);
  stroke(255);
  noFill();
  rect(x, y, w, h);
  fill(255);
  ellipse(mouseX, mouseY, 10, 10); // Mauszeiger

  // Ihr Code ...
}

Eine Vorgehensweise wäre, zwei lokale Variablen x2 und y2 anzulegen, in die Sie das Ergebnis der Umrechnung von mouseX und mouseY in das System des Rechtecks vornehmen. Es handelt sich um eine klassische Anwendung des Dreisatz'.

2.5 d) Balken

Realisieren Sie folgende Interaktion mit Balken. Verwenden Sie einfach rect() im regulären Modus.

Verwenden Sie eine lokale Variable.

2.6 Operatoren

Operatoren sind Symbole, die ein oder zwei Operanden verbinden und nach Anwenden einer Operation ein Ergebnis zurückliefern. So ist z.B. das Plus-Zeichen ein Operator, die dazugehörige Operation heißt Addition und das zurückgelieferte Ergebnis ist die Summe der zwei Operanden. Hier stellen wir noch einige weitere nützliche Operatoren vor.

Ganzzahlige Division und Modulo

Sie kennen das Symbol / als Operator für die "geteilt durch" Operation. Was Sie noch nicht wissen, ist, dass es zwei unterschiedliche Divisionen gibt: die ganzzahlige Division und die Gleitkommazahlen-Division. Beide Divisionen verwenden aber das gleiche Symbol /. Bei der ganzzahligen Division haben wir zwei Resultate: das Ergebnis der Division und den Rest (Modulo).

Ganzzahlige Division

Sehen wir uns das im Beispiel an. Hier haben wir eine ganzzahlige Division:

println(5 / 2);
2

Wie entscheidet Processing, ob eine ganzzahlige Division verwendet wird, oder die Gleitkomma-Division? Die Regel lautet: Wenn beide Operanden des Operators (hier: die 5 und die 2) vom Typ int sind, dann verwende die ganzzahlige Division. Umgekehrt folgt daraus: Wenn mindestens ein Operand vom Typ float ist, dann verwende die Gleitkomma-Division.

Hier haben wir ein Beispiel für eine Gleitkomma-Division:

println(5.0 / 2);
2.5

Durch das .0 wird die fünf als Gleitkommazahl interpretiert. Das könnten wir natürlich auch so bewirken:

  println(float(5) / 2);
  

In diesem Fall ist das etwas umständlich, bei Variablen ist es aber manchmal notwendig:

int zahl = 5;
println(float(zahl) / 2);
  

Achten Sie darauf, dass bei komplexeren Rechnungen nicht immer offensichtlich ist, von welchem Typ die jeweiligen Operanden sind. Sie müssen sich genau überlegen, in welcher Reihenfolge gerechnet wird und welchen Typ die Operanden zu dem Zeitpunkt haben, wo der Divisions-Operator an der Reihe ist:

println(2.5 + 5 / 2 + 5.0);

Bekommen Sie hier eine 10.0 oder eine 9.5? Versuchen Sie sich zuerst klarzumachen, was herauskommt. Probieren Sie es dann in Processing aus.

Modulo-Operator

Wir haben gelernt, dass Processing eine ganzzahlige Division durchführt, wenn zwei Integer-Werte die Operanden sind. Das Resultat einer ganzzahligen Division hat zwei Bestandteile: den Ganzzahlquotient und den Rest.

5 / 2 ergibt 2 (Quotient) mit Rest 1
7 / 5 ergibt 1 (Quotient) mit Rest 2

Mathematisch ist das wie folgt definiert:

x / y ergibt Quotient q mit Rest r

x = q * y + r

Der Modulo-Operator % gibt Ihnen für eine ganzzahlige Division den Rest zurück.

Hier sehen Sie ihn in Aktion:

println(5 % 2);
1
println(7 % 5);
2

Der Modulo-Operator erlaubt Ihnen zu testen, ob eine Zahl gerade oder ungerade ist, denn wenn man eine gerade Zahl durch 2 teilt, ist der Rest gleich Null:

int zahl = 7;

if (zahl % 2 == 0) {
  println("gerade");
} else {
  println("ungerade");
}

Ein Grund, warum der Modulo-Operator in der Informatik und insbesondere im Bereich Animation so interessant ist, ist seine Ring-Eigenschaft. Dazu sehen wir uns an, was passiert, wenn man sich den Rest von einer Zahlenreihe (0, 1, 2, 3, ...) mit einer festen Zahl (z.B. 3) ansieht:

0 % 3 = 0
1 % 3 = 1
2 % 3 = 2
3 % 3 = 0
4 % 3 = 1
5 % 3 = 2
6 % 3 = 0
7 % 3 = 1
8 % 3 = 2
...

Fällt Ihnen etwas auf? Richtig. Obwohl der erste Operand immer größer wird, bewegt sich das Ergebnis der Modulo-Operation immer im Bereich 0...2.

Allgemein bewegt sich bei einer Restoperation mit der Zahl N das Resultat immer im Bereich von 0 bis N-1.

Das ist sehr nützlich bei Animationen:

int x = 0;

void draw() {
  background(255);
  int x2 = x % 100;
  ellipse(x2, 50, 20, 20);
  x++;
}

Obwohl x immer größer wird, bleibt x2 im Bereich 0..99, so dass der Ball immer von links nach rechts im sichtbaren Bereich fliegt.

Interessante Operatoren

Mit den folgenden Operatoren können Sie stellenweise Ihren Code verkürzen oder eleganter formulieren.

Inkrement-Operator

Eine nützliche Kurzform für folgende Operation (x sei eine int-Variable)

x = x + 1;

ist die folgende Zeile

x++;

Das bedeutet, dass der Inhalt der Variable x um 1 erhöht wird. Das "++" nennt man einen Operator, und zwar den Inkrement-Operator.

Dekrement-Operator

Passend dazu gibt es einen Operator zum Runterzählen:

x--;

Dieser sog. Dekrement-Operator reduziert den Wert von x um 1.

Additions-Zuweisung

Was machen Sie, wenn Sie so eine Erhöhung haben:

x = x + 2;

Ganz einfach: Sie nutzen die Additions-Zuweisung:

x += 2;

Sie denken: Naja, soviel Schreibarbeit hab ich hier nicht gespart! Schauen Sie mal:

ziemlichLangerVariablennameMitExtras += 2;

Schon praktisch. Diese Art der Zuweisung gibt's auch in anderen Geschmacksrichtungen:

            x -= 3; // x = x - 3
            x *= 5; // x = x * 5
            x /= 2; // x = x / 2

Tipp: Wie merken Sie sich, ob es jetzt "+=" oder "=+" heißt? Überlegen Sie sich, dass "x =+ 2" von Processing gelesen würde wie "x = +2", d.h. das Plus-Zeichen wird der Zahl zugeordnet und x daher nicht um 2 erhöht, sondern auf 2 gesetzt.

Details zum Inkrementoperator

Ausführungsreihenfolge

Sie können den Inkrement-Operator nicht nur allein auf einer Zeile ausführen, sondern auch in Befehle oder Rechnungen einbetten.

Beispiel 1:

int foo = 5;
println(foo++);

Beispiel 2:

int boo = 10;
int goo = 100 + boo++;

Was passiert hier? Wir haben hier zwei Vorgänge, die wir unterscheiden müssen:

  • (a) Verwendung/Substitution der Variable, d.h. die Variable wird durch ihren aktuellen Wert ersetzt
  • (b) Inkrementierung, d.h. der Wert der Variable wird um eins erhöht.

Jetzt wird klar, dass es eine große Rolle spielt, ob erst (a) oder (b) durchgeführt wird! Wenn Sie Beispiel 1 ausführen, sehen Sie:

5

Der Wert der Variable foo ist aber 6. Das liegt daran, dass beim Einsetzen (a) der alte Wert 5 verwendet wurde. Im Anschluss wurde dann die Variable foo erst um eins auf 6 erhöht.

Verstehen Sie dann auch Beispiel 2? Probieren Sie es aus und überlegen Sie, warum das Ergebnis so ist, wie es ist.

Insbesondere verstehen Sie jetzt auch, warum der folgende Anfängerfehler so gravierend ist:

int x = 0;
x = x++;

Hier passiert folgendes:

  1. x wird eingesetzt, d.h. dort steht x = 0, aber dies wird noch nicht umgesetzt
  2. x wird um eins erhöht, d.h. x hat jetzt den Wert eins
  3. die gesamte Zeile, also x = 0, wird jetzt erst ausgeführt, d.h. x wird auf 0 gesetzt

Im Endeffekt bleibt x also immer Null.

Unterschied ++i und i++

Jetzt kann man sich vorstellen, dass diese Reihenfolge umkehren möchte:

  • (b) Inkrementierung, d.h. der Wert der Variable wird um eins erhöht.
  • (a) Verwendung/Substitution der Variable, d.h. die Variable wird durch ihren aktuellen Wert ersetzt

Das heißt, Sie möchten erst die Variable erhöhen (b) und dann den neuen Wert einsetzen (a). Dies erreichen Sie mit der Formulierung

++i

Sehen wir uns die Beispiele an, um uns davon zu überzeugen, dass das funktioniert:

Beispiel 1:

int foo = 5;
println(++foo);
6

Beispiel 2:

int boo = 10;
int goo = 100 + ++boo;
111

Übungsaufgaben

2.6 a) Muster

Versuchen Sie zu verstehen, wie folgender Code funktioniert:

int x = 0;
int y = 0;

void draw() {
  line(x, 0, x, y);
  x++;
  y++;
}

Verändern Sie den Code um jeweils eines der folgenden Muster zu erzeugen:

2.6 b) Animation mit Modulo

Die Systemvariable frameCount zählt von Beginn Ihres Programms an, wieviele Frames bereits vergangen sind, d.h. die Variable enthält 0, 1, 2, 3 etc.

Verwenden Sie frameCount zusammen mit dem Modulo-Operator, um einen Ball immer wieder von links nach rechts fliegen zu lassen. Ihr draw() besteht dabei lediglich aus zwei Code-Zeilen.

2.6 c) Pulsierender Cursor

Verwenden Sie den Modulo-Trick aus der vorigen Aufgabe, um an die Stelle der Maus einen pulsierenden Cursor zu stellen.

Tipp
Zeichnen Sie einen Kreis, dessen Durchmesser immer größer wird (bis 30) und dann wieder 0 wird.

2.7 Systemvariablen

Processing ist so freundlich, Ihnen eine Handvoll Variablen zu "schenken". Das bedeutet: diese Variablen sind immer vorhanden, Sie brauchen sie nicht deklarieren oder setzen, sondern Sie verwenden Sie nur. Zum Beispiel, um die aktuelle Position der Maus rauszukriegen oder um die Bildschirmbreite zu erfahren.

Systemvariable

Eine Systemvariable in Processing ist eine Variable, deren Wert durch die Processingumgebung gesetzt und gegebenenfalls fortlaufend aktualisiert wird.

Achten Sie darauf, dass Sie Ihre Variablen nicht so benennen wie Systemvariablen und dass Sie Systemvariablen nie selbst setzen.

Größe des Grafikfensters

Hier ein Beispiel, wo Sie ein Rechteck malen, das das linke obere Viertel des Bildschirms besetzt. Sie benutzen die Systemvariablen width und height , welche die Breite und Höhe Ihres Grafikfensters beinhalten:

rect(0, 0, width/2, height/2);

Achten Sie darauf, dass die Systemvariablen width und height erst ab setup() zur Verfügung stehen und auch dort sinnvollerweise erst nach dem Kommando size() abgegriffen werden sollten. Das heißt, dass dieser Code nicht funktionieren wird:

int w = width;  // Problem! width noch nicht gesetzt!
int h = height; // Problem! height noch nicht gesetzt!

void setup() {
}

void draw() {
  ellipse(w/2, h/2, 40, 40);
}

Position der Maus

Anderes typisches Beispiel: Ein Kreis soll an Ihrer Maus kleben. Dazu verwenden Sie mouseX und mouseY :

void setup() {
}

void draw() {
  background(255); // wir wollen ja keine Schlieren
  ellipse(mouseX, mouseY, 20, 20);
}

Bildwiederholrate

Einen Aufruf von draw() nennt man auch einen Frame. Das kommt aus der Filmbranche, ein Frame ist ein Einzelbild eines Films. Normalerweise wird draw() 60 Mal pro Sekunde aufgerufen. Diese 60 nennt man auch die frame rate (Bildwiederholrate). Dies ist allerdings nur das Idealverhalten. Wenn Sie viele Programme laufen haben, die viel Rechenzeit benötigen, wird dieser Wert unterschritten. In der Variablen frameRate steht der derzeit erreichte Wert (als float). Geben Sie sich das ruhig mal aus:

void setup() {
}

void draw() {
  println(frameRate);
}

Überfordern Sie auch mal den Rechner und testen Sie, wo die Grenzen liegen:

void setup() {
  frameRate(100000); // setze meine gewünschte frame rate
}

void draw() {
  println(frameRate); // zeige echte frame rate
}

Wie schnell ist Ihr Rechner maximal? Und ändert sich dieser Wert, wenn Sie gleichzeitig Programme laufen lassen, die Ihren Rechner fordern (z.B. Spiele mit 3D-Grafik, mehrere Videoplayer).

Nützliche Systemvariablen

Hier sehen Sie eine Liste nützlicher Systemvariablen. Jede dieser Variablen hat natürlich auch einen Datentyp.

Systemvariable/nTypBeschreibung
mouseX, mouseY int aktuelle Position des Mauszeigers
pmouseX, pmouseY int Position des Mauszeigers im vorherigen Frame
width, height int Pixelgröße des Grafikfensters (Breite und Höhe)
frameRate float Anzahl der Frames pro Sekunde, d.h. wie oft draw() aufgerufen wird
frameCount int Anzahl der bisherigen Aufrufe von draw() seit Programmstart
keyPressed boolean ob eine Taste derzeit gedrückt ist
key char zuletzt gedrückte Taste
keyCode int zuletzt gedrückte Taste als Zahlencode (z.B. für Cursortasten)
mousePressed boolean ob ein Mausbutton derzeit gedrückt ist
mouseButton int welche der Maustasten aktuell gedrückt ist
displayHeight, displayWidth int Maße des gesamten Bildschirms in Pixeln

Übungsaufgaben

2.7 a) Zeichen-Ticker

Zeichnen Sie das Zeichen der zuletzt gedrückten Taste in die Mitte des Fensters.

(Interaktives Feld - erst anklicken.)

Tipp
Verwenden Sie den Befehl text() und die Systemvariable key.

2.7 b) Mitte

Zeichnen Sie einen Kreis genau in die Mitte des Fensters (Größe 111 x 77). Verwenden Sie dazu width und height. Wenn Sie die Fenstergröße ändern, sollte der Kreis immer in der Mitte bleiben, ohne dass Sie den Ellipse-Befehl ändern.

2.7 c) Zähler *

Unter der Annahme, dass die frameRate konstant bleibt, können Sie mit frameCount einen Sekundenzähler realisieren. Bei Mausklick soll der Zähler auf 0 springen.

Verwenden Sie nicht die Funktion frameRate().

(Interaktives Feld - bei Mausklick stellt sich der Zähler zurück.)

Tipp
Führen Sie eine Variable ein, die sich den "Zeitpunkt" merkt, an dem geklickt wurde und dies in die Berechnung der gezeigten Zahl mit einbringt. Außerdem müssen Sie noch den (relativen) frameCount in Sekunden umwandeln.