Sensitive Partikel in Processing

Hinzugefügt am von Steffen Fiedler

Powered by Processing.js

Dieser interaktive Sketch zeigt, auf welche Art und Weise man visuellen Elementen simple Verhaltensweisen mit auf den Weg geben kann. Grundsätzlich bedienen wir uns dabei dem Ansatz der objekt orientierte Programmierung.
Untergliedert in zwei Teile besteht das Programm aus einem üblichen Processing-Sketch mit setup() / draw() Block, in welchem der Sketch initialisiert und gesteuert wird. Der zweite Teil beschreibt unser Model Circle (bzw. die Klasse Circle); die Beschaffenheit und Funktionalität die jeder Kreis im Sketchfenster mitbringt. Wir nehmen zuerst diesen – spannenderen Teil – genauer unter die Lupe.

Im dafür angelegten Tab Circle leiten wir die Klasse mit dem Ausdruck class ein und legen vorab zwischen den Geschweiften Klammern { … } die Eigenschaften des Kreises fest.
class Circle {
    boolean iq;
    float x, y;
    float xTarget, yTarget;
    float radius = 5;
    //...
}
Neben den üblichen Eigenschaften wie Position und Radius besitzt jeder Kreis unteranderem Information über seine Cleverness iq (schlau oder dumm) und seine Zielposition xTarget, yTarget (alle hier definierte Variablen werden im Programm für jeden einzelnen Kreis vorhanden sein).
Der weitere Code innerhalb des Circle Tabs beinhaltet den Konstruktor zur Initialisierung und einige Methoden (spezifische Fähigkeiten des Kreises). Die wichtigste von diesen ist mit dem Namen move() betitelt und ist für die Bewegung des Kreises verantwortlich. Die Logik hierbei ist eine Simple Frage: Wie nah ist die Maus, bzw. bewegt sich der Kreis zu ihr hin oder von ihr weg? Um ein Antwort zu erhalten, ermitteln wir zu Beginn die Distanz zwischen Maus und Kreis:
if (dist (x, y, mouseX, mouseY) <= RANGE) {
    …
Ist diese Aussage wahr, befindet sich die Maus nah am Kreis und veranlasst diesen sich zu bewegen. Abhängig von seiner Cleverness (die Variable iq) wird das neue Ziel des Kreises entweder dichter zur Mausposition (dumm) oder weiter entfernt(schlau) definiert; und der Bewegungszustand slideStat auf wahr gesetzt. Mit diesem Schritt hat der Kreis ein Ziel und die Information sich zu bewegen.
Im letzten Abschnitt der move() Methode wird der slideStat des Kreises erfragt und wenn true weiter zu seinem Ziel verschoben:
x -= (x - xTarget) / slideSpeed;
y -= (y - yTarget) / slideSpeed;
Die Methode bounceCircle() ist ein weiterer wichtiger Bestandteil der Kreis-Animation und stellt das Nichtverlassen des Sketchfensters sicher.

Im Sketch-Tab Circles-example-01 werden im setup() Block mit der Funktion createCircles() 40 Kreise erzeugt:
for (int i=0; i < circles.length; i++) {
    float x = random (width);
    float y = random (height);
    circles[i] = new Circle (x, y);
}
Initialisiert und versehen mit einer zufälligen Position "existieren" diese Kreise nun in unserem Sketchfenster und müssen in jedem Frame aufgefordert werden auf die Maus zu reagieren. Dies passiert innerhalb des draw() Blocks. Wir laufen mit einer for-Schleife durch das circles Array und rufen die move() Methode auf, bevor wir letztendlich den Kreis an seiner jeweiligen Position mit dem ellipse() Befehl darstellen.

Um die Funktionsweise abzubilden sind folgende Tastenbelegungen im Programm implementiert:
  • n Sketch mit erneut mit 40 Kreisen befühlen
  • t Kreis-Ziele anzeigen/ausblenden
  • r Sensitiven Bereich anzeigen/ausblenden

Der Sketch

// Display setting: zeichne sensitiven Bereich
boolean dRange = true;
// Display setting: zeichne Ziel
boolean dTarget = true;
// Display setting: zeichne Schweif
boolean dTail = true;

// Array zum Ablegen aller circles
Circle circles[] = new Circle[40];

void setup() {
    // Sketch-Einstellungen
    size (550, 220);
    smooth ();
    // Erstelle circles an zufälligen Positionen
    createCircles ();
}

void draw() {
    background (79);

    // Für jeden einzelnen circle im 
    // array 'circles'...
    for (int i=0; i < circles.length; i++) {

        // Bewege circle 'i'
        circles[i].move ();

        // Zeige debug-Informationen an,
        // wenn diese aktiviert sind
        if (dRange) displayRange(i);
        if (dTarget) displayTarget(i);
        if (dTail) displayTail(i);

        // Lege die Füllfarbe für den 'circle' fest
        // entsprechend seiner 'Fluchteigentschaft'
        if (circles[i].iq) {
            fill (229, 90, 38);
        }
        else {
            fill (138, 170, 178);
        }

        // Setze Strichfarbe auf weiß
        stroke (239);

        // Zeichne den circle 'i' an seiner Position
        ellipse (circles[i].x, circles[i].y, circles[i].radius*2, circles[i].radius*2);
    }
}

// Methode zum Erstellen und Befüllen des 'circles' 
// Arrays. Alle circles werden zufällig positioniert.
void createCircles () {
    // Für jedes Feld im 'circles' array...
    for (int i=0; i < circles.length; i++) {
        // Ermittle Zufallsposition
        float x = random (width);
        float y = random (height);
        // Erstelle neuen circle
        circles[i] = new Circle (x, y);
    }
}

// Methode zum Darstellen des sensitiven Bereichs
// um einen circle herum.
void displayRange (int id) {
    // Wenn der circle 'id' sichtbar ist...
    if (circles[id].radius > 0) {
        // Aktiver 'range' Durchmesser
        float diam = circles[id].RANGE * 2;
        // Deaktiviere Füllfarbe
        noFill ();
        // Setze Linienfarbe
        stroke (150);
        // Zeichne Range-Bereich
        ellipse (circles[id].x, circles[id].y, diam, diam);
    }
}

// Methode zum Darstellen des Bewegungs-Ziels.
void displayTarget (int id) {
    // Wenn circle 'id' sichtbar ist...
    if (circles[id].radius > 0) {
        // Setze Strichfarbe
        stroke (239);
        // Zeichne Linie zwischen aktueller circle-Position
        // und dem circle-Ziel
        line (circles[id].x, circles[id].y, circles[id].xTarget, circles[id].yTarget);
    }
}

// Methode zum Darstellen des circle-Schweifs.
void displayTail (int id) {
    // Wenn circle 'id' sichtbar ist...
    if (circles[id].radius > 0) {
        // Deaktiviere Füllfarbe
        noFill ();
        // Für alle Schweifpositionen
        for (int i=circles[id].tailArr.length-1; i > 0; i--) {
            // Passe Strichfarbe an
            stroke (170, 170, 170, i*20);
            // Passe Kreisradius an
            float rad  = circles[id].radius*8/i;
            // Zeichne Schweifelement
            ellipse (circles[id].tailArr[i][0], circles[id].tailArr[i][1], rad, rad);
        }
    }
}

// Tastatur-Event zum Einstellen der display settin
void keyPressed () {
    if (key == 'b' || key == 'B') dTail = !dTail;
    if (key == 'n' || key == 'N') createCircles();
    if (key == 'r' || key == 'R') dRange = !dRange;
    if (key == 't' || key == 'T') dTarget = !dTarget;
}
class Circle {
    
    // Größe des sensitiven Circle-Bereichs
    float RANGE = 30;
    
    // Bewegungszustand (ja/nein)
    boolean slideStat = false;
    // Cleverness (schlau=true,dumm=false)
    boolean iq;
    
    // Aktuelle Circle-Position
    float x, y;
    // Circle-Zielposition
    float xTarget, yTarget;
    // Circle Radius
    float radius = 5;
    
    // Fliehdistanz
    float slideRange = 6;
    float slideSpeed = 12;
    
    // Maximale Anzahl an Schweifelementen
    int maxTailNum = 21;
    // Aktuelles Schweifelement
    int tailCount;
    
    // Schweif-array mit x/y Positionen
    // für 'maxTeilNum' Schweifelemente
    float[][] tailArr = new float[maxTailNum][2];
    
    
    // Konstrutor Funktion zum Initialisieren
    // eines neuen Circles.
    Circle (float xPos, float yPos) {
        
        // Übernimm Startposition
        x  = xPos;
        y  = yPos;
        // Definieren zufälliges Start-Ziel
        xTarget = random (x-2, x+2);
        yTarget = random (y-2, y+2);
        // Setze 'schlau/dumm' Status
        // basierent auf 50/50 chance
        iq = (random (1) < 0.5f);
        
        tailCount = 0;
        
        // Erstelle leeres Schweif-array und
        // setze alle Positionen auf die aktuelle
        for (int i=0; i < this.tailArr.length; i++) {
            tailArr[i][0] = x;
            tailArr[i][1] = y;
        }
    }
    
    // Funktion zum Bewegen des Circles. Je nach 'cleverness'
    // bewegt sich dieser zur Maus hin bzw. weg.
    public void move () {
        
        // Wenn die Distanz zwischen Circle und Maus-Position
        // kleiner/gleich der sensitiven RANGE ist...
        if (dist (x, y, mouseX, mouseY) <= RANGE) {
            // Wenn Circle schlau ist...
            if (iq) {
                // Verschiebe Circle weg von der Maus
                xTarget = x - (mouseX - x) * slideRange;
                yTarget = y - (mouseY - y) * slideRange;
            }else{
                // Verschiebe Circle hin zur Maus
                xTarget = mouseX + radius * 2;
                yTarget = mouseY + radius * 2;
            }
            // Setze den Bewegungsstatus auf true
            slideStat = true;
        }
        
        // Wenn Circle dumm ist...
        if (!iq) {
            // Wenn die Maus den Circle berührt...
            if (dist (x, y, mouseX, mouseY) <= radius + 1) {
                // Wenn Circle-radius größer als 0 ist...
                if (radius > 0) {
                    // Reduziere Radius um 0.2
                    radius -= 0.2;
                }else{
                    // Anderenfalls setze Radius auf 0
                    radius = 0;
                }
            }
        }
        
        // Wenn Bewegungsstatus 'true' ist...
        if (slideStat) {
            // Überprüfe ob Distanz zwischen Circle-Position
            // und Circle-Ziel kleiner/gleich 1 ist...
            if (dist (x, y, xTarget, yTarget) <= 1) {
                // Wenn ja, setze Bewegungsstatus auf 'false'
                slideStat = false;
            }
            
            // Teste ob sich der Circle aus dem 
            // Sketchfenster bewegt
            bounceCircle ();
            
            // Bewege den Circle einen 'slideSpeed'
            // Schritt zu seinem Ziel
            x -= (x - xTarget) / slideSpeed;
            y -= (y - yTarget) / slideSpeed;
            
            // Füge die Aktuelle Position zum Schweif hinzu
            createTail ();
        }else{
            // Anderenfalls, bearbeite Schweif
            removeTail ();
        }
    }
    
    
    // Funktion zum Abprallen des Circles an den
    // Rändern des Sketchfensters.
    private void bounceCircle () {
        // Linke Seite des Sketchfensters
        if (x <= radius * 2) {
            xTarget = x + (x - xTarget);
            yTarget = y - (y - yTarget);
        }
        // Obere Seite des Sketchfensters
        if (y <= radius * 2) {
            xTarget = x - (x - xTarget);
            yTarget = y + (y - yTarget);
        }
        // Rechte Seite des Sketchfensters
        if (x >= width - radius * 2) {
            xTarget = x - (xTarget - x);
            yTarget = y - (y - yTarget);
        }
        // Untere Seite des Sketchfensters
        if (y >= height - radius * 2) {
            xTarget = x - (x - xTarget);
            yTarget = y - (yTarget - y);
        }
    }
    
    private void createTail () {
        if (slideStat == true) {
            if (tailCount > maxTailNum-1) {
                tailCount = 0;
            }
            tailArr[tailCount][0] = x;
            tailArr[tailCount][1] = y;
        }
        tailCount++;
    }
    
    private void removeTail () {
        if (slideStat == false) {
            for (int i=0; i < tailArr.length; i++) {
                tailArr[i][0] = -radius * 1.2;
                tailArr[i][1] = -radius * 1.2;
            }
        }
    }
}