MausinteraktionGrundlagen für die Gestaltung interaktiver Programme

Nach einer kurzen Einführung in die Erstellung unendlich fortlaufender Programme werden die Grundlagen für die Interaktion mit der Maus in Processing vorgestellt. Dabei wird auf die Verwendung der Mausposition sowie auf die Abfrage der Maustasten eingegangen.

Vorbereitung: Fortlaufende Programme

Grundsätzlich werden alle im Vorherigen geschriebenen Programme nach einmaligem Durchlaufen beendet. Das Resultat wird im definierten Fensterbereich abgebildet - weitere Modifikationen am Dargestellten durchzuführen ist jedoch nicht möglich. Um dies zu ermöglichen müssen im Programm spezielle Bereiche angelegt werden. Block eins setup() und zwei draw().
  • setup() wird direkt nach dem Programmstart einmalig ausgeführt.
  • draw() wird nach dem Ablauf von setup() ins Leben gerufen und läuft in einer Schleife bis das Fenster geschlossen wird.
Wenn beide Blöcke vorhanden sind und wir das Programm starten wird zuerst der Inhalt ( {…} ) des setup() Blocks abgearbeitet. Nach Beendigung steigt die Applikation in den draw() Teil ein und führt diesen in einer Schleife aus. D.h. sobald die letzte Anweisung im draw() ausgeführt wurde springt sie zur ersten Zeile im draw() Block - bis das Sketchfenster geschlossen wird.
Bsp.: unendlich laufendes Programm
// Aufruf in einer Schleife
// bis zum Schließen des Fensters
void draw () {
  fill (255, 0, 0, 1);
  rect (10, 10, 80, 80);
}
Das Programm zeichnet 60 mal in der Sekunde ein fast nicht sichtbares rotes Quadrat in die Zeichenfläche. Durch die Überlagerungen der einzelnen Objekte addieren sich die Transparenzen und das Quadrat erhält eine bestechende Farbe.
Bsp.: Initialisierung & Fortlauf
// Aufruf einmal direkt nach
// dem Programmstart
void setup () {
  fill (0, 125, 0, 50);
  rect (40, 40, 20, 20);
}

// Aufruf in einer Schleife
// nach Beendigung des "setup" Blocks
// bis zum Schließen des Fensters
void draw () {
  fill (255, 0, 0, 4);
  rect (10, 10, 80, 80);
}
Im setup() Block wird nach dem Festlegen der Füllfarbe auf ein Grün ein Rechteck einmal gezeichnet. Nach Beendigung dieser Anweisungen wird der draw() Block bis zum Schließen des Fensters in einer Schleife ausgeführt. D.h. es wird permanent ein rotes, stark transparentes Rechteck in die Arbeitsfläche gezeichnet. Nach einer gewissen Zeit wurden genügend Rechtecke platziert um den grünen Farbton zu verdecken. Diese Prozedur müsste ohne die Unterteilung in setup() und draw() nach dem folgenden Schema ablaufen:
// setup() direkt nach dem Programmstart
fill (0, 125, 0, 50);
rect (40, 40, 20, 20);
// draw() in einer unendlichen Schleife
// Durchlauf 1
fill (255, 0, 0, 4);
rect (10, 10, 80, 80);
// Durchlauf 2
fill (255, 0, 0, 4);
rect (10, 10, 80, 80);
// Durchlauf 3
// ...
Einen Durchlauf des draw() Abschnitts bezeichnet man als Einzelbild (ursprünglich aus dem Film). Processing versucht pro Sekunde 60 dieser Einzelbilder zu erzeugen und im Sketchfenster abzubilden. Diese Bildfrequenz wird im Bereich der Computergrafik als frameRate bezeichnet.
  • frameRate beinhaltet die aktuell angestrebte Anzahl von Einzelbildern (frames) der Processing Sketches.
  • frameRate() erlaubt das Steuern der Bildfrequnz (wie oft der draw() Block pro Sekunde abgearbeitet wird) durch Angabe einer Ganzzahl. Für Processing stellt es jedoch nur einen Richtwert dar - Abweichungen sind möglich.
Bsp.: Bildfrequnz
void setup () {
  // festlegung der Bildrate auf ein 
  // Bild pro Sekunde
  frameRate (1);
}

void draw () {
   fill (255, 0, 0, 15);
   rect (20, 20, 60, 60);
}

Mausinteraktion

Mausposition

Die Kommunikation zwischen Benutzer und Programm kann auf vielfältige Weise geschehen. Im Folgenden wollen wir uns auf den Bereich der Maus - deren Position in der Zeichenfläche - beschäftigen. Die Processingumgebung bietet für diesen Zweck vier Ausdrücke: mouseX, mouseY für die aktuelle Position der Maus und pmouseX, pmouseY für die Position im vorherigen Bild. Alle vier Angaben geben dabei die Distanz zum Koordinatenursprung (oben, links) in Pixeln auf der jeweiligen Achse an.
Bsp.: aktuelle Mausposition 1
void draw () {
  ellipse (mouseX, mouseY, 20, 20);
}
In jedem draw() Durchlauf wird ein Kreis an der aktuellen Mauskoordinaten positioniert. In der Vergangenheit gezeichnete Kreise befinden sich im Hintergrund und können bei Überlagerungen verdeckt werden.
Bsp.: aktuelle Mausposition 2
void draw () {
  background (255, 255, 255);
  ellipse (mouseX, mouseY, 20, 20);
}
Durch das Aufrufen des background() Befehls wird vor jedem Zeichnen eines Kreises die Arbeitsfläche vollflächig weiß gefüllt. Es scheint als würde sich ein und der selbe Kreis nach den Vorgaben der Maus bewegen.
Bsp.: aktuelle und vorherige Mausposition
void setup () {
    frameRate (5);
}

void draw () {
  background (255, 255, 255);
  line (pmouseX, pmouseY, mouseX, mouseY);
}
Bei schneller Bewegung der Maus wird eine schwarze Linie sichtbar. Diese wird zwischen der vorherigen und der aktuellen Mausposition gezeichnet und stellt die während eines Zeichenvorgangs zurückgelegte Distanz dar. Das "p" in pmouseX und pmouseY steht dabei für den englischen Begriff previous (dt.: früher, vorig).

Maustasten

Um in einem Processing Programm herauszufinden, ob die Maustaste gedrückt ist müssen wir lediglich den Zustand der Maustaste "abfragen". Genau eine solche Abfrage können wir mit einem neuen Befehl stellen:
  • if (Bedingung ) also "falls" eine von uns genannte Bedingung "wahr" ist(true), wird der darauf folgende Abschnitt in geschweiften Klammern ausgeführt ( {…} )
Für unsere Abfrage, ob die Taste der Mausgedrückt ist, haben wir nun eine Eigenschaft in unserem Processing Programm ähnlich der width oder height Eigenschaft zur Verfügung:
  • mousePressed ist die Maustaste gedrückt, oder nicht.
Folgendes Beispiel demonstriert, wie man diese Abfrage richtig stellt.
Bsp.: Drücken der Maustaste I
// beim zeichnen kommt nun nichts weiter hinzu
// als die Abfrage, ob die Maus gedrückt ist mit Hilfe
// der "if" Abfrage (Statement)
void draw() {
  // lösche den Hintergrund und fülle ihn mit weiss
  background (255, 255, 255);

  // wenn die Maus gedrückt ist…
  if (mousePressed) {
    // zeichne eine ellipse an der Mausposition
    ellipse (mouseX, mouseY, 10, 10);
  }

  // dieser Teil wird immer ausgeführt
  // (unabhängig vom Mauszustand)
  rect (90, 90, 10, 10);
}
Bei einer if-Abfrage wird immer zwischen Ja und Nein entschieden. Im oberen Beispiel haben wir den Status der Maustaste als Bedingung festgelegt - und zeichnen bei "Wahrheit" einen Kreis. Als Gegenstück zum Ja-Abschnitt können wir einen Bereich definieren der nur ausgeführt wird wenn die Maustaste nicht gedrückt wird.
  • else leitet einen Programmblock ein der bei Nichtzutreffen der if-Bedingung abgearbeitet wird. Dieser Block wird ebenfalls von zwei geschweiften Klammern umfasst ( {...} ).
Bsp.: Drücken der Maustaste II
void draw() {
  // lösche den Hintergrund und fülle ihn mit weiss
  background (255, 255, 255);

  // wenn die Maus gedrückt ist…
  if (mousePressed) {
    // zeichne eine ellipse an der Mausposition
    // wenn die Maustaste gedrückt ist
    ellipse (mouseX, mouseY, 10, 10);
  }else{
    // zeichne ein Rechteck an der Mausposition
    // wenn die Maustaste nicht gedrückt ist
    rectMode (CENTER);
    rect (mouseX, mouseY, 30, 30);
  }
}

Anwendungsbeispiele: Mausinteraktion

Elementar für die Interaktion mit der Maus sind Abfragen ob sich ein Punk in einem bestimmten Bereich befindet. Angefangen bei simplen Elementen wie beispielsweise rechteckigen Buttons bis hin zu komplexen Ansammlungen die einen Charakter eines Spiels darstellen funktioniert dies immer nach dem selben Prinzip.
Punkt in Kreis in Processing

Beim Test ob sich ein Punkt, in diesem Fall die Mausposition, in der Fläche eines Kreises befindet bedarf es wenig Aufwand. Indem der Abstand zwischen dem Punkt und dem Zentrum des Kreises mit dem Radius dessen verglichen wird, erhält man eine konkrete Aussage ob der Fall zutrifft oder nicht. Der dist() Befehl erleichtert uns das Arbeiten und liefert die Entfernung zwischen zwei Punkten ohne das mit Quadrat oder »Wurzel aus...« gearbeitet werden muss.

float x;  // x-Position
float y;  // y-Position
float rad = 70; // Radius

void setup () {
  size(320, 240);
  noStroke ();
  smooth ();
  
  // Kreis im Sketch zentrieren
  x = width / 2;
  y = height / 2;
}

void draw () {
  background (255);
  
  /* Wenn der Abstand zwischen Maus und 
   * Kreiszentrum kleiner als der Radius 
   * des Kreises ist -> Cursor im Kreis!
   */
  if (dist (mouseX, mouseY, x, y) < rad) {
    fill (255, 0, 0);
  }else{
    fill (0);
  }
  // Zeichne Kreis
  ellipse (x, y, rad * 2, rad * 2);
}
Punkt in Rechteck in Processing

Es wird überprüft, ob sich die Koordinaten des Punktes in beiden Achsenabschnitten befinden(2D). Dh. auf der x-Achse muss sich die x-Koordinate des Punktes zwischen der linken und rechten Außenseite des Rechtecks befinden. Trifft dies auch für die y-Achse zu, ist der Punkt innerhalb der durch das Rechteck bestimmten Fläche. Im Beispiel sind beide if-Abfragen ineinander verschachtelt. Wenn die Erste (für die x-Achse) nicht zutreffen sollte, brauch die Zweite nicht ausgeführte werden - beide müssen erfüllt sein.

float x = 20;  // x-Position
float y = 28;  // y-Position
float w = 55;  // Breite
float h = 30;  // Höhe

void setup () {
  size(320, 240);
  noStroke ();
}

void draw () {
  background (255);
  
  /* standardmäßig ist die Maus nicht
   * über dem Rechteck. Es sei denn, 
   * "hit" wird im folgenden Bedingung-
   * block auf true gesetzt.
   */
  boolean hit = false;
  
  /* Wenn sich die Maus auf der x-Achse
   * zwischen Flächenanfang und Ende befindet.
   */
  if (mouseX > x && mouseX < x + w) {
    /* Wenn sich die Maus auf der y-Achse
     * zwischen Flächenanfang und Ende befindet.
     */
    if (mouseY > y && mouseY < y + h) {
      // setze "hit" auf true!
      hit = true;
    }
  }
  
  // Bestimme Füllfarbe am Zustand von "hit"
  if (hit == true) {
    fill (255, 0, 0);
  }else{
    fill (0);
  }
  // Zeichne Rechteck
  rect (x, y, w, h);
}