Struktur III: Objekte und Agenten

Objekt/Klasse: Mover, Objekte verwenden, Ball, viele Bälle, Bälle hinzufügen und entfernen, Schriftagenten: außen, Schriftagenten: innen

Objekt/Klasse: Mover

Ein bewegliches Objekt...

class Mover {
}

...hat eine Position (Eigenschaften)...

float x;
float y;
...die zu Beginn seiner Existenz initialisiert wird:
Mover() {
  x = width/2;
  y = height/2;
}

Es kann sich mit jedem Frame zeichnen lassen...

void display() {
  point(x,y);
}

...und verändert sich von Frame zu Frame (Methoden).

void update() {
  float r = random(4);
  if (r>3) {
    x = x + 1;
  } else if (r>2) {
    x = x - 1;
  } else if (r>1) {
    y = y + 1;
  } else {
    y = y - 1;
  }
}

Insgesamt:

class Mover {
  float x;
  float y;
  
  Mover() {
    x = width/2;
    y = height/2;
  }
  
  void display() {
    point(x,y);
  }
  
  void update() {
    float r = random(4);
    if (r>3) {
      x = x + 1;
    } else if (r>2) {
      x = x - 1;
    } else if (r>1) {
      y = y + 1;
    } else {
      y = y - 1;
    }
  }
}

Objekte verwenden

Der Mover ist als Variable verwendbar...

Mover m;

...und muss initialisiert (erzeugt) werden (new!):

void setup() {
  size(400,300);
  background(255);
  m = new Mover();
}

Jetzt kann er mit jedem Frame gezeichnet und aktualisiert werden:

void draw() {
  m.update();
  m.display();
}

Ball: Geschwindigkeit und Kollisionen

Ein Ball besitzt Variablen für Position und Geschwindigkeit. Abhängig davon bewegt er sich von Frame zu Frame.

class Ball {
  float x;
  float y;
  float vx;
  float vy;
  
  Ball() {
    x = width/2;
    y = height/2;
    vx = random(-4,4);
    vy = random(-4,4);
  }
  
  void display() {
    strokeWeight(16);
    point(x,y);
  }
  
  void update() {
    x += vx;
    y += vy;
  }
}

Wenn er an einer Wand ankommt, dreht er um (siehe auch: ||).

  void update() {
    x += vx;
    y += vy;
    if (x < 0 || x > width) {
      vx = -vx;
    }
  }

Collection und Schleife: viele Bälle

Eine Variable als Behälter für Objekte: ArrayList, zwischen < und > ist angegeben, was enthalten ist.

ArrayList<Ball> baelle;

void setup() {
  size(800,600);
  baelle = new ArrayList();
  for (int i=0; i<10; i++) {
    baelle.add(new Ball());
  }
}

void draw() {
  background(255);
  for (int i=0; i<baelle.size(); i++) {
    Ball nextBall = baelle.get(i);
    nextBall.update();
    nextBall.display();
  }
}

(neuer Tab für den Ball...)

class Ball {
  float x;
  float y;
  float vx;
  float vy;

  Ball() {
    x = width/2;
    y = height/2;
    vx = random(-4, 4);
    vy = random(-4, 4);
  }

  void display() {
    stroke(100,100,255);
    strokeWeight(16);
    point(x, y);
  }

  void update() {
    x = x+vx;
    y = y+vy;
    if (x<0) { 
      vx = -vx; 
      x = 0;
    }
    else if (x>width) {
      vx = -vx; 
      x = width;
    }
    
    if (y<0) { 
      vy = -vy; 
      y = 0;
    }
    else if (y>height) { 
      vy = -vy; 
      y = height;
    }
  }
}

Add/remove: Bälle hinzufügen und entfernen

Die Liste der Bälle kann jederzeit verändert werden. Dazu zunächst eine Anpassung an die Ball-Klasse: Bälle erzeugen an Wunschposition. Achtung: Ball() gibt es doppelt. Ball() ohne Eingabe zwischen den Klammern startet in der Mitte, Ball(xpos, ypos) startet an der festeglegten Position.

class Ball {
  float x;
  float y;
  float vx;
  float vy;

  Ball() {
    x = width/2;
    y = height/2;
    vx = random(-4, 4);
    vy = random(-4, 4);
  }
  Ball(float xpos, float ypos) {
    x = xpos;
    y = ypos;
    vx = random(-4, 4);
    vy = random(-4, 4);
  }

  void display() {
    stroke(100,100,255);
    strokeWeight(16);
    point(x, y);
  }

  void update() {
    x = x+vx;
    y = y+vy;
    if (x<0) { 
      vx = -vx; 
      x = 0;
    }
    else if (x>width) {
      vx = -vx; 
      x = width;
    }
    
    if (y<0) { 
      vy = -vy; 
      y = 0;
    }
    else if (y>height) { 
      vy = -vy; 
      y = height;
    }
  }
}

Jetzt Ball hinzufügen bei Mausklick mit add() und der neuen Erzeugung mit Position:

ArrayList<Ball> baelle;

void setup() {
  size(800,600);
  baelle = new ArrayList();
}

void draw() {
  background(255);
  for (int i=0; i<baelle.size(); i++) {
    Ball nextBall = baelle.get(i);
    nextBall.update();
    nextBall.display();
  }
}

void mousePressed() {
  baelle.add(new Ball(mouseX,mouseY);
}

Auf rechte und linke Maustaste reagieren mit mouseButton (Achtung: ==). Löschen von Bällen mit remove(). Der älteste Ball steht am Ende der Liste. Das ist size()-1, weil die Liste von 0 an durchnumeriert ist.

void mousePressed() {
  if (mouseButton == LEFT) {
    baelle.add(new Ball(mouseX,mouseY));
  } else if (mouseButton == RIGHT) {
    baelle.remove(baelle.size()-1);
  }
}

Damit es keine Fehler beim Löschen gibt: Testen, ob die Liste leer ist.

void mousePressed() {
  if (mouseButton == LEFT) {
    baelle.add(new Ball(mouseX,mouseY));
  } else if (mouseButton == RIGHT) {
    if (baelle.size() > 0) {
      baelle.remove(baelle.size()-1);
    }
  }
}

Schriftagenten: außen

Schriftgeometrie erzeugen mit Hilfe der Geomerative-Library. Diesmal interessiert nur die Form der Schrift (RShape), nicht die Punkte, die ihre Geometrie beschreiben (RPoint).

import geomerative.*;

RShape textForm;

void setup() {
  size(820, 300);
  
  RG.init(this);
  RFont font;
  font = new RFont("cour.ttf", 150, RFont.LEFT);
  String text = "Form&Code";
  RCommand.setSegmentLength(0);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  textForm = font.toShape(text);
  
  background(255);
}

Der Mover/Agent wird in update() so angepasst, dass er merkt, ob er die Kontur der Schrift berührt. Die Funktion RShape.contains(...) teilt dabei mit, ob ein Punkt innerhalb der Schriftform liegt. Wenn das so ist (if (...)), wird die Verschiebung (dx, dy) von x und y rückgängig gemacht.

  void update() {
    float r = random(4);
    float dx = 0;
    float dy = 0;
    if (r>3) {
      dx = 4;
    } else if (r>2) {
      dx = - 4;
    } else if (r>1) {
      dy = 4;
    } else {
      dy = - 4;
    }
    x = x + dx;
    y = y + dy;
    if (textForm.contains(new RPoint(x,y))) {	// Test ob innerhalb der Schrift
      x = x - dx;
      y = y - dy;
    }
  }

Gezeichnet werden die Agenten weiter als einfache Punkte.

  void display() {
    strokeWeight(3);
    stroke(0,32);
    point(x,y);
  }

Beim Erzeugen müssen die Punkte so gewürfelt werden, dass sie nicht innerhalb der Schrift liegen. So lange (while (...)) ein Punkt also innerhalb der Schrift liegt (textForm.contains (new RPoint (x, y))), werden die Zufallszahlen neu bestimmt.

  Mover() {
    x = random(width);
    y = random(-100, 50);
    while (textForm.contains (new RPoint (x, y))) {
      x = random(width);
      y = random(-100, 50);
    }
  }

Jetzt die Agenten als Array deklarieren und im setup() erzeugen...

import geomerative.*;

Mover[] agenten;
RShape textForm;

void setup() {
  size(820, 300);
  
  RG.init(this);
  RFont font;
  font = new RFont("cour.ttf", 150, RFont.LEFT);
  String text = "Form&Code";
  RCommand.setSegmentLength(0);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  textForm = font.toShape(text);
  
  background(255);
  
  agenten = new Mover[50];
  for (int i=0; i<agenten.length; i++) {
    agenten[i] = new Mover();
  }
}

...und in draw() updaten und zeichnen.

void draw() {
  translate(0, height/2+25);
  for (int i=0; i<agenten.length; i++) {
    agenten[i].update();
    agenten[i].display();
  }
}

Reset aller Agenten mit der Maus.

void mousePressed() {
  for (int i=0; i<agenten.length; i++) {
    agenten[i] = new Mover();
  }
}

Schriftagenten: innen

Sicherstellen, dass die Agenten innerhalb der Schriftform starten. Statt...

textForm.contains (new RPoint (x, y))

...die Verneinung (nicht/not) benutzen: !

!textForm.contains (new RPoint (x, y))
  Mover() {
    x = random(width);
    y = random(-400, 0);
    while (!textForm.contains (new RPoint (x, y))) {
      x = random(width);
      y = random(-400, 0);
    }
  }

Zeichnen der Agenten als Linien. Dafür die Position aus dem letzten Frame merken (px, py) und diese mit der aktuellen Position (x, y) verbinden. Der Mover bekommt also zwei neue variablen:

class Mover {
  float x;
  float y;
  float xp;
  float yp;
  // usw...

...die beim Zeichnen verwendet werden...

  void display() {
    strokeWeight(1);
    stroke(0,32);
	line(x,y,xp,yp);
  }

...und beim update() aktualisiert. Hier wird ebenfalls überprüft, ob die neue Position immer noch innerhalb der Schrift liegt.

  void update() {
    xp = x;
    yp = y;
    float r = random(4);
    float dx = 0;
    float dy = 0;
    dx = random(-15,15);
    dy = random(-15,15);
    x = x + dx;
    y = y + dy;
    if (!textForm.contains(new RPoint(x,y))) {
      x = x - dx;
      y = y - dy;
    }
  }

Insgesamt zwei Tabs: Einer mit dem Hauptprogramm...

import geomerative.*;

Mover[] agenten;
RShape textForm;

void setup() {
  size(300, 300);
  
  RG.init(this);
  RFont font;
  font = new RFont("cour.ttf", 400, RFont.LEFT);

  String text = "A";
  RCommand.setSegmentLength(0);
  RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
  textForm = font.toShape(text);
  
  background(255);
  
  agenten = new Mover[10];
  for (int i=0; i<agenten.length; i++) {
    agenten[i] = new Mover();
  }
}

void draw() {
  translate(30, 270);
  for (int i=0; i<agenten.length; i++) {
    agenten[i].update();
    agenten[i].display();
  }
}

...und einer mit dem Mover für das Innere der Schriftform.

class Mover {
  float x;
  float y;
  float xp;
  float yp;
  
  Mover() {
    x = random(width);
    y = random(-400, 0);
    while (!textForm.contains (new RPoint (x, y))) {
      x = random(width);
      y = random(-400, 0);
    }
  }
  
  void display() {
    strokeWeight(1);
    stroke(0,32);
    line(x,y,xp,yp);
  }
  
  void update() {
    xp = x;
    yp = y;
    float r = random(4);
    float dx = 0;
    float dy = 0;
    dx = random(-15,15);
    dy = random(-15,15);
    x = x + dx;
    y = y + dy;
    if (!textForm.contains(new RPoint(x,y))) {
      x = x - dx;
      y = y - dy;
    }
  }
}