Processing – Über dieses Weblog
Dieses Weblog gibt eine Einführung in die Programmierung mit Processing.
Basics:
- Processing verwenden
- Die Programmstruktur
- Das Koordinatensystem
- Einfache Formen
- Variablen und Datentypen
- Strings
- Mathematische Operationen
- Mouse Input
- If-Anweisung
- Bedingungen mit switch – case
- Schleifen
- Textausgabe
- Tastatureingaben
- Projekt Ping Pong
- Farbe
- Fotos
- Zufall
- Funktionen
- Winkel und Wellen
- Transformationen
Advanced:
- Viele Arten ein Programm zu erstellen
- Events
- Easing
- Datenfelder (Arrays)
- ArrayLists()
- Lesen und schreiben von Textdateien
- Vektoren
- XML-Element
- Recursionen
- Objekte und Klassen
- Agenten
- Libraries verwenden
- Processing mit Eclipse entwickeln
Android Apps mit Processing erstellen:
- Android Grundlagen
- Erste App erstellen
- Multitouch
- Vibration
- OptionsMenü
- Erstellen einer .apk-Datei für den Market
- Processing Sketch in eine vollständige App einbinden
Projekte:
- Bild aus Text
- Facebook Anwendung I
- Android App Pure Pong!
- Korruption in der Demokratie
- 3D wie beim Magischen Auge
Libraries
Für Kommentare, Anregungen und Verbesserungsvorschläge bin ich jederzeit dankbar!
Projektseite: http://processing.org/
Hier findest du neben dem Programm selbst auch noch viele Anwendungsbeispiele, die Programm – Referenz und einige gute Tutorials für den Einstieg in Processing.
Twitter API für Processing
Eine sehr gute Beschreibung der Funktionsweise in englischer Sprache findet man hier: http://blog.blprnt.com/blog/blprnt/updated-quick-tutorial-processing-twitter.
1. Twitter Library herunterladen und im Sketch platzieren
Für die Kommunikation mit der Twitter API gibt es schon eine fertige Java Library, nämlich twitter4j. Du kannst “latest stable version” hier herunterladen. Nutzen kann man die Library, nachdem man .zip Datei extrahiert und dann das Java Archiv twitter4j-core-2.2.5.jar in den (villeicht noch nicht vorhandenen) /code Ordner im Sketch Ordner kopiert hat. Man kann das auch bewerkstelligen, indem man die Datei einfach auf die Processing IDE zieht.
Link zur Javadoc von twitter4j.
Sehr interessant ist, dass es von der Library auch eine Android Version gibt. Falls das jemand getestet hat, bitte ich im Feedback.
2. Einen Entwickler Account bei Twitter erstellen
- Gehe auf die Homepage https://dev.twitter.com/.
- Klicke “create an App” und dann “sign in”
- Formular ausfüllen und Nutzungsbedingungen akzeptieren.
- Nun bist du auf diese Seite gelangt:

- Von dieser Seite brauchst du den Consumer key und das Consumer secret.
- Klicke Create my access token
- Nun hast du auch Access token und das Access token secret. Damit kannst du nun deinen Processing Sketch mit der Twitter API verbinden.
3. Sketch erstellen und twitter4j einbinden
//Verbindung herstellen
ConfigurationBuilder cb = new ConfigurationBuilder();
cb.setOAuthConsumerKey("eigenerSchlüssel");
cb.setOAuthConsumerSecret("eigenerSchlüssel");
cb.setOAuthAccessToken("eigenesToken");
cb.setOAuthAccessTokenSecret("eigenesToken");
//Abfrage starten
Twitter twitter = new TwitterFactory(cb.build()).getInstance();
Query query = new Query("pink floyd now playing");
query.setRpp(5);
//tryCatch, damit das Programm nicht abstürzt, wenn die Abfrage
//mal schief geht
try {
//Abrufen der Seiten mit den Suchergebnissen
for (int j=1; j<=15 ; j++) {
QueryResult result = twitter.search(query.page(j));
ArrayList tweets = (ArrayList) result.getTweets();
//Abrufen der einzelnen Tweets
for (int i = 0; i < tweets.size(); i++) {
Tweet tweet = (Tweet) tweets.get(i);
String user = tweet.getFromUser();
String msg = tweet.getText();
long geo=tweet.getFromUserId();
Date d = tweet.getCreatedAt();
//Abrufen des Heimatortes des Users
User USER = twitter.showUser(geo);
String location = USER.getLocation();
println("Page: "+j+" "+i+": Tweet by " + user + " at " + d +"from: "+location );
}
}
}
catch (TwitterException te) {
println("Couldn't connect: " + te);
};
- Mit der TwitterFactory stellt die twitter4j Library ein Objekt zu Verfügung, das eine Abfrage durchführen kann.Twitter twitter = new TwitterFactory(cb.build()).getInstance();
- Mittels der Query kann die Abfrage spezifiziert werden.
Query query = new Query(“pink floyd now playing”);
- Mit query.setRpp(5) stellt man die Anzahl der Treffer pro Seite ein (maximal 100). Außerdem kann man mit query.since(Datum) und query.until(Datum) die Suche zeitlich eingrenzen.
- Man kann maximal 15 Seiten mit max. 100 Treffern pro Seite abrufen.
//Abrufen der Seiten mit den Suchergebnissen
for (int j=1; j<=15 ; j++) {
- Die Treffer einer Seite werden in einem QueryResult Objekt gespeichert und dann ine eine ArrayList kopiert.
QueryResult result = twitter.search(query.page(j));
ArrayList tweets = (ArrayList) result.getTweets();
- Aus dieser ArrayList rufen wir dann Treffer für Treffer ab und extrahieren die benötigten Daten aus dem Tweet Objekt.
//Abrufen der einzelnen Tweets
for (int i = 0; i < tweets.size(); i++) {
Tweet tweet = (Tweet) tweets.get(i);
String user = tweet.getFromUser();
String msg = tweet.getText();
long geo=tweet.getFromUserId();
Date d = tweet.getCreatedAt();
- Falls man noch den Heimatort des Posters abrufen möchte, kann man das mit diesem Code machen:
//Abrufen des Heimatortes des Users
User USER = twitter.showUser(geo);
String location = USER.getLocation();
Da dies die Heimatort Einträge aus dem UserProfil sind, bekommt man natürlich auch Einträge wie: “Zwischen Seite 299 und 300″. Damit muss man leben.
3D wie beim Magischen Auge
Wer kennt sie nicht? Die die 3D Bilder der Buchreihe “Das magische Auge” die Anfang der 90er erschien. Bei den 3D Bildern handelt es sich um sogenannte Stereogramme. Das sind räumlich wirkende Darstellungen zu deren Betrachtung man keine weiteren Hilfsmittel wie etwa eine Rot- Grün 3D Brille benötigt. Hier ein sehr einfaches Beispiel:
Wie funktioniert die Betrachtungstechnik?
Entspanne Deine Augen und blicke geradeaus, als wenn Du durch das 3D Bild hindurchgucken wolltest. Jetzt sollte sich normalerweise langsam ein räumliches Bild entwickeln. Versuche jetzt mit den Augen das 3D Bild scharf zu stellen ohne sich bewußt auf das Stereogramm zu konzentrieren. Diese Methode des Betrachtens erfordert bei einigen eine gewisse Übung. Also nicht verzagen wenn es nicht auf Anhieb klappt.
Hier noch einige Tipps damit sich der Erfolg schneller einstellt:
* Keine Lichtreflexionen auf dem Bildschirm.
* Kopf geradehalten und die Augen parallel zur Bildschirmhorizontale.
* Abstand zum Bildschirm sollte ca. 80 cm betragen.
* Konzentration auf markante Bildpunkte und durch unscharfes Sehen Bildpunkte benachbarter Perioden zur Überlagerung bringen.
Text aus: http://www.wer-kennt-wen.de/gruppen/weltweit/das-magische-auge-uk6hr5qx/
Übe die Technik mit folgenden Beispielbildern:
Über den Erfinder dieser Technik:
- Kurze Geschichte über die Entstehung der Magic Eye – Bücher Datei
- Welche Eigenschaften machten Tom Baccei so erfolgreich?
Aufbau des Auges:
Das Licht fällt durch die Linse und den durchsichtigen Glaskörper auf die Netzhaut. Dort wird von den Sinneszellen (Zäpfchen und Stäbchen) die Helligkeit und Wellenlänge (Farbe) des Lichts aufgenommen. Über den Sehnerv, in dem schon die ersten signalverarbeitenden Prozesse stattfinden, werden die mittlerweile in Nervensignale umgewandelten Reize zum Gehirn weitergeleitet. Dort findet dann der eigentliche Wahrnehmungsprozess statt.
Wie nimmt das Auge Tiefe wahr?
Lies auf Wikipedia nach wie die Binokulare Raumwahrnehmung (mit 2 Augen) zustande kommt!
3 Tiefenkriterien:
http://de.wikipedia.org/wiki/Raumwahrnehmung
Während die Parallaxe den Winkel zwischen zwei Geraden beschreibt, steht der Begriff Disparation für für den Unterschied zwischen den Bildern, die unsere 2 Augen von einem 3-dimensionalen Objekt liefern.
Wie erzeugen wir ein Bild mit 3-D Effekt – Ein Autostereogramm?
Was ist ein Autosteregramm?
Ein Autostereogramm ist ein 2-D Bild, das unser Gehirn, mit Hilfe seiner Eigenschaft immer nach korrespondierenden Netzhautpunkten zu suchen, dazu bringt ein 3-dimensionales Bild wahrzunehmen.
Dazu muss man allerdings seine Augen trainieren, denn das normale Verhältnis des Winkels der Augen zueinander und dem Fokus muss verändert werden. Dies gelingt nicht allen Menschen gleich gut. Außerdem können etwa 10 % der Menschen überhaupt keine 3-D Bilder sehen.
Die einfachste Form eines Autosterogramms besteht aus horizontal in gleichem Abstand angeordneten gleichen Objekten (Siehe Beispielbild oben).
Wie entsteht der Tiefeneindruck?
Die Tiefe der einzelnen Objektreihen wird durch deren (horizontalen) Abstand zu einander bestimmt (“distance“). Dieser Abstand kann nun variieren. Diese Variation bezeichnen wir als “shift“, also die Abweichung vom Standard-Abstand.
In dieser Konstellation erscheinen die größeren Wolken unten(shift 0) als weiter entfernt, als die kleineren Wolken oben (shift 40). Man kann diese Methode auch ganz einfach mit Schrift erzeugen.
Betrachte die Buchstaben mit dem 3D Blick. Was kannst du beobachten?

Aus Wikipedia: http://en.wikipedia.org/wiki/File:Stereogram_Tut_Clean.png by Fred Hsu
Demo: Hier siehst Du wie aus 2 gleichen Objekten im Bild ein virtuelles Objekt, das hinter der Bildebene liegt entsteht.
Bewege den Mauszeiger auf der Grafik auf und ab, um die Tiefe des virtuellen Objekts verändern.
Aufgabe: Verändere das Programm so, dass die Flugrichtung und Geschwindigkeit der Vögel mit der Maus gesteuert werden können.
Der Processing Sketch als Download: Landschaft
Etwas schwerer: Schreibe selbst ein Programm, das ein Autostereogramm erzeugt.
Programm zur Aufgabe: starte Applet
float picSize=1; //standard Zoom Factor
ArrayList pics; //holds the Pic-Objects
void setup() {
size (1200, 800);
background(9, 139, 232);
smooth();
fill(0, 200, 0);
pics = new ArrayList();
//adds the Pics to the ArrayList (Path,distance,Start-X-Pos,y-Pos, Zoom-Factor
pics.add(new Pic (loadImage("tree1.png"), 210,0, 540,picSize));
pics.add(new Pic (loadImage("cloud3.png"), 210,0, 160,picSize));
pics.add(new Pic (loadImage("tree2.png"), 190,200, 400,3));
pics.add(new Pic (loadImage("birds1.png"), 170,0, 340,picSize));
pics.add(new Pic (loadImage("butterfly.png"), 160,50, 430,picSize));
pics.add(new Pic (loadImage("bird2.png"), 150,0, 590,picSize));
pics.add(new Pic (loadImage("cloud1.png"), 190,0, 100,picSize));
pics.add(new Pic (loadImage("cloud2.png"), 170,0, 80,picSize));
pics.add(new Pic (loadImage("cloud1.png"), 150,0, 10,picSize));
}
void draw() {
//draws the Pics
for (int i=0; i<pics.size(); i++) {
Pic pic = (Pic) pics.get(i);
pic.drawpic();
}
}
class Pic {
//Variables of Pic-Object
PImage pic;
int distance;
int startX;
int yPos;
float picSize;
//The Constructor
Pic (PImage pic, int distance, int startX, int yPos, float picSize) {
this.pic = pic;
this.distance = distance;
this.yPos = yPos;
this.picSize=picSize;
this.startX=startX;
}
//Methods of Pics
void drawpic() {
for (int i=10; i<width; i+=distance) {
image(pic, i-startX, yPos, distance*picSize, distance*7/10*picSize);
}
}
}
Einfache Ebene mit Wallpaper
Ersetzen wir nun die einzelnen Objekte mit einem Flächen füllenden Wallpaper, sehen wir, dass bei entsprechender Betrachtung eine Fläche zu sehen ist, die unter der realen Zeichenebene liegt.
Aus Wikipedia: http://en.wikipedia.org/wiki/File:Stereogram_Tut_Clean.png by Fred Hsu
Wieder gilt: Je weiter die korrespondierenden Pixel auseinander liegen, desto weiter unter der Oberfläche wird der Bildabschnitt wahrgenommen.
3D Bild aus einer Depth Map:
Als Depth Map bezeichnet man ein Graustufen-Bild, welches die Tiefeninformation als Grauwert darstellt (dunkel…hinten, hell…vorne). Damit kann man nun nicht nur ganze Objekte in einer bestimmten Ebene platzieren, sondern jedes Pixel seiner Tiefeninformation entsprechend darstellen (stimmt eigentlich nicht ganz, ist zu diesem Zeitpunkt aber egal).
Aus Wikipedia: http://en.wikipedia.org/wiki/File:Stereogram_Tut_Clean.png by Fred Hsu
Das funktioniert folgendermaßen: Der erste Steifen Wallpaper wird ganz links am Bildschirm gezeichnet. Beim Zeichnen des zweiten Streifens wird dann Zeile für Zeile zu jedem Pixel das entsprechende Pixel aus der Depth Map eingelesen. Ist das Pixel schwarz, wird das Pixel von der gleichen Position im 1.Streifen (rot) in den 2. Streifen (blau) kopiert (siehe Abb. oben!). Ist das Pixel in der Depth Map heller, wird im ersten Streifen (rot) ein Pixel weiter rechts kopiert.

Dadurch wird das Bild Streifen für Streifen immer weiter verzerrt. Schöner wäre es vielleicht, wenn man von der Mitte ausgehend dann links und nach rechts verzerrt.
Wallpapers kann man relativ leicht selber herstellen, oder durch ein zufälliges Pixelmuster ersetzen. Depth Maps können aus 3D Programmen exportiert werden.
Mit einem Gimp Plugin können solche Depth Maps auch selbst hergestellt werden.
Es findet sich aber auch im Internet brauchbares Material. Z.B. hier: Muster, Depth-Maps und einige Stereogramme
- Oder mit einer veränderten Code-Zeile :
Beispiel: starte Applet
</pre>
PImage depthMap, wallpaper;//deklariert die Bild Variablen;
int [] [] grau = new int [1260] [600]; //hier wird ein zweidimensionales Array deklariert
color [] [] wall = new color [140] [600];
color [] [] actual = new color [140] [600];
int shift; //horizontale Verschiebung des Pixels
int x=0; // damit sich die
int xToTake; //Bildpunkt im vorigen Wallpaper Streifen, der gezeichnet wird
void setup() {
size(1260, 600);
smooth();
strokeWeight(5);
//Einlesen der Depth Maps und speichern der Tiefeninfo als Int-Array
depthMap = loadImage("hai_grey.png");//weist der Variablen depthMap die Datei hai_grey.png zu
depthMap.loadPixels(); //sollte man aufrufen, bevor man die einzelen Pixel bearbeitet
for (int gridX = 0; gridX < depthMap.width; gridX++) {
for (int gridY = 0; gridY < depthMap.height; gridY++) {
// überträgt die Farbinfo eines Pixels auf die Variable farbe
color farbe = depthMap.pixels[gridY*depthMap.width+gridX];
// wandelt die Farbinfo in einen Grauwert um
int grauwert =round(red(farbe)*0.222+green(farbe)*0.707+blue(farbe)*0.071);
grau [gridX] [gridY] = grauwert;//speichert den Grauwert in unser Array grau
}
}
//Einlesen der Depth Maps und speichern der Tiefeninfo als Int-Array
wallpaper=loadImage("hai_pattern.png");
wallpaper.loadPixels(); //sollte man aufrufen, bevor man die einzelen Pixel bearbeitet
for (int gridX = 0; gridX < wallpaper.width; gridX++) {
for (int gridY = 0; gridY < wallpaper.height; gridY++) {
// überträgt die Farbinfo eines Pixels auf die Variable farbe
color farbe = wallpaper.pixels[gridY*wallpaper.width+gridX];
// wird die nächste Zeile statt der vorherigen einkommentiert, wird das Wallpaper Bild
// durch ein zufälliges Pixelmuster ersetzt
// color farbe = color ((int) random(0,255),(int) random(0,255),(int) random(0,255));
wall [gridX] [gridY] = farbe;//speichert den Grauwert in unser Array grau
}
}
}
void draw() {
x=(frameCount-1)*wallpaper.width;
if (frameCount<10) {
for (int gridY = 0; gridY < wallpaper.height; gridY+=1) {
for (int gridX = 0; gridX < wallpaper.width; gridX+=1) {
shift=(int) map(grau[gridX+(frameCount-1)*wallpaper.width] [gridY], 0, 255, 0, 60);
xToTake=(wallpaper.width-((wallpaper.width-gridX)-shift))%wallpaper.width;
//einkommentiert kann man hier gut die Funktionsweise ablesen!
//if(gridY==height/2) println("grauwert: "+grau[gridX+(frameCount-1)*wallpaper.width] [gridY]+"grid: "+gridX+"shift: "+shift+" shift:"+xToTake);
stroke(wall [xToTake] [gridY]);//gibt den Grauwert aus unserem Array aus
point(gridX+x, gridY);
wall [gridX] [gridY]=wall [xToTake] [gridY];
}
}
}
else noLoop();
}
<pre>
Processing Sketch als Download: 3D Hai
Array Lists
Im Gegensatz zu Arrays muss bei einer ArrayList am Anfang nicht festgelegt werden, wie viele Elemente sie später enthalten wird. Man kann ihre Größe jederzeit ändern, indem man Elemente hinzufügt oder löscht. Außerdem wird kein spezieller Datentyp angegeben. Erst wenn man Objekte aus der ArrayList ausliest, wird der Datentyp zugewiesen.
Wichtigste Unterschiede zu Arrays:
- Variable Anzahl von Elementen
- Die Methode size() gibt die aktuelle Anzahl der Elemente zurück
- Mit add() werden Elemente ergänzt
- Mit remove() werden gelöscht
- und mit get() ausgelesen.
Unvollständiges Codebeispiel aus dem Projekt Korruption in der Demokratie:
.
.
// Objekt wird erstellt
ArrayList agenten;
void setup() {
.
.
// Die Variable agenten wird mit einer ArrayList befüllt
agenten = new ArrayList();
}
void draw() {
// Nacheinander erden alle Objekte der ArrayList ausgelesen
for (int i=0; i<agenten.size(); i++) {
//Hierbei muss die Art des Objekts in der ArrayList festgelegt werden.
Agent agent =(Agent) agenten.get(i);
agent.render();
}
for (int r=(int) random (8);r>0;r--) {
// Listeneintrag hinzufügen
agenten.add(new Agent(pnts[j].x, pnts[j].y, agenten.size()));
}
}
Korruption in der Demokratie
Korruption scheint in Österreich so allgegenwärtig zu sein, wie der Schimmel im Brotkasten einer Studenten-WG.
Wie “virtueller Schimmel” mit Hilfe von Agenten erzeugt werden kann und wie dieser die Demokratie “befällt”, zeigt folgendes Beispiel:
Mit Hilfe der Geomerative Library wird die Schrift erzeugt und die Koordinaten der Konturpunkte gefunden und in ein Array gespeichert. Mit einem Mausklick werden dann Agenten erzeugt, welche eine begrenzte Lebensdauer aufweisen und sich in zufälliger Richtung fortbewegen. Trifft ein Agent nun auf einen Konturpunkt, dann “stirbt” er. Davor erzeugt er aber noch 0-8 neue Agenten, die sich ihrerseits auf die Suche nach Punkten auf der Schriftkontur machen. So durchdringen sie nach und nach alle Buchstaben bis dahin unsichtbaren Buchstaben und der Schriftzug wird sichtbar.
import geomerative.*;
RShape shp, shp1, shp2;
RPoint[] pnts, pnts1;
String BspText = "Korruption durchdringt die ...";
String BspText1 = "Demokratie!";
String BspText2 = "Mausklick um zu starten!";
ArrayList agenten;
void setup() {
size(1400, 500);
strokeWeight(1);
smooth();
background(0);
translate(width/2, height*2/12);
agenten = new ArrayList();
RG.init(this);
// 3 Shape - Objekte werden erzeugt.
shp = RG.getText(BspText, "Ubuntu-R.ttf", width/60, CENTER);
shp1 = RG.getText(BspText1, "Ubuntu-R.ttf", width*8/70, CENTER);
shp2 = RG.getText(BspText2, "Ubuntu-R.ttf", width/80, CENTER);
// Punkte an der Schriftkontur finden
//Abstand der Punkte
RCommand.setSegmentLength (1);
//Modus
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
// Die Shapes wirde gezeichnet
fill(180, 160);
shp.draw();
// und positioneiert
shp1.translate(0, height*7/12);
shp2.translate(0, height*9/12);
fill(180, 160);
shp2.draw();
// Finden der Konturpunkte
pnts = shp1.getPoints();
// Variation der einzelnen Punkte
for (int i=0;i<pnts.length; i++) {
pnts[i].x+= (int) random(-5, 5);
pnts[i].y+= (int) random(-5, 5);
}
}
void draw() {
translate(width/2, height*1/12);
for (int i=0; i<agenten.size(); i++) {
//jeder Agent wird gezeichnet
Agent agent =(Agent) agenten.get(i);
agent.render();
//jeder Agent muß die Position ändern
agent.move();
// entfernen von Agenten aus der ArrayList
if (agent.lifetime<=0) agenten.remove(i);
// Wenn ein Agent auf einen Konturpunkt trifft, wird ein punkt gezeichnet und
// es werden neue Agenten erzeugt
for (int j=0; j<pnts.length; j++) {
if (!agent.blocked && (int)agent.position.x== (int) pnts[j].x && (int)agent.position.y== (int)pnts[j].y) {
stroke(100, 150, 255, 200);
point(pnts[j].x, pnts[j].y);
fill(100, 150, 255, 60);
for (int r=(int) random (8);r>0;r--) {
agenten.add(new Agent(pnts[j].x, pnts[j].y, agenten.size()));
}
// Damit jeder Punkt nur ein mal "besetzt" wird!
pnts[j].x=-10000;
pnts[j].y=-10000;
agenten.remove(i);
}
}
}
println(agenten.size());
}
// Um den Wucherprozess in Gang zu setzen!
void mousePressed() {
agenten.add(new Agent(mouseX-width/2, mouseY-height*1/12, agenten.size()));
}
class Agent {
// Variablen
PVector position;
PVector direction;
PVector start;
//definiert die Stärke der Richtungsänderung
float spin = 0.40;
int lifetime;
boolean blocked;
float lifetimeinit; // 3. Radius
//der Konstruktor für die Agenten-Klasse
Agent (float theX, float theY, float alifetime) {
start = new PVector (theX, theY);
position=new PVector(theX, theY);
direction = new PVector (10, 10);
direction.x = random (-1, 1);
direction.y = random (-1, 1);
lifetimeinit = alifetime;
if ((int)random(60/lifetimeinit)!=0) {
lifetime=(int) random(400, 600);
}
else {
lifetime=(int) random(0, 50);
}
blocked=true;
}
void render() {
stroke(255, 20);
fill(255);
point(position.x, position.y);
lifetime--;
if (((int)start.x != (int) position.x) &&((int)start.y != (int) position.y)) {
blocked=false;
}
}
void move() {
//die Agenten ändern ihre Richting nicht abrupt, sondern immer nur ein wenig!
direction.x += random (-spin, spin);
direction.y += random (-spin, spin);
//for a constant speed
direction.normalize();
// hier kann man die Geschwindigkeit ändern
//direction.mult(map(mouseX,0,width,5,1));
position.add(direction);
}
}
Geomerative Library
Download: http://www.ricardmarxer.com/geomerative/
Diese Library erweitert die graphischen Möglichkeiten von Processing (2D). Sie bietet u.a. die Möglichkeit Punkte an der Kontur von Vektorgraphiken zu finden. In der .zip Datei befinden sich viele Beispieldateien mit Kommentaren.
Sehr hilfreich fand ich auch dieses Tutorial: http://freeartbureau.org/blog/2011/09/18/geomerative-tutorial/.
Mit RFont kann man eine Schrift (eine .ttf – Datei muss sich im /data Ordner des Sketches befinden!!!) laden. Diese wird dann mit einem String verknüpft und einer RGroup zugeordnet. Mit RGroup.getPoints() bekommt man dann die Konturpunkte. Ein Nachteil dieser Methode ist, dass es in weiterer Folge schwierig ist, auf einzelne Buchstaben zuzugreifen und diese dynamisch zu ändern.
Beispiel: Punkte an der Schriftkontur
import geomerative.*;
RFont font;
String BspText = "A B";
RPoint[] pnts;
void setup() {
size(400, 200);
smooth();
stroke(255);
background(100);
// Hier wird die Library initialisiert und die zu verwendende
// Schrift erzeugt.
// Achtung: Schrift muss zuerst mit Tools --> Create Font
// erzeugt werden.
RG.init(this);
font = new RFont("FreeSans.ttf", 150, RFont.CENTER);
// Punkte an der Schriftkontur finden
//Abstand der Punkte
RCommand.setSegmentLength (5);
//Modus
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
//RCommand.setSegmentStep(5);
//RCommand.setSegmentator(RCommand.UNIFORMSTEP);
//RCommand.setSegmentAngle(random(0,HALF_PI));
//RCommand.setSegmentator(RCommand.ADAPTATIVE);
if (BspText.length() > 0) {
// Erzeugen einer neuer Gruppe von Graphiken
RGroup grp;
//Zuordnen des Texts
grp = font.toGroup(BspText);
//Finden der Punkte
pnts = grp.getPoints();
}
}
void draw() {
translate(width/2, height*7/8);
for (int i=0; i<pnts.length; i++) {
//Zeichnen der Punkte
point(pnts[i].x, pnts[i].y);
}
}
Beispiel: starte Applet
In diesem Beispiel müssen die Positionen einzelner Buchstaben einer Gruppe verändert werden. Deshalb wird hier nicht mit der RGroup – Klasse, sondern mit der RShape – Klasse gearbeitet.
import geomerative.*;
RShape shp, shp1;
RPoint[] pnts, pnts1;
String BspText = "O c c u p y";
String BspText1 = "W A L L ST";
int occupylen, actframe;
void setup() {
size(1480, 800);
strokeWeight(1);
smooth();
fill(120, 50);
background(0);
translate(width/2, height*4/8);
// Hier wird die Library initialisiert und die zu verwendende
// Schrift erzeugt.
// Achtung: Schrift muss zuerst mit Tools --> Create Font
// erzeugt werden.
RG.init(this);
// 2 Shape Objekte werden zugewiesen
shp = RG.getText(BspText, "Ubuntu-R.ttf", width*8/70, CENTER);
shp1 = RG.getText(BspText1, "Ubuntu-R.ttf", width*8/50, CENTER);
// Punkte an der Schriftkontur finden
//Abstand der Punkte
RCommand.setSegmentLength (height/400);
//Modus
RCommand.setSegmentator(RCommand.UNIFORMLENGTH);
// Shape wirde gezeichnet
shp.draw();
// für Abbruch nach einem Durchlauf
pnts = shp.getPoints();
occupylen=pnts.length;
// damit der Text Wall ST wie gewünscht dargestellt wird
shp1.translate(width/20, height*3/8);
//mit children[i] kann man in Shapes immer auf die Buchstaben zugreifen
shp1.children[4].translate(width/5, height/40);
shp1.children[4].scale(0.6, 0.6);
shp1.children[5].translate(width*2/9, height/40);
shp1.children[5].scale(0.6, 0.6);
shp1.draw();
// Hier werden die beiden Shapes zu einer zusammengefügt
shp.addChild(shp1);
pnts = shp.getPoints();
}
void draw() {
translate(width/2, height*4/8);
// damit gleich mit dem Zeichnen begonnen wird
actframe=frameCount+occupylen;
//für Abbruch nach einem Durchlauf der Konturpunkte
if (frameCount<pnts.length) {
for (int j=0; j<pnts.length; j++) {
//Zeichnen der Punkte
if (dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y)<random(0, height)
&& dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y)>height/30
&&(int)random(height/20)==3) {
stroke(map(dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y), height/160, height*3/4, 0, 255),
map(pnts[actframe%pnts.length].x-pnts[j].x, -height/2, height/2, 255, 0),
map(dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y), height/160, height*3/4, 255, 0),
map(dist(pnts[actframe%pnts.length].x, pnts[actframe%pnts.length].y, pnts[j].x, pnts[j].y), height/160, height*3/4, 10, 0));
line(pnts[actframe%pnts.length].x+random(-height/160, height/160), pnts[actframe%pnts.length].y+random(-height/160, height/160), pnts[j].x+random(-height/160, height/160), pnts[j].y+random(-height/160, height/160));
}
}
}
}
Agenten
Agenten bewegen sich entsprechend ihrer Programmierung. Hier werden sie als Objekte realisiert und bewegen sich zufällig.
Sie ändern aber nicht abrupt ihre Richtung, da wir für die Position der Agenten Vektoren verwenden, denen wir in jedem Schritt eine zufällige Abweichung addieren.
In der einfachsten Ausführung des Programms bewegen sie sich aus der Mitte heraus und prallen dann von den Rändern des Animationsfensters zurück. Dabei ist ihre Bewegung nicht gerade, sondern ändern ständig ihre Richtung.
Beispiel: einfache Agenten starte Applet
Agent [] agenten;
void setup() {
size(500, 500);
smooth();
stroke(255);
// hier wird ein Array für 10 Agenten erzeugt
agenten= new Agent[10];
// die Agenten selber werden erzeugt
for (int i=0; i<agenten.length; i++) {
agenten[i]=new Agent(width/2, height/2, 5);
}
}
void draw() {
// für den Spur, die die Agenten hinterlassen
fill(0, 2);
rect(0, 0, width, height);
//alle Agenten müssen die Position ändern
for (int i=0; i<agenten.length; i++) {
//jeder Agent wird gezeichnet
agenten[i].render();
//jeder Agent muß die Position ändern
agenten[i].move();
}
}
class Agent {
// Variablen
PVector position;
PVector direction;
//definiert die Stärke der Richtungsänderung
float spin = 0.20;
float radius; // 3. Radius
//der Konstruktor für die Agenten-Klasse
Agent (float theX, float theY, float aradius) {
position = new PVector (theX, theY);
direction = new PVector (10, 10);
direction.x = random (-1, 1);
direction.y = random (-1, 1);
radius = aradius;
}
//eine Methode
void render() {
//einkommentiert ergibt sich hieraus ein schönes Fächer-Muster
//line(position.x, position.y, width/2, height/2);
ellipse(position.x, position.y,5,5);
}
//Methode
void move() {
//die Agenten ändern ihre Richting nicht abrupt, sondern immer nur ein wenig!
direction.x += random (-spin, spin);
direction.y += random (-spin, spin);
//for a constant speed
direction.normalize();
// hier kann man die Geschwindigkeit ändern
direction.mult(1);
position.add(direction);
//damit die Agenten das Bild nicht verlassen
if (position.x < radius || position.x > (width-radius)) {
direction.x *= -1;
}
if (position.y < radius || position.y > (height-radius)) {
direction.y *= -1;
}
}
}
Beispiel: einfache Agenten2 starte Applet
Hier wurde der Code um Farbe und die Linien von jedem Agenten zum Mittelpunkt erweitert.
in setup();
colorMode(HSB, 360, 100, 100);
im Agenten selbst
int col;
...
col=(int)random(0, 360);
...
void render() {
if (col<360) {
col+=2;
} else {
col=0;
}
//reuncomment for random color
stroke(col,100,100);
Den kompletten Source-Code findest Du beim Applet!
Beispiel: einfache Agenten3 starte Applet
Hier eine Variante, bei der die Agenten miteinander durch gerade Linien verbunden werden.
Änderung in draw();
if (i==0) {
line(agenten[i].position.x,
agenten[i].position.y,
agenten[agenten.length-1].position.x,
agenten[agenten.length-1].position.y);
}
else {
line(agenten[i].position.x, agenten[i].position.y,
agenten[i-1].position.x, agenten[i-1].position.y);
}
Den kompletten Source-Code findest Du beim Applet!
Beispiel: einfache Agenten4 starte Applet
In diesem Beispiel wird die Bewegungsfreiheit der Agenten durch die aktuelle Mausposition in x- und y- Richtung eingeschränkt. Ist der Mauszeiger in der linken oberen Ecke, versammeln sich alle Agenten im Mittelpunkt, ist er in der rechten unteren Ecke, können sie sich (fast) frei bewegen.
Dafür platzieren wir folgende Code-Zeile in setup() und in draw(). Sie bewirkt, dass der Ursprung des Koordinatensystems in die Mitte des Rahmens verschoben wird.
translate(width/2,height/2);
Da wir nun vom Mittelpunkt des Rahmens als Nullpunkt ausgehen, können wir mit Hilfe der map()- Funktion die aktuelle Position (x und y) von -width/2 bis +width/2 um die aktuelle Mausposition korrigieren. Das machen wir aus optischen Gründen nun für 2 Ellipsen.
ellipse(
map(position.x,-width/2,width/2,-mouseX/2,mouseX/2),
map(position.y,-height/2,height/2,-mouseY/2,mouseY/2)
,4,4);
fill(100,50,255);
ellipse(
map(position.x,-width/2,width/2,-mouseX/2,mouseX/2),
map(position.y,-height/2,height/2,-mouseY/2,mouseY/2)
,3,3);
Weiters wird nun die Grenze, an der die Agenten zurückprallen vom Fensterrahmen auf einen Kreis geändert:
if (dist(position.x,position.y,0,0)>width/2) {
direction.x *= -1;
direction.y *= -1;
}
Den kompletten Source-Code findest Du beim Applet!
Beispiel: einfache Agenten5 starte Applet
In diesem Beispiel arbeiten wir wieder im Color-Mode HSB.
colorMode(HSB, 360, 100, 100);
Außerdem erhält hier jeder Agent einen Index und ein Array mit den Indizes von 3 seiner nächsten Nachbarn. Damit kann man über 4 Punkte eine Kurve (curve() ) zeichnen und die Agenten damit dann Verbinden.
Variablendefinition in der Agenten-Klasse:
int [] neighbours = new int [4]; int index;
Im Konstruktor des Agenten:
Start-Farbpunkt für jeden Agenten, damit alle gemeinsam das gesamte Spektrum abdecken.
col=index*(360/agenten.length);
Jeder Agent muss, wenn er erzeugt wird seine Nachbarn finden. Das macht er hiermit:
for (int i=0;i<4;i++) {
if (index+i>agenten.length-1) {
neighbours[i]= index+i-agenten.length;
}else {
neighbours[i]= index+i;
}
In der Methode render des Agenten wird nun nichts mehr gezeichnet. Wir nutzen sie aber für die Veränderung der Farbwerte:
if (col<360) {
col+=2;
} else {
col=0;
}
Wir zeichnen die Kurve nun in draw():
curve(
map(agenten[agenten[i].neighbours[0]].position.x,-width/2,width/2,-mouseX/2,mouseX/2),
map(agenten[agenten[i].neighbours[0]].position.y,-height/2,height/2,-mouseY/2,mouseY/2),
map(agenten[agenten[i].neighbours[1]].position.x,-width/2,width/2,-mouseX/2,mouseX/2),
map(agenten[agenten[i].neighbours[1]].position.y,-height/2,height/2,-mouseY/2,mouseY/2),
map(agenten[agenten[i].neighbours[2]].position.x,-width/2,width/2,-mouseX/2,mouseX/2),
map(agenten[agenten[i].neighbours[2]].position.y,-height/2,height/2,-mouseY/2,mouseY/2),
map(agenten[agenten[i].neighbours[3]].position.x,-width/2,width/2,-mouseX/2,mouseX/2),
map(agenten[agenten[i].neighbours[3]].position.y,-height/2,height/2,-mouseY/2,mouseY/2));
Den kompletten Source-Code findest Du beim Applet!
Beispiel: einfache Agenten6 starte Applet
Hier bekommt jeder Agent einen Startpunkt zugeordnet. In diesem Fall bilden alle Agenten gemeinsam einen Kreis. Damit jeder Agent wieder in seinen Ursprung zurückkehren kann, muss er sich seine Startposition merken. Das tut er in der PVector Variablen start.
PVector start;
Auch der Konstruktor der Agenten wird angepasst. Die Koordinaten, die beim Erzeugen des Agenten übertragen werden, werden in der Variable start gespeichert und die Variable position wird mit den Koordinaten 0,0 gefüllt.
//der Konstruktor für die Agenten-Klasse
Agent (float theX, float theY, float aradius) {
start = new PVector (theX, theY);
position=new PVector(0,0);
direction = new PVector (10, 10);
direction.x = random (-1, 1);
direction.y = random (-1, 1);
radius = aradius;
}
Beim Zeichnen der Agenten wird nun das Koordinatensystem auf die jeweilige Startposition verschoben. Und von dort ausgehend seine Position gezeichnet.
pushMatrix();
translate(start.x,start.y);
....
popMatrix();
Mit den folgenden Zeilen legen wir die Startpositionen der Agenten fest. Sie ordnen sich kreisförmig um dem Mittelpunkt an.
for (int i=0; i<agenten.length; i++) {
float x,y;
x= width/2+(cos(map(i,0,agenten.length,0,TWO_PI))*width/4);
y= height/2+(sin(map(i,0,agenten.length,0,TWO_PI))*height/4);
agenten[i]=new Agent(x,y, 5);
}
Den kompletten Source-Code findest Du beim Applet!
Easing
Um eine Bewegung nicht abrupt ablaufen zu lassen gibt es eine einfache Technik – das “Easing”. Wird ein Objekt nicht sofort an seinen Endpunkt bewegt, sondern nur einen bestimmten Prozentwert in dessen Richtung. Das führt zu einer harmonischen runden Bewegung.
Konkret wird der Unterschied in der x-Positionen von Objekt und Mauszeiger errechnet (dx). Wenn dieser Wert größer ist als 1, dann wird ein neuer Wert für die Objektposition berechnet (x+= dx*easing;).
Beispiel: eindimensionales Easing starte Applet

float x = 0.0; //aktuelle x-Position des Objekts
float easing = 0.05; //Stärke der Dämpfung
void setup() {
smooth();
size(500, 100);
}
void draw () {
background(0);
float targetX = mouseX;
// Unterschied der x-Position von Objekt und Mauszeiger
float dx = targetX -x;
//abs gibt den ansoluten Wert einer Zahl aus
if (abs(dx) >1.0) {
x+= dx*easing;
}
ellipse(mouseX, 30, 40, 40);
ellipse(x, 30, 40, 40);
}
Aufgabe: Erweitere das obige Programm so, dass auch eine Bewegung der Objekte in Y-Richtung möglich ist. starte Applet
Lesen und schreiben von Textdateien
Natürlich kann man im Processing auch mit Textdateien arbeiten. Zum Einlesen einer Textdatei in ein String Array wird der Befehl loadStrings() verwendet. Um Daten in eine Textdatei zu schreiben verwendet man saveStrings(). Auch hier muss man mit einem String Array arbeiten.
Sowohl beim Lesen, als auch beim Schreiben ist der Standard-Ordner der Ordner /data im Sketchbook-Verzeichnis. D.h. dort ist eine Datei zu platzieren, wenn man sie einlesen möchte.
Beispiel: Einlesen einer Textdatei
Damit das Beispiel funktioniert, muss eine Textdatei mit dem Namen “Textdatei.txt” im /data Ordner des Projekts erstellt werden!!!
String[] zeilen; //String-Array
void setup() {
// hier wird der Inhalt der Textdatei in das String-Array
// zeilen eingelesen.
// jede Zeile als eignener String
zeilen = loadStrings("Textdatei.txt");
noLoop(); // damit draw() nur ein mal ausgeführt wird
}
void draw() {
println(zeilen);
}
Beispiel: Textdatei schreiben
Funktioniert auch nur mit einer Datei “Textdatei.txt” im /data Ordner des Projekts.
String[] zeilen; //String-Array
void setup() {
// hier wird der Inhalt der Textdatei in das String-Array
// zeilen eingelesen.
// jede Zeile als eignener String
zeilen = loadStrings("Textdatei.txt");
noLoop(); // damit draw() nur ein mal ausgeführt wird
}
void draw() {
println(zeilen);
// schreibt den Inhalt von "zeilen" in eine Datei mit
// dem Namen "neueDatei.txt"
saveStrings("neueDatei.txt", zeilen);
}
Libraries
Libraties sind Programme, genauer genommen Programmpakete, die Programmierer anderen Processing- Nutzern zur Verfügung stellen. Sie erweitern die ohnehin schon großartigen Möglichkeiten von Processing. Libraries gibt es für alle möglichen Einsatzbereiche. Einige sind schon in der Processing-Installation enthalten. Man nennt diese die Core-Libraries:
- Video
- Network
- DXF Export
- Java Skript
- Minim Audio
- OpenGL
- OpenGL2
- PDF Export
- Serial I/O
Darüber hinaus gibt es noch eine ganze Menge sog. User-Libraries, die frei genutzt werden können, in Punkto Stabilität aber nicht immer mit den Core- Libraries mithalten können.
Eine gute Übersicht findet man hier auf der offiziellen Processing-Seite.
Um eine Library in Processing zu verwenden muss sie immer separat mit der import- Anweisung in den Sketch eingebunden werden. Außerdem gibt in der Processing IDE auch die Möglichkeit das im Menü unter Sketch –> import Library –> zu erledigen.
import processing.pdf.*;
Processing mit Eclipse entwickeln
Zuerst sollte man dieses Tutorial durcharbeiten: http://erik-bartmann.de/programmierung/processing/processing-mit-eclipse.html
Mit der folgenden Funktion kann man seinen Code auch als Java Application laufen lassen.
public static void main(String args[]) {
PApplet.main(new String[] { "--present", "NAMEDERKLASSE" });
}
Da Processing von PApplet abgeleitet wird, muss jede Klasse neue ” extends PApplet” enthalten, damit alle Processing – Objekte auch innerhalb der neuen Klasse funktionieren.
Will man aber separate Klassen erzeugen, die von einer Processing Anwendung aus benutzbar sind, haben wir das Problem, dass sich die Processing eigenen Anweisungen, wie z.B. rect() u.a. nicht verwenden lassen. Dieses Problem kann man lösen, in dem man der Klasse sagt, dass sie auf dem PApplet zeichnen soll.
Details darüber findet man hier: http://www.learningprocessing.com/tutorials/processing-in-eclipse/
Eine andere sehr gute Anleitung gibt es hier: http://creativecoding.org/en/beyond/p5/eclipse_as_editor
Processing Sketch in eine vollständige Android-App einbinden
In diesem Tutorial wird gezeigt, wie man einen Procesing-Sketch in Eclipse zu einer vollständigen App mit Menü-Activity und einem Button ausbaut, der die Processing-Acitvity öffnet. Bei der Erstellung ist dabei die Processing-IDE auch weiterhin sinnvoll. Die Processing-Sketches werden weiterhin darin entwickelt und dann, wenn sie fertig sind exportiert. Dabei erstellt Processing im Sketchbook-Folder einen Ordner mit dem Namen Android. Darin sind im src Ordner die *.java – Dateien zu finden, die man dann in das Eclipse-Projekt importieren kann.
Vorbereitungen: Installation der Android SDK des ADT Plugins für Eclipse lt. Anleitung.
1. Wir erstellen mit Hilfe des Assistenten des ADT-Plugins ein neues Android Programm –> New Android Programm . Alle Felder ausfüllen und Create Projekt from existing Sample –> Skeleton App wählen. Das erstellt ein leeres Android Projekt mit einer Activity und allen Dateien, die sonst noch notwendig sind.
Die Ordner-Struktur sieht dann folgendermaßen aus:
- src: enthält die Java-Quelltexte
- gen: hier finden sich automatisch generierte Klassen, wie die R-Klasse
- res: hier kommen die Resourcen hin
- asset: weiterer Ordner für Ressourcen
- im root liegt dann noch die Android Manifest.xml

2. Wir kopieren die Processing .java – Exportdatei aus dem Sketchbook /src/.. -Ordner in den Eclipse- /src-Ordner. Danach werden wir von Eclipse auf einige Fehler hingewiesen. Das liegt daran, dass Eclipse die Processing- Library nicht automatisch einbindet.
3. Die Library processing-core.jar einbinden. Re. Maustaste auf Projektname –> Build Path –> Add External Archieves. Dann die im Processing-Exportordner unter Android/libs processing-core.jar.
4. Die neue Activity muss in die AndroidManifest.xml eingetragen werden. Und zwar innerhalb des Application-Tags
<application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name="Proctest" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Procactivity" /> </application>
5. Zu guter Letzt müssen wir noch die Acitvity starten. Das machen wir, wie in Android üblich per Intent. Wir werden dafür einen Button in der Start-Activity erstellen und von dort aus die Processing-Activity starten. Dafür ergänzen wir die main.xml in /res/layout um die folgenden Zeilen:
<Button android:id="@+id/start" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/start" android:onClick="onClickStarten" />
Die erste Zeile android:id=”@+id/start” bedingt, dass wir auch in der Datei stings.xml in /res/values die Zeile: <string name=”start”>Start</string> innerhalb des resources-Tags ergänzen müssen.
Dannach erweitern wir die, vom Android-Assistenten erstellte *.java -Datei um diese Zeilen innerhalb der onCreate-Funktion:
final Button buttonstart = (Button) findViewById(R.id.start); buttonstart.setOnClickListener(mStartenListener);
Dadurch wird unserem Button ein Listener hinzugefügt. In den folgenden Zeilen wird dann noch ein der Intent aufgerufen, der die Processing -Acitvity startet. Dieser Code muss nach der onCreate-Funktion aufgerufen werden.
private OnClickListener mStartenListener =
new OnClickListener() {
public void onClick(View v) {
onClickStarten(v);
}
};
public void onClickStarten(final View sfNormal) {
final Intent i = new Intent(this, Procactivity.class);
startActivity(i);
}
Damit sind wir fertig und können nun eine Processing-Acitvity inkl. Optionsmenü von einer beliebigen Activity aus starten.
Hier noch der gesamte Quellcode zum Download: proctest.zip
Processing Apps für Android – Optionsmenü
Nachdem Optionsmenüs für Android-Apps enorm wichtig sind, hier ein Code-Beispiel, wie man eines erstellen kann.
Als Beispiel habe ich die Funktion Vieleck aus dem basic-Artikel Winkel und Wellen gewählt, in dem man dann die Eckenzahl mit Hilfe eines Optionsmenüs wählen kann. Die Funktion ist, wie in den anderen Artikeln auch, einfach gewählt, damit der Fokus auf den eigentlichen Inhalten bleibt. In diesem Fall das Optionsmenü.
- Processing Sketches laufen in Android als Activity, d.h. die Processing-Klasse PApplet erweitert die Klasse Acitvity. In so fern stehen auch alle Methoden der Acitvity-Klasse zur Verfügung.
- menu.add(0, DREI_ID, Menu.NONE, “3″); erzeugt einen Menüeintrag, wobei 0 die goupId, DREI_ID die itemId, Menu.NONE die Reihenfolge und “3″ den Titel des Menüpunkts definieren. Menu.NONE wird angegeben, wenn die Reihenfolge der Einträge egal ist
//Imports für die Anzeige des Menüs
import android.view.Menu;
import android.view.MenuItem;
int ecken = 5; //Anzahl der Ecken beim Start
int winkel;
float x1, y1, x2, y2;
//IDs der Menüeinträge werden zugewiesen
public static final int DREI_ID = Menu.FIRST;
public static final int VIER_ID =Menu.FIRST+2;
public static final int FUENF_ID = Menu.FIRST+3;
public static final int SECHS_ID = Menu.FIRST+4;
public static final int ACHT_ID = Menu.FIRST+5;
void setup() {
smooth();
}
void draw() {
fill(255, 20);
rect(0, 0, width, height);
vieleck(ecken, mouseX, mouseY, 10, 10);
}
void vieleck (int seiten, int x, int y, int radiusX, int radiusY) {
winkel = (int) 360/seiten;
for (int grade=0; grade<360; grade+=winkel) {
x1 =sin(radians(grade))*radiusX+(x);
y1 =cos(radians(grade))*radiusY+(y);
x2 =sin(radians(grade+winkel))*radiusX+(x);
y2 =cos(radians(grade+winkel))*radiusY+(y);
line(x1, y1, x2, y2);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// hier werden die einzelnen Menüeinträge erzeugt
menu.add(0, DREI_ID, Menu.NONE, "3");
menu.add(1, VIER_ID, Menu.NONE, "4");
menu.add(2, FUENF_ID, Menu.NONE, "5");
menu.add(3, SECHS_ID, Menu.NONE, "6");
menu.add(4, ACHT_ID, Menu.NONE, "8");
return super.onCreateOptionsMenu(menu);
}
//wird aufgerufen, wenn ein Menüpunkt ausgewählt worden ist
@Override
public boolean onOptionsItemSelected(MenuItem item) {
//die Auswahl wird über die ItemId des Menüpunktes überprüft
switch (item.getItemId()) {
case DREI_ID:
ecken=3;
break;
case VIER_ID:
ecken=4;
break;
case FUENF_ID:
ecken=5;
break;
case SECHS_ID:
ecken=6;
break;
case ACHT_ID:
ecken=8;
break;
}
return super.onOptionsItemSelected(item);
}
Das wars!
Bedingungen mit Switch – Case
Mit switch / case kann man wie mit if / else Bedingungen programmieren. Während bei Verzweigungen mit weniger als 3 Möglichkeiten if / else die richtige Wahl ist, verwendet man bei Verzweigungen mit vielem Möglichkeiten besser switch / case.
switch (Variable) {
case Wert der Variable:
Anweisung1;
break; //notwendig, sonst würde auch die Anweisung 2 ausgeführt!
case Wert der Variable:
Anweisung2;
break;
case Wert der Variable:
Anweisung3;
break;
default:
Anweisung4;
}
Interessant ist dabei, dass die Ausführung der Anweisungen separat mit dem Befehl break abgebrochen werden muss. Andernfalls werden alle weiteren Anweisungen ausgeführt, selbst wenn die Bedingungen dafür nicht erfüllt sind.
Die default: Anweisung kann optional verwendet werden, falls man eine Anweisung ausführen will, wenn alle anderen Bedingungen nicht erfüllt werden. Entspricht somit der else – Funktion.
Beispiel Mausposition 1: starte Applet
void setup() {
size(500, 200);
smooth();
}
void draw() {
cursor(ARROW); //macht den Auszeiger in Form eines Pfeils sichtbar
background(120);
switch (mouseX/100) {
case 0:
rect(0, 0, 100, 200);
break;
case 1:
rect(100, 0, 100, 200);
break;
case 2:
rect(200, 0, 100, 200);
break;
case 3:
rect(300, 0, 100, 200);
break;
case 4:
rect(400, 0, 100, 200);
break;
}
}
Beispiel Mausposition 2: starte Applet
void setup() {
size(500, 200);
smooth();
}
void draw() {
cursor(ARROW);//macht den Auszeiger in Form eines Pfeils sichtbar
background(120);
switch (mouseX/100) {
case 0:
rect(0, 0, 100, 200);
case 1:
rect(100, 0, 100, 200);
case 2:
rect(200, 0, 100, 200);
case 3:
rect(300, 0, 100, 200);
case 4:
rect(400, 0, 100, 200);
} case
}
Um die Elementfolge von links nach rechts wachsen zu lassen, müsste man nur die case – Anweisungen in umgekehrter Reihenfolge platzieren.
Android Grundlagen
Android ist ein auf Linux basierendes Betriebssystem, das für die Anwendung auf mobilen Geräten optimiert ist. Da sich der Linux-Kernel relativ leicht an die Anforderungen verschiedener Hardware-Plattformen anpassen lässt, ist das anfangs nur für ARM -Prozessoren verfügbare Android heute auf vielen verschiedenen Plattformen anzutreffen. Es kommt u.a. in Fernsehern, Infotainment-Systemen in Autos, Spielekonsolen und vielem mehr zum Einsatz.

Wie in der Abbildung (Quelle: Wikipedia) oben zu sehen ist, stellt der Linux- Kernel die Basis von Android dar.
Die Libraries (oder Bibliotheken, geschrieben in C/C++) stellen die vom Anwendungsrahmen (Application Framework) benötigte Funktionalität zur Verfügung.
Daneben steht die Android-Laufzeit (Runtime), deren Kernkomponente die Dalvik-VM (DVM) ist. Sie sorgt dafür, dass pro Anwendung ein eigener Systemprozess gestartet wird. Dieser enthält eine eigene Dalvic-VM und darin die Anwendung selbst. Dies hat den Vorteil, dass nicht das ganze Betriebssystem “hängt”, wenn eine Anwendung abstürzt. Was die Programmierung angeht, kann man Android vollständig in Java programmieren, man muss allerdings den Java-Code noch extra durch den Dx-Converter verarbeiten.In Summe sieht das das dann so aus:
(Java) IDE (Processing) –> Java-Compiler –> *.class-Datei –> Dx-Konverter –> *.dex
Der Anwendungs-Rahmen (Application-Framework) stellt jetzt die Basis für unsere Entwicklungsarbeit. Sie enthält viele Systemklassen, auf die wir aus unseren Processing-Anwendungen heraus zugreifen können. Processing-Programme laufen als Activity. Diese Activitys unterliegen einem Lebenszyklus, d.h. bestimmte Ereignisse im Lebenszyklus können als Trigger in unseren Programmen verwendet werden.
Alle Anwendungen (Applications) greifen auf den darunter liegenden Anwendungs-Rahmen zu.
Weiterführende Info: Android-Laufzeitumgebung.pdf
Processing Apps für Android X – Erstellen einer .apk-Datei aus .pde
Nun, da wir das Projekt Pure Pong! soweit fertig gestellt haben, versuchen wir jetzt es zu veröffentlichen. Da es in Processing derzeit keine Möglichkeit gibt, das per Knopfdruck zu erledigen, müssen wir hier selber Hand anlegen. Dafür gibt es auch eine Step by Step Anleitung von Eric Pavey, die aber bei mir auf Ubuntu 10.10 nicht genau funktionierte. Sehr hilfreich war bei mir dann die Anleitung von Google selbst.
Deshalb hier noch meine Erfahrungen Schritt für Schritt:
- Processing Sketch Exportieren
Dabei erhalten wir eine .pde -Datei im Projektverzeichnis im Sketchbook. - Einen geheimen Schlüssel erstellen
In Ubuntu 10.10 sind alle notwendigen tools schon installiert. sudo keytool -genkey -v -keystore NAMEDESPROGRAMMS-release-key.keystore -alias DEINNAMEHIER -keyalg RSA -keysize 2048 -validity 10000
Bevor der Schlüssel erstellt wird, muss man noch einige Fragen beantworten, die bei der Erstellung eine Rolle spielen. Wenn das Programm keytool durchgelaufen ist, wird im aktuellen Verzeichnis eine Datei NAMEDESPROGRAMMS-release-key.keystore erstellt.Vorsicht: der Schlüssel muss unbeding gesichert werden, denn für etwaige Updates muss das neue Programm wieder mit dem originalen Schlüssel signiert werden.
- Das Programm compilieren
Unter Ubuntu brauchte ich dafür nur in das Sketchbook-Verzeichnis von Pure Pong! und dann in den Ordner android. Mit dem Befehl
ant release wird dann ein neues Unterverzeichnis /bin erzeugt in der sich ein Verzeichnis classes und folgende Dateien befinden classes.dex PurePong_.ap_ PurePong_-unsigned.apk. - Das Programm signierenROGRAMMS-release-key.keystore -alias DEINNAMEHIER -keyalg RSA -keysize 2048 -validity 10000
Die PurePong_-unsigned.apk ist wie der Name schon sagt, nicht signiert. Das erledigen wir mit dem Programm jarsigner. Ein vollständiger Komandozeilen-Befehlt sieht dann etwa so aus:jarsigner -verbose -keystore
NAMEDESPROGRAMMS-release-key.keystore /VOLLSTÄNDIGER_PFAD+NAME-unsigned.apk DEINNAMEHIERMan muss dann noch das vorher bei der Schlüsselerstellung gewählte Passwort eingeben und schon laufen einige Zeilen Code mit adding und signing auf der Konsole durch und schon ist die Sache erledigt! Eine Erfolgsmeldung oder ähnliches gibt es bei mir nicht.
- Erstellen der fertigen .apk -Datei
Hier kommt das Programm zipalign zum Einsatz. Es befindet sich im Installationsverzeichnis der Android SDK im Unterordner tools. Von dort aus gestartet macht es aus unserer Datei eine fertige .apk- Datei./PFAD ZU DEINEM HOME VERZEICHNIS/android-sdk-linux_x86/tools/zipalign -v 4 /VOLLSTÄNDIGER_PFAD+NAME-unsigned.apk GEWÜNSCHTER_NAME.apk
Wenn das Programm fertig ist hat man ein fertige .apk -Datei zur Verfügung
Zu guter Letzt meine purepong.apk zum Download (mit RechtsClick – Datei speichern unter)!!!
Veröffentlichen im Market – Vorbereitungen
Die oben beschriebene Art der Veröffentlichung ist sozusagen die “basic” Variante. Will man seine App im Market platzieren, sollte man noch einige Schritte ergänzen. Eine englischsprachige Anleitung gibt es direkt von Google.
3 Dinge sind hier wichtig:
- Die App muss mit einem Sicherheitsschlüssel signiert werden, der bis 2033 gültig ist. Das habe wir mit dem Schlüssel oben erfüllt, da wir das keytool mit einer -validity 10000 aufgerufen haben. Diese Einstellung erzeugt einen Schlüssel mit einer Gültigkeit von 10000 Tagen, also ~27 Jahren.

- Die Datei AndroidManifest.XML muss unbedingt einen Eintrag
android:versionCodeundandroid:versionNameenthalten. Diese werden von Processing automatisch beim Exportieren erstellt und man muss sich nicht darum kümmern, wenn man das nicht ausdrücklich will. - Weiters müssen im Application-Tag noch die Attribute
android:label undandroid:icongesetzt sein, was Processing ebenfalls schon automatisch erledigt.
Ich habe dann noch ein eigenes Icon erstellt. Hier muss man allerdings gleich 3 mit verschiedenen Auflösungen generieren, damit es möglichst vielen Geräten funktioniert. Alle sind im Sketchbook-Ordner des Projekts unter /android/res/drawable zu finden. Meine Empfehlung: Das größte Icon im Ordner /drawable-hdpi mit Gimp öffner und bearbeiten. Ist es so, wie man es sich vorstellt, kann man es dann nach unten skalieren und auch in die anderen Ordner mit den entsprechenden Auflösungen speichern.
Der Android Market bietet die Möglichkeit das Lizensierungs-Modell von Google zu verwenden. Ich werde das in diesem Fall nicht tun. Infos darüber gibt es hier: Lizensierung für den Android Market.
Veröffentlichen im Market
Um Apps auf dem Market veröffentlichen zu können, braucht man, wie nicht anders zu erwarten eine Konto bei Google. Damit kann man sich dann auf dieser Seite für den Market anmelden: http://market.android.com/publish
- Hier ist dann erst einmal ein Formular auszufüllen, das unter anderem auch nach der Telefonnummer fragt! Typisch Google.
- Weiters sind 25 USD zu bezahlen (per Kreditkarte)
- Danach muss man nur noch alles akzeptieren, was Google verlangt.
Geschafft. Nun kann man seine App in den Market hochladen.
Interessanterweise hatte ich in weiterer Folge die größten Probleme damit, einen Screenshot von meinem Game zu erzeugen. All gratis im Market verfügbaren Programme versagten auf meinem Phone. Ich habe dann den Inhalt einfach abfotografiert. Ist zwar nicht so schön, aber es funkt.
Geschafft: Game ist im Market unter: https://market.android.com/search?q=PurePong!
Probiert es aus, ich freue mich über jedes Feedback!
Android App Pure Pong!
Mein erstes fertiges Projekt ist wieder mal Pong, diesmal Pure Pong!. Zum allgemeinen Verständnis siehe: Projekt Ping Pong. Die Version für Android ist eine schon recht deutlich veränderte.
- Ich habe im Gegensatz zu Ping Pong für die Steuerung des Balls und der Spielerbalken jetzt PVector-Objekte verwendet.
- Das Spiel nutzt Multitouch und ist ein reines Multiplayer-Game.
- Durch das Bewegen des Balkens während der Ballberührung kann man den Balls seitlich beschleunigen.
- Die Geschwindigkeit des Balls hängt von der Größe des Pointers während der Ballberührung ab. Er kann dadurch beschleunigt, oder gebremst werden.
- Es wurden Vibrations-Effekte für das haptisches Feedback eingebaut.
Hier meine purepong.apk zum Download (mit RechtsClick – Datei speichern unter)!!!
Geschafft: Game ist im Market unter: https://market.android.com/search?q=PurePong!
Hier der Quellcode mit Kommentaren:
// Imports
import android.content.Context;
import android.app.Notification;
import android.app.NotificationManager;
// Setup vibration globals:
NotificationManager gNotificationManager;
Notification gNotification;
long[] gVibrate = {
0, 100
};
// Globale Variablen
int size;
int s2;
int balls=5;
int balls1=5;
int serve=0;
int frame=0;
PVector ballPos;
PVector ballDir;
PVector playerPos;
PVector pplayerPos;
PVector playerDir;
PVector playerPos1;
PVector pplayerPos1;
PVector playerDir1;
float ballspeed;
float ballspeed1;
float maxSpeed;
float minSpeed;
float h24;
float h48;
float hh6;
float h6;
float h4;
float w8;
float w6;
int blockdir=0;
color bg = color(0, 150, 255);
color pl = color(255, 170, 0);
PFont font;
void setup()
{
size(screenWidth, screenHeight, A2D);
frameRate(30);
smooth();
//Damit sich die Ausrichtung des Displays nicht während des Spiels ändert
orientation(PORTRAIT);
maxSpeed=screenHeight/40;
minSpeed=screenHeight/200;
size=height/24;
//hier die am öftesten vorkommenden Berechnungen
s2=size/2;
h24=screenHeight/24;
h48=screenHeight/48;
hh6=screenHeight-screenHeight/6;
h6=screenHeight/6;
h4=screenHeight/4;
w8=screenWidth/8;
w6=screenWidth/6;
// die PVector-Objekte werden erzeugt
playerPos = new PVector(width/2, hh6);
pplayerPos = new PVector(playerPos.x, playerPos.y);
playerPos1 = new PVector(width/2, h6);
pplayerPos1 = new PVector(playerPos1.x, playerPos1.y);
ballPos = new PVector(width/2, height-h4);
ballDir = new PVector (0, -4);
textAlign(CENTER);
rectMode(CENTER);
font = createFont("Arial", 12);
stroke(180);
// Create our Notification Manager:
gNotificationManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
// Create our Notification that will do the vibration:
gNotification = new Notification();
// Set the vibration:
gNotification.vibrate = gVibrate;
}
void draw()
{
background(bg);
fill(180);
// für die Start-Einblendung
if (frameCount<120) {
translate(width/2, height/2);
textSize(w8);
rotate(PI/2);
text("PURE PONG!", 0, 0);
rotate(-PI/2);
translate(-width/2, -height/2);
}
else { // hier läuft das Spiel
line(0, height/2, width, height/2);
//Zeichnet verbleibende Bälle für die Spieler
for (int i=1; i<=balls1; i++) {
ellipse(width/(balls1+1)*i, height/3, h48, h48);
}
for (int i=1; i<=balls; i++) {
ellipse(width/(balls+1)*i, height*2/3, h48, h48);
}
// wenn ein Spieler gewonnen hat
if (balls<=0 || balls1 <=0) {
textSize(w8);
translate(width/2, height/2);
if (balls>balls1) {
text("YOU WON !", 0, h4);
}
else {
rotate(PI);
text("YOU WON !", 0, h4);
rotate(-PI);
}
translate(-width/2, -height/2);
}
else {
// Bewegung des Balles
ballPos.add(ballDir);
//Begrenzung seitlich
if (ballPos.x < 0+s2 || ballPos.x > width-s2) {
ballDir.x *= -1;
}
// Begrenzung Spieler 1 und Fehler
// Ball ist im Bereich des Balkens
if ( int(ballPos.y) >= int(hh6-h48-s2)) {
//Ball ist vor dem Balken
if ((ballPos.x > playerPos.x-w6 && ballPos.x < playerPos.x+w6)
&& blockdir>4) {
//Beschleunigung oder Verzögerung des Ball je nach Druckpunkt
ballDir.y *=-lerp(0.6, 3, ballspeed);
//Maximal und Minimalwerte für die Ballgeschwindigkeit
if (ballDir.y>0) {
ballDir.y=min(ballDir.y, maxSpeed);
ballDir.y=max(ballDir.y, minSpeed);
}
else {
ballDir.y=max(ballDir.y, -maxSpeed);
ballDir.y=min(ballDir.y, -minSpeed);
}
// Damit kann man dem Ball "Schnitt" verleihen
ballDir.x -=(pplayerPos.x-playerPos.x)/3;
//Haptisches Feedback wenn der Ball getroffen wird
gNotificationManager.notify(1, gNotification);
//Damit verhindert wird, dass der Ball mehrmals die Richtung wechselt
blockdir=0;
}
//bei Fehler
else {
balls-=1;
serve=0;
frame=0;
ballDir.y *=-1;
}
}
// Begrenzung Spieler 1 und Fehler
// Ball ist im Bereich des Balkens
if ( int(ballPos.y) <= int(h6+h48+s2)) {
//Ball ist vor dem Balken
if ((ballPos.x > playerPos1.x-w6 && ballPos.x < playerPos1.x+w6)
&& blockdir>4) {
//Beschleunigung oder Verzögerung des Ball je nach Druckpunkt
ballDir.y *=-lerp(0.6, 3, ballspeed1);
//Maximal und Minimalwerte für die Ballgeschwindigkeit
if (ballDir.y>0) {
ballDir.y=min(ballDir.y, maxSpeed);
ballDir.y=max(ballDir.y, minSpeed);
}
else {
ballDir.y=max(ballDir.y, -maxSpeed);
ballDir.y=min(ballDir.y, -minSpeed);
}
// Damit kann man dem Ball "Schnitt" verleihen
ballDir.x -=(pplayerPos1.x-playerPos1.x)/3;
//Haptisches Feedback wenn der Ball getroffen wird
gNotificationManager.notify(1, gNotification);
//Damit verhindert wird, dass der Ball mehrmals die Richtung wechselt
blockdir=0;
}
//bei Fehler
else {
balls1-=1;
serve=1;
frame=0;
ballDir.y *=-1;
}
}
// Zeichnet den Ball
stroke(255);
fill(255);
ellipse(ballPos.x, ballPos.y, size, size);
//Zeichnet die Balken
stroke(pl);
strokeWeight(h24);
line(playerPos.x-w8, hh6, playerPos.x+w8, hh6);
line(playerPos1.x-w8, h6, playerPos1.x+w8, h6);
strokeWeight(0);
stroke(200);
// Bewegung Player 1 und 2
pplayerPos.x=playerPos.x;
pplayerPos1.x=playerPos1.x;
}
// Spielverzögerung bei Fehler
if (serve==0 && frame<60) {
ballDir.x=0;
ballPos.x=playerPos.x;
ballPos.y=height-h4;
}
if (serve==1 && frame<60) {
ballPos.x=playerPos1.x;
ballPos.y=h4;
ballDir.x=0;
}
// Verzögerung bei Fehler
frame++;
// Blockierung der Ballrichtung nach dem Rückprall am Balken
blockdir++;
}
}
//Funktion für Multitouch Events von
// Eric Pavey - www.akeric.com - 2010-10-24 (verändert)
//-----------------------------------------------------------------------------------------
// Override Processing's surfaceTouchEvent, which will intercept all
// screen touch events. This code only runs when the screen is touched.
public boolean surfaceTouchEvent(MotionEvent me) {
// Zuordnung der TouchPoints zum jeweiligen Spieler
if (int(me.getY(0))<height/2) {
playerPos.x=me.getX(1);
playerPos1.x=me.getX(0);
ballspeed = me.getSize(1);
ballspeed1= me.getSize(0);
}
else {
playerPos.x=me.getX(0);
playerPos1.x=me.getX(1);
ballspeed = me.getSize(0);
ballspeed1= me.getSize(1);
}
return super.surfaceTouchEvent(me);
}
Viel Spass!
Processing Apps für Android II – Multitouch
Ein interessantes Feature der Android- Programmierung ist auf jeden Fall die Möglichkeit Multitouch zu verwenden. Es gibt im Netz schon einige Implementierungen, von denen ich diese von Eric Pavey für mein Projekt Pong für Android genutzt habe. Dieser Beispielcode ist zum Testen hervorragende geeignet.
Das Kernstück verwendet den Processing surfaceTouchEvent und triggert ihn mit dem MotionEvent aus der Android-Library.
//-----------------------------------------------------------------------------------------
// Override Processing's surfaceTouchEvent, which will intercept all
// screen touch events. This code only runs when the screen is touched.
public boolean surfaceTouchEvent(MotionEvent me) {
// Number of places on the screen being touched:
int numPointers = me.getPointerCount();
if (int(me.getY(0))<height/2) {
playerPos.x=me.getX(1);
playerPos1.x=me.getX(0);
ballspeed = me.getSize(1);
ballspeed1= me.getSize(0);
} else {
playerPos.x=me.getX(0);
playerPos1.x=me.getX(1);
ballspeed = me.getSize(0);
ballspeed1= me.getSize(1);
}
return super.surfaceTouchEvent(me);
}
Ich habe diesen Code für mein Projekt Pure Pong! genutzt. Man kann mit ihm dann die 2 Pointer für das Spiel dem richtigen Spieler zuordnen und in draw() ihre Koordinaten verarbeiten.
Processing Apps für Android III – Vibration
Auch die Vibrationsfunktion von Android-Handys kann man in Processing nutzen. Dabei muss man der Anwendung aber zuerst die Rechte dazu einräumen. Das geht unter Processing Menü –> Android –> Sketch Permissions –> Vibration. Oder in der Datei AndroidManifest.XML.
Dieser Code von Eric Pavey ist gut zum Experimentieren geeignet.
Im Projekt Pure Pong! wurde ein ganz leicht veränderter Code verwendet. Zuerst müssen die nötigen Lybraries importiert werden.
import android.content.Context; import android.app.Notification; import android.app.NotificationManager;
Dann werden die Objekte erzeugt.
NotificationManager gNotificationManager;
Notification gNotification;
long[] gVibrate = {0,100};
Dann erzeugt man das die eigentliche Benachrichtigung mit folgendem Code. Ich habe diesen in setup() platziert.
void setup() {
.
.
.
// Create our Notification Manager:
gNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Create our Notification that will do the vibration:
gNotification = new Notification();
// Set the vibration:
gNotification.vibrate = gVibrate;
}
Irgendwo im Code, bei mir in draw() kann die Vibration dann aufgerufen werden.
gNotificationManager.notify(1, gNotification);
Processing Apps für Android I – Erste App erstellen
Habe heute mein Samsung Galxy 3 bekommen.
Hier die offizielle Dokumentation für Processing auf Android und die entsprechende Abteilung im Forum.
Auch das ist ein guter Erfahrungsbericht und eine sehr einfache Schritt für Schritt Anleitung.
In der Processing Dokumentation wird davor gewarnt, dass die, in der Android-Entwicklung verwendete Version von Processing noch im beta-Stadium ist und nicht produktiv eingesetzt werden soll. Unterstützt werden Android-Versionen ab 2.1.
- Zuerst muss die Android SDK heruntergeladen werden. Ich verwende Ubuntu 10.10 und habe das entsprechende Paket laut Anleitung installiert.
Das ADT-Plugin für Eclipse muss nicht installiert werden. Dafür müssen aber im Schritt 4 der Installationsanleitung die folgenden Pakete installiert werden:- unter Available Packages –> Android Repository: SDK Platform Android 2.1, API 7 Revision2
unter Available Packages –> Third Party Add-ons: Google APIs by Google Inc., Android API 7 Revision1
Man kann auch die neueren Versionen der API oder gleich alles zusätzlich installieren.
- Um Android Anwendungen zu erstellen, braucht man eine spezielle Processing Pre-Release 0191 oder0192. Von der Processing Download-Seite, oder von hier: offizielle Dokumentation. Diese Processing Version hat ein eigenes Menü, um Android-Mo
de zu aktivieren, Berechtigungen zu setzen usw. - Jetzt kann man den Android SKD Manager starten (unter Ubuntu, indem ich im Installationsordner das Programm Android). Unter Virtual Devices kann man nun eine gewünschte Maschine erstellen. Hier sind z.B. die gewünschte Version der API und die Bildschirmauflösung anzugeben. Diese virtuelle Maschine kann man dann starten und mit dem Programmieren beginnen.Will man ein Anwendung auch für andere Nutzer freigeben, ist es unerlässlich, diese vorher auf einer realen Maschine zu testen.
- Wenn wir unsere neu installierte Processing -Version jezt starten. Wenn wir den Menü –> Android Mode aktiviert haben, wir, wenn wir auf Play drücken der aktuelle Sketch auf dem Emulator des Android SDK ausgeführt. Durch das Drücken von Play wird der Run-Mode in Processing aktiviert, was im Android-Mode heißt: Der Emulator wird gestartet. Hier ein kleines Beispielprogramm zum Testen:
-
void setup() {
size(240,400);
smooth();
noStroke();
fill(255);
ellipseMode(CENTER);
};
void draw() {
background(10,80,139);
ellipse(width/2, height/2, 150, 150);
}; - Damit man das Programm nun auf dem Android-Gerät laufen lassen kann, muss man die Anleitung hier durcharbeiten: http://developer.android.com/guide/developing/device.html
Tipp: Unter Ubuntu ab 10.04 muss die USB-Konfiguratmionsdatei unter: /etc/udev/rules.d/51-android.rules in 70-android.rules umbenannt werden. - Wenn das Gerät verbunden ist, kann man das Programm von oben in Processing Menü –> Sketch –> Present am Gerät laufen lassen. Mit Run ist es aber nach wie vor möglich, den Emulator aufzurufen!
- Bei mir (Samsung Galaxy 3 Ubuntu 10.10 gab es in weiterer Folge Verbindungsprobleme, die ich auch durch viel Querlesen in den Foren nicht lösen konnte.
Eine Alternative zur Arbeit mit USB stellt das Programm ADB Wireless aus dem Market dar. Damit kann man über Wifi arbeiten. Es benötigt allerdings root-Rechte. Dies bekommt man auf meinem Handy mit z4root (einfach googeln).
- Bei mir (Samsung Galaxy 3 Ubuntu 10.10 gab es in weiterer Folge Verbindungsprobleme, die ich auch durch viel Querlesen in den Foren nicht lösen konnte.
- Einige Tipps zur Programmierung speziell für Android:
- Die Geschwindigkeit der Anwendungen ist ab Froyo (Android 2.2) deutlich höher.
- Viele Mobiltelefone unterstützen nicht die volle 24bit Farbtiefe. Dadurch kann es zu Darstellungsproblemen bei Farbübergängen kommen.
- Die Verwendung von createFont() bringt speziell bei 2D Anwendungen Qualitätsvorteile gegenüber loadFont().
- Statt size(width, height), ist es besser size(screenWidth, screenHeight, A2D) zu verwenden. Dann wird die Anwendung immer auf den vollen Bildschirm skaliert. Für den 3D Renderer würde man dann statt A2D A3D eintragen.
- Was Maus-Eingabe betrifft, können mouseX und mouseY wie gehabt verwendet werden, zusätzlich gibt es neue Variablen. motionX, motionY, pmotionX, pmotionY und motionPressure.
- EventHandler für Key- und MouseEvents sind nicht verfügbar.
- Wenn man die Bildschirm-Orientierung sperren will, kann man das mit orientation(PORTRAIT) oder orientation(LANDSCAPE) machen.
- weiter info unter: http://wiki.processing.org/w/Android
Processing verwenden
Processing kann von der Projektseite heruntergeladen werden. Einfach auspacken und starten.
Der Programmcode wird im Programmfenster geschrieben und kann über die Play-Taste ausgeführt werden. Bei der Ausführung eines Programm wird dieses in einem separaten Fenster geöffnet.
Das Terminal-Fenster unten kann für Textausgaben benutzt werden.
Die Export-Funktion erzeugt eine index.html mit dem dazugehörigen Java Applet. Das Ganze kann dann ganz leicht in eine Homepage eingebaut werden.
Mittlerweile kann man nach der Installation der Android SDK auch direkt für Android entwicklen. Siehe Processing Android.
Des Weiteren gibt es ein Schwester-Projekt namens Processing.js, mit dem man Webanwendungen (welche ohne Plugins laufen) in Processing entwickeln kann.
Die Programmstruktur
Ein klassisches Processing-Programm besteht aus 2 Funktionen. setup() und draw(), wobei der Code in setup() einmalig und der Code in draw() kontinuierlich ausgeführt wird. Jeder Durchlauf von draw() zeichnet ein Frame in das Anwendungsfenster.
Beispiel: hier klicken um das Applet zu starten
void setup() {
size(300,300);
/*legt dir größe
des anwendungsfensters fest*/
}
void draw() {
rect(50,50,100,100); // zeichnet ein rechteck auf den schirm
}
Processing basiert auf Java und übernimmt auch dessen Syntax. Es wurde aber für eine bessere Usability bei grafischen Aufgaben um einige Elemente erweitert.
Kommentare: // einzeiliger Kommentar, /* */ mehrzeiliger Kommentar siehe Beispiel oben Anweisungstrennzeichen: “;” – jede Programmanweisung sollte mit einem Strichpunkt abgeschlossen werden.
Funktionen: über sie kann man bestimmte Aktionen ausführen. Bsp: size(300,300). Funktionen werden immer von zwei runden Klammern gefolgt, innerhalb derer verschiedene Parameter zu finden sein können. Parameter können aber auch fehlen, Bsp: smooth();. Es ist auch möglich eigene Funktionen zu schreiben und diese dann in anderen Teilen des Programms aufzurufen, was die Übersichtlichkeit des Programmcodes deutlich erhöht.
Ausdrücke: Sind vielleicht aus der Mathematik bekannt und auch mit diesen zu Vergleichen. Ein Operator kombiniert dabei verschiedene Werte.
Bsp:
Ausdruck Wert
7 7
3+8 11
(12,8+179)*8 1534,4
7>5 true
6<3 false
Konsolenfenster: Um wichtige Werte auszugeben, die sich z.B. im Anwendungsfenster zu schnell ändern, kann man die Konsole benutzen. Bsp.: print(), oder println() Das folgende Beispiel gibt eine 10 im Konsolenfenster aus.
<em>int x=10;</em> <em>println(x); </em>
Zusätzliche Leerzeichen sind egal. Siehe Beispiel oben.
Groß- Klein- Schreibung beachten!!!
Das Koordinatensystem
In Processing wird von jeder Anwendung ein Fenster erzeugt, dessen Größe von der Funktion size(x,y) bestimmt wird. Der erste Wert x steht für die Anzahl der horizontalen Pixel, der 2. Wert y für die Anzahl der vertikalen.
size(640,480); //erzeugt somit eine Fenster mit 640*480 Bildpunkten
Auch alle anderen Funktionen, nutzen ein Koordinatensystem. Jeder Punkt wird von einer x- und einer y- Koordinate bestimmt.
point(200,200); //zeichnet einen Punkt auf 200 horizontal und 200 vertikal
Um das Programmfenster skalierbar zu machen verwendet man das Objekt frame. Dazu vielleicht später mehr. Durch die Zeile frame.setResizable(true); in setup() erhält man ein Programmfenster, das mit der Maus skaliert werden kann.
Einfache Formen
Punkt:
point(x,y);
Ein Punkt ist 1 Pixel groß und durch seine Position auf der x- und der y-Achse bestimmt.
Beispiel: Applet starten
point(10,20); //erster Punkt links oben, letzer point(20,30);//rechts unten point(30,40); point(40,50); point(50,60);
Punkte außerhalb des Displays werden nicht als Fehler behandelt, sondern einfach nicht angezeigt.
Übung: Schreibe ein Programm, das eine vertikale Linie aus Punkten von (10,10) bis (10,20) ausgibt.
Linie
line(x1,y1,x2,y2);
Zeichnet eine Linie von einem Anfangspunkt zu einem Endpunkt. Die Strichdicke kann nicht direkt in der Funktion line() eingestellt werden. Dafür gibt es die Funktion strokeWeight().
Beispiel: Applet starten

strokeWeight(2);//legt die Linienstäre auf 2 Pixel fest line(10,10,90,90); // zeichnet eine Line von (10,10) nach (90,90) strokeWeight(4); line(90,10,10,90);
Dreieck
triangle(x1,y1,x2,y2,x3,y3);
Zeichnet ein Dreieck zwischen den 3 Eckpunkten. Dieses kann, wie auch andere 2-dimensionale Objekte gefüllt werden. Dafür wird die Funktion fill() verwendet. Als Parameter für fill() kann entweder ein Grauwert (fill(255) wäre weiß), oder eine beliebige Farbe lt. RGB-Modell verwendet werden. fill(r,g,b), wobei r,g und b für die Rot, Grün und Blau-Werte von 0-255 stehen. Es können aber auch 2 Parameter angegeben werden, wobei der 2. für die Transparenz steht (0 – nicht durchsichtig, 255 völlig durchsichtig).
Siehe RGB Modell auf Wikipedia.
Beispiel: Applet starten

fill(227,16,16); //legt dir Füllfarbe für das // hintere Dreieck fest triangle(10,10,90,10,70,70); //hinten fill(255,230);//der 2. Wert definiert die Transparenz triangle(10,40,90,40,70,100);//vorne
Das Beispiel zeigt gut, dass jene Objekte, die im Code weiter unten generiert werden dann in der Darstellung im Vordergrund zu finden sind.
Viereck
quad(x1,y1,x2,y2,x3,y3,x4,y4);
Mit dieser Funkton lassen sich alle Arte von Vierecken erzeugen, auch Parallelogramme und irreguläre Vierecke.
Rechtecke
rect(x,y,width,height);
Rechtecke werden mit Hilfe des linken oberen Eckpunktes (x – und y- Koordinaten), der Breite und der Höhe bestimmt.
Übung: Erstelle ein Bild wie das unten. Verwende dafür die Funktionen size(), fill(), und rect(). 
Ellipse
ellipse(x,y,width,height);
Zeichnet Ellipsen und Kreise. Kreise entstehen dann, wenn widht und height gleich groß sind.
Übung: Erweitere die Abbildung oben mit ein paar runden Elementen.
Bezier
bezier(x1,y1,cx1,cy1,cx2,cy2,x2,y2);
Mit der Bezier- Funktion kann man Kurven erzeugen. Dies erfordert aber einige Übung. Prinzipiell funktioniert es wie die Vektor-Linienfunktion in Grafikprogrammen. Man definiert einen Punkt und lenkt durch einen 2. Punkt, der nicht Teil der Kurve ist die Linie ab.
Beispiel: Applet starten
void setup () {
size(640,480);
}
void draw (){
bezier(100,100,mouseX,mouseY,mouseX,mouseY,400,100);
}
Hier kann mit der Maus cx1,cy1, cx2 und cy2 verändern und bekommt so einen Eindruck wie Bezier- Kurven entstehen.
Hintergrund
background();
Diese Funktion legt die Hintergrundfarbe fest. Sie kann genau wie fill() als Parameter einen Graustufenwert oder aber Werte nach RGB-Modell beinhalten.
Hinweis: Wenn man die RGB- Werte von bestimmten Farben sucht, kann man unter Tools>ColorSelektor ein Fenster aufrufen, dass die RGB Werte von individuell ausgewählten Farben anzeigt.
Sonstiges:
noFill() verhindert das Füllen von 2 und 3-D Objekten, es werden nur die Konturen angezeigt.
noStroke() verhindert das Anzeigen der Ränder von Objekten.
smooth() aktiviert das Antialiasing – Kanten werden geglättet.
noSmooth() deaktiviert Antialiasing
Variablen und Datentypen
Alle Daten im Computer werden irgendwann einmal in eine Folge 0er und 1er übersetzt. In Software sind Daten meist Buchstaben und Zahlen, aber auch Bilder, Musik und Video. In Processing kann man viele dieser verschiedenen Daten verarbeiten, aber nicht alle auf die gleiche Weise.
Datentypen
Sie legen fest, wie Daten in die Sprache des Computers (eine Reihe aus 0 und 1) übersetzt werden. Es gibt sog. primitive Datentypen, die in den meisten Programmiersprachen schon festgelegt sind. Darüber hinaus, kann man aber auch seine eigenen Datentypen erstellen.
Primitive Datentypen sind:
- int – 32 bit – Ganzzahlen von ca. -2 Mrd. bis ca. +2Mrd.
- float – 32 bit – Gleitkommazahl mit 8 Stellen Genauigkeit
- boolean – 1 bit – true oder false
- byte – 8 bit – -128 bis+127
- char – 16 bit – 0 bis 65535 für einzelne Zeichen
- color – 32 bit – 1.6777.216 Farben
Im Gegensatz zu den primitiven gibt es auch nicht primitive Datentypen (z.b. String), die bei Bedarf auch selbst definiert werden können. Beim Typ String kann man schon an der Schreibweise (groß) erkennen, dass er sich von den primitiven Datentypen unterscheidet. Und zwar dadurch, dass er, wie auch in Java üblich ein Objekt ist, dessen Eigenschaften durch eine Klasse definiert werden (zur genaueren Erklärung suche nach Objekte und Klassen). Schauen wir uns nun den Unterschied zwischen char und String genauer an. Während eine Variable vom Typ char ein Zeichen enthalten kann, ist kann ein String eine beliebige Anzahl an Zeichen (eine Zeichenkette) beinhalten. Ein weiterer Unterschied besteht in der Zuweisung der Werte.
char zeichen = ‘a’; // deklariert die Variable zeichen und weist ihr den Wert a zu
char zeichen = “a”; // Error! ist nicht zulässig
String kette = “hallo welt!”; //Erzeugt ein Objekt vom Typ String mit dem Namen kette und weist diesem Objekt den Wert “hallo welt” zu.
Variablen
Variablen sind Platzhalter im Speicher und können mit entsprechenden Werten beschrieben werden. Bevor ein Wert in einer Variablen gespeichert werden kann, muss diese “deklariert” werden. Dabei wird ein entsprechender Datentyp für die Variable definiert. Bsp:
- int x; // deklariert die Variable x
- x=12; //weist der Variablen x den Wert 12 zu
- float z; //deklariert die Variable z
- z=3,14 //weist der Variablen z den Wert 3,14 zu
- boolean wahr;//deklariert die Variable wahr
- wahr=true;//weist der Variablen wahr den Wert true zu
Man kann das Ganze etwas abkürzen und Deklaration und Wertzuweisung in einem Schritt durchführen.
- int x=12;
- float z=3,14;
Eine Variable darf nur einmal deklariert werden, kann dann aber nacheinander verschiedenste Werte enthalten.
Variablenname dürfen frei gewählt werden, allerdings keine reservierten Ausdrücke sein (int, null, true, false usw.).
Processing hat einige vordefinierte Variablen, nämlich in size(): width and height. Wenn diese nicht anders definiert werden, haben sie den Wert 100. Deshalb ist das Anwendungsfenster in einem Programm ohne size(x,y) – Anweisung 100*100 px groß.
println(width + ", " + height); //gibt 100, 100 im Terminalfenster aus
Wenn man ein Programm für verschiedene Auflösungen schreiben will, ist es gut die Variablen width und height zu verwenden, da man dann allein mit der size() – Funktion die ganze Anwendung skalieren kann.
Beispiel:
size(300,300); rect(width/10,height/10,width/2,height/2); rect(width/4,height/4,width/2,height/2); ellipse(width/2,height/2,width/4, height/4);
Aufgabe: Schreibe ein Programm, das einige Variablen von den Typen char und String erzeugt und gibt diese mit der Funktion println im Konsolenfenster aus.
Mathematische Operationen
Da alles was in einer Anwendung gezeigt wird auf Nummern basiert, ist es unerlässlich, dass wir uns zumindest kurz ausschließlich mit diesen beschäftigen.
Beispiel: starte Applet
size(250,250); int grau=50; fill(grau); rect(20,20,100,100); grau=grau+50; fill(grau); rect(40,40,100,100); grau=grau+50; fill(grau); rect(60,60,100,100); grau=grau+50; fill(grau); rect(80,80,100,100); grau=grau+50; fill(grau); rect(100,100,100,100);
Hier wird mit Hilfe einer Variable der Grau-Wert der Quadrate definiert.
Beispiel: starte Applet
size(300,300); int x=2; //x ist jetzt 2 line(x,0, x,height); x=x*2;//x ist jetzt 4 line(x,0, x,height); x=x*2;//x ist jetzt 8 line(x,0, x,height); x=x*2;//x ist jetzt 16 line(x,0, x,height); x=x*2;//x ist jetzt 32 line(x,0, x,height); x=x*2;//x ist jetzt 64 line(x,0, x,height); x=x*2;//x ist jetzt 128 line(x,0, x,height); x=x*2;//x ist jetzt 256 line(x,0, x,height);
Hier werden Parameter der Funktion line() durch die Variable x kontrolliert.
Übung: Verändere das Programm Variablen so, dass es horizontale statt vertikale Linien zeichnet und lass die Linien immer um 2 Pixel dicker werden.
Modulo
In einem Programm kann man also ganz leicht alle Grundrechnungsarten anwenden. Oft ist es aber auch nötig den Rest einer Berechnung mit Ganzzahlen zu ermitteln. Dafür gibt es einen eigenen Operator. Dieser wird Modulo genannt und mit einem % geschrieben.
10 % 3 = 1
6 % 2 = 0
29 % 9 = 2
Natürlich können solche Berechnungen nur mit Zahlen des Datentyps int durchgefürht werden.
Datentypen können nicht beliebig kombiniert werden. Ein Ausdruck in der Form int x=4.0/3 liefert eine Fehlermeldung. float x=4.0/3 würde hingegen 1,3333334 ergeben. Die gleichen Ergebnisse kommen auch zustande, wenn die Zahlen durch Variablen mit den gleichen Werten ersetzt werden. Man muss also eigentlich immer genau wissen, welche Werte eine Variable annehmen kann.
Punktrechnung vor Strichrechnung
… gilt auch in Processing. Zudem kann man, wie auch in der Mathematik, Klammern setzen.
In der Kürze liegt die Würze
x++ steht für x=x+1;
y– für y=y-1;
x+=5 für x=x+5;
y-=5 für y=y-5;
usw.
Mathematische Funktionen
ceil();
Berechnet die nächste Ganzzahl aus einer Gleitkommazahl und rundet dabei immer auf.
floor();
Berechnet die nächste Ganzzahl aus einer Gleitkommazahl und rundet dabei immer ab.
round();
Berechnet eine Ganzzahl aus einer Gleitkommazahl. Dabei wird gerundet.
min(); und max();
Geben von beliebig vielen Zahlen als Parameter jeweils die größte oder die kleinste zurück.
Werte Normalisieren und Mappen
norm(Wert, niedrig, hoch);
Beim Normalisieren geht es darum Wertbereiche ineinander Umzuwandeln, bsp. einen Farbwinkel(0-360°) in einen Bereich zwischen 0 und 1. Die entsprechende Anweisung würde dann lauten: norm(Winkelx, 0, 360);
Der Vorteil des Zahlenbereichs zwischen 0 und 1 besteht darin, dass man die Zahlen beliebig multiplizieren und dividieren kann, ohne je den Bereich zu verlassen.
lerp(niedrig, hoch, norm.Wert);
Die lerp()-Funktion ist die Umkehrfunktion von norm(). Man gibt einen Bereich an und dann einen bereits normalisierten Wert (zwischen 0 und 1). Die Funktion gibt dann den, dem Bereich entsprechenden Wert zurück.
map(Wert, niedrig1, hoch1, niedrig2, hoch2);
Mit der Funtion map() kann man zwei beliebige Zahlenbereiche direkt ineinander umwandeln.
Mouse Input
Position des Mauszeigers
Natürlich kann Processing auch auf Benutzereingaben reagieren. Die Variablen mouseX und mouseY geben die aktuellen x- und y- Koordinaten des Mauszeigers zurück. Zu Beginn sind die beiden 0, das bleiben sie auch, wenn die draw()- Funktion nicht aufgerufen, oder mit noLoop() nur ein mal ausgeführt wird.
Beispiel: starte Applet
void setup() {
size(200,200);
smooth();
noStroke();
}
void draw() {
background(50);
ellipse(mouseX,mouseY,30,30);
}
Übung: Erstelle ein Programm, in dem man mit einem weißen Stift einen dunklen Hintergrund anmalen kann.
Um festzustellen, ob die Maus sich bewegt hat, kann man die Variablen pmouseX (für previous) und pmouseY verwenden. Sie haben den Wert des Mauszeigers des vorangegangenen Frames. Wenn die Maus sich nicht bewegt sind also mouseX und pmouseX gleich. Bewegt sich die Maus, sind sie unterschiedlich.
Beispiel: starte Applet
</pre>
void setup() {
size(200,200);
smooth();
strokeWeight(12);
background(50);
} void draw() {
background(50);
stroke(255);
line(mouseX,mouseY,pmouseX,pmouseY);
}
Übung: Mit der Funktion frameRate() kann man festlegen, wie schnell der Bildschirminhalt erneuert wird. Wert steht für Bilder/sek. Probiere das Programm mit verschiedenen FrameRate – Werten aus.
Mausbuttons
Dafür gibt es in Processing die Variable mousePressed, die bei gedrückem Mausbutton true, sonst false rückmeldet. Außerdem ist es möglich bis zu 3 Mausbuttons abzufragen. Das geht mit mouseButton und liefert die Werte LEFT, CENTER oder RIGHT.
Beispiel: starte Applet
void setup() {
size(200,200);
smooth();
}
void draw() {
background(120);
if (mousePressed) {
if (mouseButton == LEFT) {
fill(0);
} else if (mouseButton == RIGHT) {
fill(255);
} else {
fill(120);
}
ellipse(100,100,100,100);
}
}
Mauszeiger
Mit den Funktionen noCursor() und cursor() kann der Mauszeiger ein- und ausgeblendet werden. Ein individueller Mauszeiger kann mit Hilfe eines beliebigen Objekts und der Variablen mouseX und mouseY als Koordinatenangabe erstellt werden.
Aufgabe: Verändere das obige Programm so, dass es den Mauszeiger nicht mehr anzeigt.
Aufgabe: Schreibe ein Programm, das den Mauszeiger anzeigt und die Maus gleichzeitig ein Objekt bewegt, das aber in X und Y-Richtung nur immer 1/3 des Weges des Mauszeigers zurücklegt.
if – Anweisung
Bis jetzt sind unsere Programm immer von vorne nach hinten durchgelaufen, ohne auf irgend etwas zu reagieren.
Die if – Anweisung gibt uns die Möglichkeit Bedingungen für den weiteren Verlauf des Programms zu setzen. Das können true/false -Abfragen oder der Vergleich zweier Zahlen sein. Es sollten bei Zahlen immer nur Ganzzahlen (int) miteinander verglichen werden.
Vergleichsoperatoren:
> größer als
< kleiner als
>= größer oder gleich
<= kleiner oder gleich
== ist gleich (als Vergleich, einfaches = ist ein Zuweisungsoperator!!!)
!= ist nicht gleich
|| logisches oder
&& logisches und
! logisches nicht.
Für Anweisungen, die nur ausgeführt werden sollen, wenn bestimmte Bedingungen erfüllt sind, empfiehlt sich folgende Schreibweise:
if (test) {
Anweisungen
}
Wobei test für die Bedingung steht.
Beispiel: starte Applet
void setup() {
size(203,203);
smooth();
}
void draw(){
background(255);
stroke(5);
fill(150);
if (mouseX<100 && mouseY<100) {
rect(0,0,100,100);
} else if (mouseX>100 && mouseY<100){
rect(100,0,100,100);
} else if (mouseX<100 && mouseY>100){
rect(0,100,100,100);
} else if (mouseX>100 && mouseY>100){
rect(100,100,100,100);
}
}
Als Struktogramm sieht das Programm so aus:
Viele Möglichkeiten ergeben sich dann,wenn man versucht mehrere Bedingungen ineinander zu verschachteln.
Logische Operationen
In Processing kann man 3 logische Operationen verwenden (&& logisches UND, || logisches ODER und ! logisches NICHT).
Diese Operationen kann man nun beliebig miteinander verknüpfen. Man nennt dies die Boolsche Algebra.
Schleifen
Mit Schleifen kann man unter anderem den Programmablauf steuern. Wir werden uns jetzt speziell mit der for-Schleife beschäftigen. Damit kann man den zu schreibenden Code beträchtlich verkürzen, was zu weniger Fehlern und besserem Überblick führt.
Beispiel (lang, ohne Schleife): starte Applet
size (200,200); line (10,10,190,10); line (10,20,190,20); line (10,30,190,30); line (10,40,190,40); line (10,50,190,50); line (10,60,190,60); line (10,70,190,70); line (10,80,190,80); line (10,90,190,90); line (10,100,190,100); line (10,110,190,110); line (10,120,190,120); line (10,130,190,130); line (10,140,190,140); line (10,150,190,150); line (10,160,190,160); line (10,170,190,170); line (10,180,190,180); line (10,190,190,190);
Beispiel (kurzer Code mit Schleife): starte Applet
size (200,200);
for (int i=10; i<200; i+=10) {
line (10,i,190,i);
}
Die Länge des Codes kann also in diesem Beispiel von 20 Zeilen auf 4 Zeilen verringert werden, ohne die Funktionsweise zu verändern.
Aufgabe: Schreibe ein Programm, das vom Mittelpunkt ausgehend immer größere Kreise auf den Schirm zeichnet. Das ganze sollte so ausshen: Applett. Tipp: verwende noFill(), um nur den Umriss der Ellipsen zu zeigen.
Es gibt nun die Möglichkeit, in Schleifen entweder Bedingungen zu verwenden, oder sie zu verschachteln.
Beispiel: starte Applet
size (200,200);
noFill(); //nur Umriss wird gezeigt
for (int i=10; i<200; i+=10) {
if (i%20==0){ //bleibt kein Rest bei der Division durch 20 --> Kreis
ellipse (width/2,height/2,i,i);
} else { //sonst --> Ellipse
ellipse (width/2,height/2,i+10,i);
}
}
Beispiel: starte Applet
size (200,200);
fill(127);
for (int x=10; x<200; x+=10) {
for (int y=10; y<200; y+=10) {
ellipse (x,y, 10, 10);
}
}
Hier werden für die x- und die y-Richtung je eine Schleife verwendet und Ellipsen mit 10 Pixel Radius auf den Schirm gezeichnet.
Aufgabe: Verändere das Programm so, dass die Größe der gezeichneten Ellipsen durch die Position der Maus veränder werden kann. Applet

Neben der for-Schleife gibt es auch noch die Möglichkeit einer while- Schleife. Sie wird vorwiegend in Fällen eingesetzt, in denen man von vorne herein nicht weiß, wie viele Schleifendurchläufe es geben soll.
Die while -Schleife wird durchlaufen, solange die jeweilig Bedingung erfüllt ist.
while (i>10) {
Anweisungen
}
Projekt Bild aus Text
Dieses Programm liest ein Bild ein und bildet es aus einem frei wählbaren Text neu. Dabei wird die Größe der Buchstaben von der Helligkeit der Bildstelle bestimmt und der Buchstabe in der Farbe der Bildstelle gezeichnet. Dies kann aber, so wie auch die Max- und Minimalgröße der Buchstaben zur Laufzeit des Programms geändert werden.
Wie das funktioniert, ist in den Kommentaren beschrieben.
Beispiel: Das Henne-Ei-Problem
// Generative Gestaltung, ISBN: 978-3-87439-759-9
// First Edition, Hermann Schmidt, Mainz, 2009
// Hartmut Bohnacker, Benedikt Groß, Julia Laub, Claudius Lazzeroni
// Copyright 2009 Hartmut Bohnacker, Benedikt Groß, Julia Laub, Claudius Lazzeroni
//
// modified by 2010 Thomas Koberger
//
// http://www.generative-gestaltung.de
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* pixel mapping. each pixel is translated into a new element (letter)
*
* KEYS
* 1 : toogle font size mode (dynamic/static)
* 2 : toogle font color mode (color/b&w)
* arrow up/down : maximal fontsize +/-
* arrow right/left : minimal fontsize +/-
* s : save png
* p : save pdf
*/
import processing.pdf.*;
boolean savePDF = false;
String inputText ="Henne"; //modified
float fontSizeMax = 50;
float fontSizeMin = 14;
float spacing = 20; // Zeilenabstand
float kerning = 0.5; // Abstand zwischen den Buchstaben
boolean fontSizeStatic = false;
boolean blackAndWhite = false;
PFont font;
PImage img;
int textstellen=0;//darin wird gespeichert, wie oft der Text ausgegeben wurde
void setup() {
size(1000, 1000);
smooth();
//falls es Probleme mit der Darstellung der richtigen Schrift
//geben sollte, kann man die Schrift auch von Hand erstellen und
//dann aus dem Programmordner laden (wie im Artikel Text beschrieben!
font = createFont("Impact",60); //modified
img = loadImage("ei.jpg");//modified
println(img.width+" x "+img.height);
}
void draw() {
background(255);
//ermöglicht das speichern als *.pdf
if (savePDF) beginRecord(PDF, timestamp()+".pdf");
textAlign(LEFT);
//textAlign(LEFT,CENTER); //// also nice!
float x = 100, y = 100; //sorgt dafür, dass ein 100 Pixel breiter Rand frei bleibt
int counter = 0;
while (y < height-100) {
// translate position (display) to position (image)
// die Zahl 100 steht für 100 Pixel, die als Rand freigelassen werden.
int imgX = (int) map(x, 100,width-100, 0,img.width);
int imgY = (int) map(y, 100,height-100, 0,img.height);
// get current color
color c = img.pixels[imgY*img.width+imgX];
//wandelt eine Farbe in einen Grauwert um
int greyscale = round(red(c)*0.222 + green(c)*0.707 + blue(c)*0.071);
// speichert die Position des Koordinatensystems
pushMatrix();
//verschiebt das Koordinatensystem um x und y
translate(x, y);
if (fontSizeStatic) {
textFont(font, fontSizeMax);
if (blackAndWhite) fill(greyscale);
else fill(c);
}
else {
// das ist die Standardeinstellung zu Beginn des Programms
// greyscale to fontsize
float fontSize = map(greyscale, 0,255, fontSizeMax,fontSizeMin);
fontSize = max(fontSize, 1);
textFont(font, fontSize);
if (blackAndWhite) fill(0);
else fill(c);
}
//hier wird der jeweils nächste Buchstabe in die Variable letter geschrieben
char letter = inputText.charAt(counter);
//gibt den Text am Bildschirm aus
text(letter, 0, 0);
float letterWidth = textWidth(letter) + kerning;
// for the next letter ... x + letter width
x = x + letterWidth; // update x-coordinate
popMatrix();
// linebreaks
if (x+letterWidth >= width-100) {
x = 100;
y = y + spacing; // add line height
}
counter++;
// damit der Text wiederholt ausgegeben wird
if (counter > inputText.length()-1) {
textstellen++;
counter = 0;
}
}
if (savePDF) {
savePDF = false;
endRecord();
}
println(textstellen);
}
void keyReleased() {
if (key == 's' || key == 'S') saveFrame(timestamp()+"_##.png");
if (key == 'p' || key == 'P') savePDF = true;
// change render mode
if (key == '1') fontSizeStatic = !fontSizeStatic;
// change color stlye
if (key == '2') blackAndWhite = !blackAndWhite;
println("fontSizeMin: "+fontSizeMin+" fontSizeMax: "+fontSizeMax+" fontSizeStatic: "+fontSizeStatic+" blackAndWhite: "+blackAndWhite);
}
void keyPressed() {
// change fontSizeMax with arrowkeys up/down
if (keyCode == UP) fontSizeMax += 2;
if (keyCode == DOWN) fontSizeMax -= 2;
// change fontSizeMin with arrowkeys left/right
if (keyCode == RIGHT) fontSizeMin += 2;
if (keyCode == LEFT) fontSizeMin -= 2;
//fontSizeMin = max(fontSizeMin, 2);
//fontSizeMax = max(fontSizeMax, 2);
}
// timestamp
String timestamp() {
Calendar now = Calendar.getInstance();
return String.format("%1$ty%1$tm%1$td_%1$tH%1$tM%1$tS", now);
}
Textausgabe
Bevor man Text in Processing ausgeben kann, muss man eine beliebige Schrift zuerst in das .vlw Format konvertieren (Processing –> Tools –> Create Font …).
Bei diesem Vorgang werden der oder die Buchstaben in der angegebenen Größe gerendert. D.h. sei liegen dann als Rastergrafik vor, was zur Folge hat, dass sie nicht mehr beliebig skalierbar sind, sonder man vorher überlegen muss, wie groß die Zeichen sein müssen. Die Rastergrafiken werden in einer .vlw – Datei im Sketch- Ordner des aktuellen Projektes gespeichert.
Alternativ dazu kann man auch im Programm eine Schrift erzeugen, dabei muss man beachten, dass auf einem anderen System die entsprechende Schrift vielleicht nicht installiert ist und sich daraus dann Probleme ergeben. Um Probleme auszuschließen kann mit der Methode Pfont.list() ein Array mit den, auf dem System verfügbaren Schriften erzeugen. Die Anweisung würde dann so aussehen:
String [] fontList=PFont.list();
font = createFont(“Miso”,12);
Um im Programm auf die Zeichen zugreifen zu können, gibt es einen eigenen Datentyp, nämlich PFont. Um Text auf dem Bildschirm ausgeben zu können, muss man eine Variable (genau genommen ein Objekt) vom Typ PFont erstellen und dann mit der Funktion LoadFont() die Zeichen laden. Mit textFont() muss die aktuelle Schrift ausgewählt werden. Um den Text dann anzeigen zu können benutzt man die Funktion text().
text(daten, x,y);
text(daten, x,y, breite, höhe);
Wobei daten alle Daten von den Typen String, char, int, float enthalten kann.
textSize();
Die Funktion textSize() legt die Schriftgröße fest. Wenn aber die gewählte Schriftgröße größer ist, als der Wert, mit dem die Schrift gerendert wurde, dann wird sie pixelig!
Beispiel: starte Applet
PFont font; //erstellt ein Pfont-Objekt mit dem Namen
font size(200,200);
font = loadFont("ArialMT-48.vlw"); //weist dem Objekt font die Grafiken für die einzelnen Buchstaben zu
textFont(font); //estellt ein Objekt textFont mit dem Parameter font
textSize(180);//setzt die Schriftgröße auf 180 Pixel
fill(255); //legt die Schriftfarbe fest
text(2, 50, 180); //gibt text auf dem Schirm aus
textSize(32); fill(0);
text("lorem ipsum ...",20,70); //gibt Text auf dem Schirm aus
Vorsicht: Bei exotischen Schriften stehen oft nicht alle Zeichen zur Verfügung (Zahlen und Umlaute).
Beispiel: starte Applet
PFont font; //erstellt ein Pfont-Objekt mit dem Namen font
size(200,200);
background(255);
font = loadFont("ArialMT-48.vlw"); //weist dem Objekt font die Grafiken für die einzelnen Buchstaben zu
textFont(font); //estellt ein Objekt textFont mit dem Parameter font
for (int i=1; i<10; i++) {
fill(0,220-i*20);
textSize(20+i*6);
text(i, i*20-20, 150); //gibt text auf dem Schirm aus
}
textLeading(abstand);
Abstand ist dabei der Abstand der Zeilen in Pixel.
textWidth(float);
Mit der Funktion textWidth() kann man die genaue Breite in Pixel eines Strings ermitteln.
Aufgabe: Schreibe ein Programm, das deinen Nachnamen schön formatiert am Bildschirm ausgibt.
Aufgabe: Überlege Dir, wie man Buchstaben in Kreisform anordnen könnte! Versuche daraus ein Programm zu erstellen!
Tastatureingaben
Die einfachste Methode abzufragen, ob eine Taste auf der Tastatur gedrückt wurde, stellt die Variable keyPressed dar. Sie kann die Werte true oder false annehmen.
Beispiel: starte Applet
int x = 10;
void setup() {
size(200,200);
}
void draw() {
background(96);
if (keyPressed == true) {
x=x+1;
}
ellipse(x,100,10,10);
}
Drücke irgend eine Taste, um die Ellipse von links nach rechts zu bewegen.
Die Variable key vom Typ char speichert jeweils die letzte Tastatureingabe.
Beispiel: starte Applet
Achtung: Die Grafiken für die Buchstaben müssen vor der Benutzung mit Processing –> Tools –> Create Font … erstellt werden.
PFont font; //erstellt ein Pfont-Objekt mit dem Namen font
void setup() {
size(200,200);
font = loadFont("ArialMT-48.vlw"); //weist dem Objekt font die Grafiken für die einzelnen Buchstaben zu
textFont(font); //erstellt ein Objekt textFont mit dem Parameter font
}
void draw() {
background(96);
text(key,90,110); //gibt text auf dem Schirm aus
}
Die key-Variable kann benutzt werden, um zu prüfen, welche Taste gedrückt wurde. Hier ist es auch wichtig bei der Abfragen mit den logischen Operatoren && (logisches UND) und || (logisches ODER) zu arbeiten.
TIPP: weil alle Zeichen über den ASCII-Code festgelegt werden, kann man mit der Variable key auch rechnen. Sie gibt, dann den ASCII-Wert der letzten Tastatureingabe zurück.
Eine weitere Variable keyCode speichert ALT, CONTROL, SHIFT, UP, DOWN, LEFT und RIGHT. Bevor man diese codierten Tasten abfragt, sollte man mit key == CODED (hoffentlich true) abfragen, ob eine Taste codiert ist.
Events
Events sind spezielle Funktionen, die den Programmfluss unterbrechen können. Dabei unterbrechen sie den Programmfluss immer nach einem Durchlauf von draw(). Sie stören also den Ablauf innerhalb der draw()-Funktion nicht. Die Eventparameter werden bis zum Ende eines draw()-Durchlaufs gespeichert. Danach läuft die Event-Funktion genau einmal durch.
Maus-Events:
mousePressed() //läuft einmal ab, wenn eine Maustaste gedrückt wird.
mouseReleased() //läuft einmal ab, wenn eine Maustaste losgelassen wird.
mouseMoved() //läuft einmal ab, wenn der Mauszeiger bewegt wird.
mouseDragged() //läuft einmal ab, wenn der Mauszeiger bewegt wird, während ein Taste gedrückt worden ist.
Keyboard-Events:
keyPressed() //läuft einmal ab, wenn eine Taste gedrückt wird.
keyReleased() //läuft einmal ab, wenn eine Taste losgelassen wird.
Beispiel: starte Applet Zeichenprogramm
void setup(){ size(300, 300); fill(255,50); noStroke(); noLoop();//dadurch wird draw nicht automatisch wiederholt background(0); } void draw() { ellipse(mouseX, mouseY,5,5); } void mouseDragged() { redraw(); //lässt den Code in draw ein mal ablaufen }
Dieses Beispiel lässt draw() immer dann einmal durchlaufen, wenn die Maustaste gedrückt ist und die Maus bewegt wird. Dies wird durch ein Event getriggert.
Aufgabe: Schreibe das Programm Pong so um, dass die Steuerung über Events erfolgt.
Facebook Anwendung Teil1
Ziel dieses Projektes ist es, eine Facebook-Anwendung zu erstellen, bei der man mit Processing auf Informationen von Facebook zugreifen kann. Konkret möchte ich den aktuell eingeloggten Facebook identifizieren (als JavaScript umgesetzt) und seine Facebook UserID in mein Processing Applett übertragen. Dann werden Userdaten und das UserImage heruntergeladen und auf den Bildschirm gezeichnet.Damit ist Teil 1 erledigt.
Wie bekomme ich einen App-Account?
Einen App-Account kann jeder Facebook User erstellen.Dafür meldet man sich bei Facebook an und aktiviert die Anwendung “Developer”. Dort kann man dann rechts oben eine “Neue Anwendung erstellen”.
http://www.facebook.com/developers/
Neuerdings muss dann das eigene Konto verfiziert werden und würde euch dringend raten das mit Handy und nicht mit der Kreditkartennummer zu machen (auch Handy ist unsicher, meiner Ansicht nach noch das kleinere Übel). Beim registrieren der App sind dann folgende Einstellungen vorzunehmen:
- Anwendungs-ID (wird zugewiesen, ist dann in die von mir erstellte Seitenvorlage einzufügen)
- API-Schlüssel (wird dann im Processing Applet benötigt)

- Anwendungs-Geheimcode (für Processing Applet)

- Seitenadresse ist auch bei Canvas-URL einzutragen (reale Internetadresse deiner Seite (Bsp: http://xxxx.bplaced.net/xxxxxx)
- Leinwandadresse oder Canvas-Seite (die Adresse deiner App in Facebook – die Seite liegt nicht wirklich dort!!! Bsp: http://apps.facebook.com/xxxxxxxxx/

- Sandkastenmodus aktivieren (nur du hast Zugriff auf deine App)

Hier gibt es allgemeine Info zur App-Registrierung.
Prinzipiell funktioniert das so, dass man auf irgendeiner URL seine Seite veröffentlicht. Diese wird dann in einem sog. IFrame (HTML-Fenster) unter der Adresse der Facebook app angezeigt.
Erstellen eines geeigneten HTML – Javaskript-Unterbaus
Hilfreich ist dieses Tutorial . Hier findet man ein Seitengerüst mit dem man über HTML/JavaSkript Abfragen auf Facebook durchführen kann.Dafür wird die aktuelle von Facebook angebotene Programmierschnittstelle, die Graph-API verwendet. JavaSkript kann nun zum Beispiel einen Login/Logout Button rendern und den aktuellen Anmeldestatus abfragen.
Konkret brauchen wir dieses Gerüst um erst einmal an die UserID des Besuchers der Seite zu kommen. Ich habe diesen Unterbau ein wenig modifiziert. Damit die Seite funktioniert muss man noch die eigene Facebook App-ID einsetzen.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:fb="http://www.facebook.com/2008/fbml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>MyFriends</title>
</head>
<body>
<div id="fb-root"></div>
<script type="text/javascript">
window.fbAsyncInit = function() {
FB.init({appId: 'YourAppID', status: true, cookie: true, xfbml: true});
/* All the events registered */
FB.Event.subscribe('auth.login', function(response) {
// do something with response
login();
});
FB.Event.subscribe('auth.logout', function(response) {
// do something with response
logout();
});
FB.getLoginStatus(function(response) {
if (response.session) {
// logged in and connected user, someone you know
login();
}
});
};
(function() {
var e = document.createElement('script');
e.type = 'text/javascript';
e.src = document.location.protocol +
'//connect.facebook.net/en_US/all.js';
e.async = true;
document.getElementById('fb-root').appendChild(e);
}());
function login(){
FB.api('/me', function(response) {
document.getElementById('login').style.display = "block";
document.getElementById('login').innerHTML = response.name + "
" +response.id + " succsessfully logged in!";
document.Hallo.setString(response.id)
});
}
function logout(){
document.getElementById('login').style.display = "none";
}
//stream publish method
function streamPublish(name, description, hrefTitle, hrefLink, userPrompt){
FB.ui(
{
method: 'stream.publish',
message: '',
attachment: {
name: name,
caption: '',
description: (description),
href: hrefLink
},
action_links: [
{ text: hrefTitle, href: hrefLink }
],
user_prompt_message: userPrompt
},
function(response) {
});
}
function showStream(){
FB.api('/me', function(response) {
//console.log(response.id);
streamPublish(response.name, 'Thinkdiff.net contains geeky stuff',
'hrefTitle', 'http://thinkdiff.net', "Share thinkdiff.net");
});
}
function share(){
var share = {
method: 'stream.share',
u: 'http://thinkdiff.net/'
};
FB.ui(share, function(response) { console.log(response); });
}
function graphStreamPublish(){
var body = 'Reading New Graph api & Javascript Base FBConnect Tutorial';
FB.api('/me/feed', 'post', { message: body }, function(response) {
if (!response || response.error) {
alert('Error occured');
} else {
alert('Post ID: ' + response.id);
}
});
}
function fqlQuery(){
FB.api('/me', function(response) {
var query = FB.Data.query('select name, hometown_location,
sex, pic_square from user where uid={0}', response.id);
query.wait(function(rows) {
document.getElementById('name').innerHTML =
'Your name: ' + rows[0].name + "<br />" +
'<img src="' + rows[0].pic_square + '" alt="" />' + "<br />";
});
});
}
function setStatus(){
status1 = document.getElementById('status').value;
FB.api(
{
method: 'status.set',
status: status1
},
function(response) {
if (response == 0){
alert('Your facebook status not updated. Give Status Update Permission.');
}
else{
alert('Your facebook status updated');
}
}
);
}
</script>
<h3>MyFriends</h3>
<p><fb:login-button autologoutlink="true" perms="email,user_birthday,
status_update,publish_stream"></fb:login-button></p>
<div id="login" style ="display:none"></div>
<div id="name"></div>
<script src="http://connect.facebook.net/en_US/all.js#xfbml=1"></script>
<fb:like href="http://apps.facebook.com/YourAppAddress"
show_faces="true" width="450"></fb:like>
###hier kommt das Processing-Applet hin###
</body>
</html>
function login(){
FB.api('/me', function(response) {
document.getElementById('login').style.display = "block";
document.getElementById('login').innerHTML = response.name + "
" +response.id + " succsessfully logged in!";
document.Hallo.setString(response.id)
});
}
Die UserID an das Applet übertragen
Damit die UserID nun von JavaSkript (läuft schon in der HTML-Seite) in das Processing Applet übertragen werden kann, habe ich in dem Beispiel oben die Funktion login um diese Zeile ergänzt:
function login(){
FB.api('/me', function(response) {
document.getElementById('login').style.display = "block";
document.getElementById('login').innerHTML = response.name + "
" +response.id + " succsessfully logged in!";
document.Hallo.setString(response.id)
});
}
Sie bewirkt, dass in einem Applet mit dem Namen Hallo die Funktion setString aufgerufen, und ein String (response.id –> dieser enthält die Facebook-UserID des eingeloggten Users) an das Applet übergeben wird.Damit das funktioniert, braucht man die Library java.applet.Applet. Sie wird mit folgender Anweisung importiert: import java.applet.Applet;
Die Funktion sieht im unserem Fall so aus:
public void setString(String aString)
{
fbUserIDs = aString;
MyIDisloaded =true;
}
In der markierten Zeile wird die von JavaSkript übergebene String Variable aString an die globale Variable fbUserIDs übergeben.
Das funktioniert aber nur, wenn man dem exportierten Processing Applet noch händisch einen Namen, wie in diesem Fall, Hallo, zuweist.
var attributes = {
code: 'facebook_myid_1_0.class',
name:'Hallo',
archive: 'facebook_myid_1_0.jar,jpen-2.jar,generativedesign.jar,core.jar',
width: 1000,
height: 600,
image: 'loading.gif'
};
Damit haben wir die UserID in dem Applet zur Verfügung.
Der Rest der Daten
Um an die Daten zu kommen, die wir sonst noch für unser Projekt benötigen, benutzen wir nun eine andere, etwas ältere Programmierschnittstelle von Facebook, nämlich die Old REST API. Auf http://wiki.processing.org/w/Facebook,_REST_API findet man Beispielcode, um Daten über die Facebook API abzurufen. Man schickt eine rel. komplexe Anfrage und bekommt als Antwort ein XML-File, aus dem man dann die Daten extrahieren und in die einzelnen Strings umwandeln muss. Ich habe die Anfragen, die Überwachung des asynchronen Ladevorgangs und auch das Verarbeiten der XML-Files in eine Klasse mit dem Namen Facebook gepackt. Die Methode für die Anfragen sieht so aus:
void getmyXML () {
int currentUser = 0;
String xmlRequest = this.CallMethod( new String[] {
"method=facebook.Users.getInfo",
"uids=" + UserIDs,
"fields=first_name,last_name,hometown_location,pic", // see link above for more options
"format=XML"
}
);
myXML = GenerativeDesign.loadXMLAsync(thisPApplet, xmlRequest);
dataloaded = false;
}
Hier wird in Zeile 3 die Methode CallMethod aufgerufen, Sie gibt dann die Anfrage mit div. Signaturen und verschlüsselten Elementen als String zurück.
String CallMethod ( String[] args )
{
String[] params = new String[args.length + 3];
System.arraycopy( args, 0, params, 0, args.length );
params[params.length-3] = "api_key=" + ApiKey;
params[params.length-2] = "call_id=" + System.currentTimeMillis();
params[params.length-1] = "v=1.0";
String sig = this.GenerateSIG ( params );
String paramString = join( params, "&" ) + "&sig=" + sig;
String response = RestServer + RestNode + "?" + paramString;
return response;
}
/**
* Generate a call signature, see:
* http://wiki.developers.facebook.com/index.php/How_Facebook_Authenticates_Your_Application
* http://wiki.developers.facebook.com/index.php/Authorization_and_Authentication_for_Desktop_Applications
*/
String GenerateSIG ( String[] args )
{
java.util.Arrays.sort( args );
String argString = join( args, "" );
argString += ApiSecret;
return this.md5Encode( argString );
}
/**
* MD5 encode a String using Processing API ( hex() )
*/
String md5Encode ( String data )
{
java.security.MessageDigest digest = null;
try {
digest = java.security.MessageDigest.getInstance("MD5");
}
catch ( java.security.NoSuchAlgorithmException nsae ) {
nsae.printStackTrace();
}
digest.update( data.getBytes() );
byte[] hash = digest.digest();
StringBuilder hexed = new StringBuilder();
for ( int i = 0; i < hash.length; i++ )
{
hexed.append( hex( hash[i], 2 ) );
}
return hexed.toString().toLowerCase();
}
Dann wird in getXML mit der Anfrage als Parameter die Methode loadXMLAsync aus der generative-design-library aufgerufen (http://www.generative-gestaltung.de/). Dieses asynchrone Laden der Daten ist wichtig, da sonst ja jegliche Animation, die in draw() läuft, unterbrochen würde, bis die Daten geladen sind. So werden die Datensätze im Hintergrund geladen, während das Programm weiterläuft. Um die fertig geladenen Daten dann im laufenden Programm verwenden zu können, muss man nun immer wieder prüfen, ob sie schon fertig geladen sind. Das macht bei mir die Methode loaderloop(), bzw. imageloaderloop(). Diese werden immer wieder in draw() aufgerufen.
boolean loaderloop () {
if (myXML != null) {
if (myXML.hasChildren() == false) {
println("not loaded yet");
}
else {
dataloaded=true;
return dataloaded;
}
}
return dataloaded;
}
boolean imgloaderloop () {
if (imgisloading) {
if (myImage.width == 0) {
// image is not yet loaded
println("not loaded yet");
}
else if (myImage.width == -1) {
// this means an error occurred during image loading
}
else {
// image is ready to go, draw it
imgloaded=true;
imgisloading =false;
return imgloaded;
}
}
return imgloaded;
}
Sind die XML-Daten geladen, wird (auch aus draw()) die Methode getMyData() aufgerufen und das XML File wird in die einzelnen Strings umgewandelt. Von hier aus wird dann auch, nachdem man die Adresse für das Profilbild ausgelesen hat der asysnchrone ImageLoader aufgerufen. Der dann das Profilbild analog zu den Daten vorher aus dem Web lädt.
String getmyData () {
usersXml = myXML.getChildren();
String myinfo = usersXml[currentUser].getChild("first_name").getContent();
myinfo += " "+usersXml[currentUser].getChild("last_name").getContent();
String mypicurl= usersXml[currentUser].getChild("pic").getContent();
myImage = GenerativeDesign.loadImageAsync(thisPApplet, mypicurl);
imgisloading=true;
myXML = null;
dataloaded = false;
return myinfo;
}
Zu guter Letzt gibt es noch das Hauptprogramm, aus dem dann diese Objekte und Methoden aufgerufen werden. Die Erklärungen dazu sind als Kommentar im Code enthalten.
//import der benötigten Programmbibliotheken
import generativedesign.*;
import java.awt.Graphics;
import java.applet.Applet;
// application api key and secret
String fbApiKey = "Dein Appkey";
String fbApiSecret = "Dien AppSecret";
// Initialisieren der Variablen und Objekte
String fbUserIDs = null;
facebook mydata;
PFont font;
PApplet thisPApplet = this;
boolean MyIDisloaded = false;
boolean MyObjektiscreated =false;
String myfull_name=null;
PImage my_image=null;
void setup ()
{
size( 1000, 600 );
fill( 250);
stroke(29,64,136);
strokeWeight(3);
smooth();
textAlign( CENTER );
font = createFont("Impact", 12);
}
void draw ()
{
background(50);
//hier die Abfage, ob die UserId schon im Applet angekommen ist
if (MyIDisloaded && !MyObjektiscreated) {
mydata = new facebook(fbApiKey, fbApiSecret, fbUserIDs);
mydata.getmyXML();
MyObjektiscreated =true;
}
//wenn nicht, wird ... is loading angezeigt
if (!MyIDisloaded && !MyObjektiscreated) {
text("...is loading", 20,20);
}
// andernfalls wird geprüft, ob die XML-Daten schon vorhanden sind
else {
if (mydata.loaderloop()) {
//wenn ja, werden diese als Strings abgerufen
myfull_name=mydata.getmyData();
}
// analog wird auch beim Profilbild geprüft, ob es schon geladen ist
if (mydata.imgloaderloop()) {
// wenn ja, wird es in my_image gespeichert
my_image=mydata.getmyImage();
}
}
//wenn das Bild dann da ist, wird am Bildschirm ausgegeben.
if(my_image!=null) {
fill(98,122,173);
Zip-File ellipse(width/2,height/2+10,200,200);
fill(255);
text(myfull_name,width/2,height/2+my_image.height/2+20);
image(my_image,width/2-my_image.width/2,height/2-my_image.height/2);
}
}
public void setString(String aString)
{
fbUserIDs = aString;
MyIDisloaded =true;
}
Hier das Ganze noch mal als Zip-File.
Funkt aber nur, wenn vorher eine Facebook App registriert und die entsprechenden Schlüssel in der Datei eingetragen wurden.
Projekt Ping Pong
Mit den bis jetzt bearbeiteten Dingen kann man schon eine Menge machen.
Beispiel: starte Applet
int size = 20; // Balldurchmesser
float xpos, ypos; // Position des Balles
float xspeed = random(5,6); // Geschw. des Balls in x-Richtung
float yspeed = random(0,4); // Geschw. des Balls in y-Richtung
int fehler=0;
int playerpos;
void setup()
{
size(640, 400);
noStroke();
frameRate(30);
smooth();
playerpos =height/2;
xpos = 45;
ypos = playerpos;
}
void draw()
{
background(96);
// Bewegung des Balles
xspeed *= 1.001;
xpos += xspeed;
ypos += yspeed;
//damit der Ball zurückprallt (rechts, oben, unten)
if (xpos > width-size/2){
xspeed *= -1;
}
if (ypos > height-size/2 || ypos < 0+size/2) {
yspeed *= -1;
}
// Begrenzung links und Fehler
if ( xpos <= 30+size/2) {
if (ypos > playerpos-50 && ypos < playerpos+50){
xspeed *= -1;
yspeed += (ypos-playerpos)/5;
}
}
//Fehler
if (xpos <= 0){
fehler=fehler+1;
if (fehler <= 4){
xpos = 50;
ypos = playerpos;
xspeed = random(5,6);
//xspeed *= -1;
yspeed = 0;
println("Ball: "+ fehler );
}
}
// Zeichnen des Balls und des Rechtecks
ellipse(xpos, ypos, size, size);
rect (10, playerpos-50,20,100);
//Ausgabe Fehlerzahl
// Bewegung Rechteck
if (keyPressed) {
if (key == 'w') {
playerpos = playerpos - 8;
}
if (key == 'y') {
playerpos = playerpos + 8;
}
}
Aufgabe1: Verändere das Programm so, dass ein Fehler (int fehler) oder Bälle (5-int fehler) und Punkte (mit der Systemvariablen frameCount) am Display ausgegeben werden.
Aufgabe2: Verbessere die Physik des Spiels.
Aufgabe3: Verändere das Programm so, dass 2 Spieler gegeneinander spielen können.
Farbe
Hier kommt bei Processing, wie bei vielen anderen Programmen zunächst das sog. RGB-Modell zum Einsatz. D.h. jede Farbe wird aus einem Rot-, Grün- und Blauwert zwischen 0 und 255 definiert. Konkret wird dann aus einer Mischung aller Farben mit der maximalen Intensität (255,255,255) –> Weiß und (0,0,0) ergibt Schwarz.
Wenn man alle drei Farben zu gleichen Teilen mischt, ist das Ergebnis immer ein Grauton. Seine Intensität ergibt sich aus der Intensitäten der Einzelfarben.
Mit den Funktionen
background(r,g,b);
fill(r,g,b);
fill(r,g,b,alpha);
stroke(r,g,b); und
stroke(r,g,b,alpha);
kann man die Eigenschaften der Elemente am Schirm definieren. Alpha definiert die Deckkraft eines Objekts (0…durchsichtig bis 255…volle Deckung).
Processing bringt im Menü unter Tools auch den Color Selector mit dem man Farben auswählen und dann deren RGB- und HSB- Werte ablesen kann.
Beispiel: starte Applet
Bewege deine Maus über die Animation und bestimme damit die Deckkraft der Farbkreise.
void setup()
{
size(200, 200);
noStroke();
smooth();
}
void draw()
{
background(255);
fill(255,0,0,mouseX*2);
ellipse(100,70,100,100);
fill(0,255,0,mouseX*2);
ellipse(70,120,100,100);
fill(0,0,255,mouseX*2);
ellipse(130,120,100,100);
}
Beispiel: starte Applet
size(200, 200);
background(262,202,49);
for(int i=0; i<200; i++){
stroke(0,0,255,i);
line(i,0,i,200);
}
Aufgabe: Verändere den Code des letzten Beispiels so, dass sich der Übergang je nach Mausposition nach links und recht verschieben lässt.
starte Applet
Um den Umgang mit Farben noch etwas einfacher zu gestalten, kann man in Processing auf einen eigenen Datentyp, nämlich color() zugreifen. Damit kann man Farben, die man öfters verwendet Namen zuordnen. Die Farbdefinition funktioniert, wie sonst auch, als Grauwert oder RGB, mit oder ohne alpha-Wert.
Beispiel: starte Applet
Die Farbe des Rechtecks ändert sich je nach Mausposition von einer vorher definierten Farbe grün zu blau.
void setup() {
size(200, 200);
}
void draw() {
background(255,188,3);
color blue=color(3,206,255);
color green = color(195,255,3);
if (mouseX<100){
fill(blue);
} else {
fill(green);
}
rect(50,50,100,100);
}
Neben dem Standard-Farbmodell RGB kann man in Processing auch das HSB-Modell anwenden. Hier wird eine Farbe mit der Angabe des Farbtons (hue), der Farbintensität (straturation) und der Helligkeit (brightness) angegeben. Mit der Funktion colorMode(mode) kann man das Fabmodell wählen. Mode kann somit für RGB oder HSB stehen.
Beispiel: starte Applet
int a=1;
int c=1;
void setup(){
size(360, 200);
colorMode(HSB);
}
void draw(){
a++;
for(int i=0; i<360; i++){
c=(a+i)%360;
stroke(c,255,255);
line(i,0,i,200);
}
}
Aufgabe: Gestalte das Spiel Ping Pong farbig.
Fotos
Auch für Bilder bringt Processing mit PImage einen eigenen Datentyp mit. Um ein Bild auszugeben geht man also folgendermaßen vor: Man deklariert eine Variable (genauer ein Objekt) vom Typ PImage. Dann weist man ihr mit der Funktion loadImage(“dateiname”) einen Wert zu. Um sie dann anzuzeigen verwendet man die Funktion image(var, x, y, breite, höhe). Processing kann Bilder der Formate jpg, gif und png verarbeiten.
Beispiel: starte Applet
PImage foto;//deklariert die Variable foto;
void setup(){
size(200, 200);
foto = loadImage("blume.JPG");
//weist der Variablen foto die datei blume.jpg zu
}
void draw(){
image(foto, 0, 0, 200,200);
//gibt die Variable foto auf dem Bildschirm aus
}
Um Bilder zu verändern kann man beispielsweise die Funktion tint(); verwenden, die wie fill() und stroke() verwendet werden können.
Aufgabe: Schreibe ein Programm die Blume je nach Mausposition rot und grün einfärbt.
Will man komplexere Veränderungen vornehmen, kann man mit der Methode loadPixels() kann man die einzelnen Bildpunkte als Daten vom Typ Color in ein Array laden.
Siehe Projekt: Bild aus Text
Zufall
Auch in Processing gibt es Möglichkeiten Zufallszahlen zu erzeugen. Die einfachste Möglichkeit bietet hier die Funktion random(). Manchmal will man aber Zufallszahlen haben, die jeweils nur leicht voneinander abweichen, sowie in der Natur. Für diese Aufgabe gibt es die noise() – Funktion, die immer Zahlen zwischen 0 und 1 ausgibt. Durch den folgenden Parameter kann man nur die Größe der Abweichung von Zahl zu Zahl beeinflusst werden.
random(hoch, tief)
Beispiel: starte Applet
Von diesem Programm werden an zufälligen Positionen Ellipsen gezeichnet. Auch die Strichstärke ist variabel. Dadurch entstehen große und kleine Formen.
void setup()
{
background(0);
smooth();
size(400,400);
stroke(255,40);
}
void draw()
{
int x=int(random(400));
int y=int(random(400));
int z=int(random(10));
strokeWeight(z);
ellipse(x, y, 1,1);
}
randomSeed(Wert)
Mit randomSeed() und einem Integer als Wert kann man, wenn man will (und der Integer-Wert immer der gleiche ist) immer die gleiche Zufallszahlenreihe erzeugen.
Aufgabe: Schreibe ein Programm, das in etwa folgenden Output erzeugt.
noise(x,y,z)
Die noise- Funktion kann mit einem, zwei oder drei- Parametern aufrufen, je nachdem, ob man 1-,2- oder 3-dimensional arbeiten will. Erzeugt werden immer Zahlenwerte zwischen o und 1, die jeweils nur geringe Unterschiede zu den Zahlen davor aufweisen. Will man natürliche Vorgänge simulieren, ist diese Funtktion sehr sinnvoll.
Die Werte von x, y und z müssen, um unterschiedliche Zunoise- Funktionfallszahlen zu generieren bei jedem Durchlauf erhöht werden. Größe der Schritte definiert dann die Variabilität der Zahlenreihe. Für die meisten Anwendungen eignen sich Schritte von 0.005 bis o.1.
Beispiel: starte Applet
float v = 0.0;
float inc = 0.01;
float next= 0.01;
void setup()
{
background(50);
smooth();
size(600,600);
noStroke();
frameRate(120);
}
void draw()
{
translate(width/2, height/2);
float b = noise(v) * 10.0;
float n = next/1.0;
rect (next*cos(n)+b,next*sin(n)+b, 1, 1);
v=v+inc;
next = next+0.01;
}
Beispiel: starte Applet
float v = 0.0;
float inc = 0.01;
float next= 0.8;
void setup()
{
background(255);
smooth();
size(600,600);
noStroke();
frameRate(120);
colorMode(HSB);
}
void draw()
{
translate(width/2, height/2);
float b = noise(v) * 10.0;
float n = next;
fill(frameCount%180,255,255,50);
ellipse (next*cos(n)+b,next*sin(n)+b, 5*b, 5*b);
v=v+inc;
next = next+0.1;
}
XMLElement
Viele große Internetportale bieten mittlerweile Programmierschnittstellen, sog. API’s an. Über diese kann man z.B. mit Processing Daten abrufen. Die Daten werden per URL angefordert. Die Antwort kommt meist (nicht immer) per XML-File. XML ist eine Auszeichnungssprache (Extensible Markup Language). Dabei werden die Daten in sog. Tags, ähnlich wie in HTML verpackt.
Um diese Daten verarbeiten zu können, gibt es in Processing Das Objekt XMLElement. In diesem können ganze XML Files gespeichert werden. XMLElement bringt Methoden mit, um einzelne Datensätze auslesen zu können.
Eine Anfrage, wie im Projekt FacebookAnwendung 1 sieht dann z.B. so aus (mit println() ausgelesen):
http://api.facebook.com/restserver.php?api_key=YourAPIKey&call_id=YourAPPID&fields=first_name,last_name,hometown_location,pic&format=XML&method=facebook.Users.getInfo&uids=YourUserID&v=1.0&sig=4c0d5ef0edf23eb16d95ed17e4fab781
Als Antwort schickt Facebook dann die folgende Datei:
<Users_getInfo_response xsi:schemaLocation="http://api.facebook.com/1.0/ http://api.facebook.com/1.0/facebook.xsd" list="true"> <user xmlns="http://api.facebook.com/1.0/"> <first_name xmlns="http://api.facebook.com/1.0/">YourFirstName</first_name> <hometown_location xsi:nil="true"/> <last_name xmlns="http://api.facebook.com/1.0/">YourLastName</last_name> <pic xmlns="http://api.facebook.com/1.0/"> http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs272.snc3/23196_YourUserID_7319_s.jpg </pic> <uid xmlns="http://api.facebook.com/1.0/">YourUserID</uid> </user> </Users_getInfo_response>
Alle diese Infos zwischen Tags, also <first_name>YourFirstName</first_name> nennt man Elemente. Diese Elemente sind hierarchisch gegliedert, d.h. <user> wäre das Parent Element von <first_name>, <last_name>, <pic>, usw. Diese sind dann also Child-Elemente von <user>. xmlns=”http://api.facebook.com/1.0/” steckt in einem öffnenden Tag und ist eine sog. Attribut.
Angenommen wir haben die obige Antwort in ein XMLElement myXML gespeichert, so können wir mit folgenden Anweisungen die Daten auslesen:
- myXML.getChild(“user”)
gibt das Child-Element von <Users>, also <User> zurück-
<user xmlns="http://api.facebook.com/1.0/"> <first_name xmlns="http://api.facebook.com/1.0/">YourFirstName</first_name> <hometown_location xsi:nil="true"/> <last_name xmlns="http://api.facebook.com/1.0/">YourLastName</last_name> <pic xmlns="http://api.facebook.com/1.0/"> http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs272.snc3/23196_YourUserID_7319_s.jpg </pic> <uid xmlns="http://api.facebook.com/1.0/">YourUserID</uid> </user>
-
- myXML.getChild(“user/first_name”)
-
<first_name xmlns="http://api.facebook.com/1.0/">YourFirstName</first_name>
-
- myXML.getChild(“user”).getContent()
gibt den Inhalt des Elements <User> zurück, in dem Fall leer, also null-
null
-
- myXML.getChild(“user/first_name”).getContent()
gibt den Inhalt des Elements <First_Name> zurück-
YourFirsName
-
- myXML.getChildren()
gibt alle Child-Elemente eines Elements als String-Array zurück z.B.: - myXML.getChild(“user”).getChildren()
-
[0] <first_name xmlns="http://api.facebook.com/1.0/">YourFirstName</first_name> [1] <hometown_location xmlns="http://api.facebook.com/1.0/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/> [2] <last_name xmlns="http://api.facebook.com/1.0/">YourLastName</last_name> [3] <pic xmlns="http://api.facebook.com/1.0/"> http://profile.ak.fbcdn.net/hprofile-ak-snc4/hs272.snc3/23196_YourUserID_7319_s.jpg</pic>
-
[4] <uid xmlns="http://api.facebook.com/1.0/">YourUID</uid>
-
Den Content der Elemente kann man entweder als String, oder Array abspeichern und dann im Programm weiterverarbeiten.
Darüber hinaus gibt es auch noch die Möglichkeit selbst XMLElemente zu erstellen. Eine komplette Elementreferenz findet man hier http://processing.googlecode.com/svn/trunk/processing/build/javadoc/core/index.html.
Strings
Variablen des Datentyps String können Zeichenketten enthalten. Im Gegensatz zu char, int oder float handelt es sich beim String nicht um einen “primitiven” Datentyp, sondern um ein Objekt (siehe Objekte und Klassen).
Während der Datentyp char ein Zeichen enthalten kann und dieses in Apostrophen ‘ ‘ gesetzt werden muss, enthält ein String in der Regel mehrere Zeichen und diese werden in normale Anführungszeichen ” ” gesetzt.
Beispiel:
String A ="Hallo Welt!"; println(A);
Gibt “Hallo Welt” im Terminalfenster aus.
Der Datentyp String bringt viele Methoden mit. Mit ihnen können die Daten im Objekt manipuliert werden.
Methoden von String:
- String.length(); //gibt die Anzahl der Zeichen zurück.
Beispiel:
String A ="Hallo Welt!"; println(A.length());
Gibt 12 im Terminalfenster aus. Beachte die runde Klammer nach length(). Sie ist hier unbedingt nötig, da es sich im Unterschied zum Array.length hier um eine Methode handelt.
- String.startsWith(); //gibt je nach Übereinstimmung true oder false zurück
String.endsWith(); //gibt je nach Übereinstimmung true oder false zurück
Beispiel:
String A ="Hallo Welt!";
println(A.startsWith("Hallo"));
println(A.endsWith("Hallo"));
println(A.endsWith("Welt!"));
Dieses Beispiel gibt true, false, true zurück, da die jeweiligen Zeichenketten mit dem Beginn oder dem Ende des Strings A übereinstimmen, oder auch nicht.
- String.charAt(); //gibt das Zeichen an der in Klammer angegebenen Position zurück (beginnt nat. mit 0)
Beispiel:
String A ="Hallo Welt!"; println(A.charAt(6));
Gibt ein ‘W’ im Terminalfenster aus.
String.toLowerCase(); //wandelt den String in Kleinbuchstaben um String.toUpperCase(); //wandelt den String in Großbuchstaben um
Beispiel:
String A ="Hallo Welt!"; println(A.toLowerCase()); println(A.toUpperCase());
Gibt hallo welt und HALLO WELT im Terminalfenster aus.
- String.substring(); // gibt den String ab der in Klammer angegebenen Stelle, oder zwischen diesen Stellen zurück
Beispiel:
String A ="Hallo Welt!"; println(A.substring(4)); println(A.substring(3,8));
Gibt “o Welt!” und “lo We” zurück!
- String.equals(); // vergleicht 2 Strings und gibt true oder false zurück!
Da Strings keine primitiven Datentypen sind, können sie nicht mit == verglichen werden.
- String.toCharArray(); // legt die Zeichen des Strings in einem Array ab.
Beispiel:
String A ="Hallo Welt!"; char [] c = A.toCharArray(); println(c[0]); println(c[6]);
Gibt H und W im Terminalfenster aus.
Funktionen
Funktionen dienen dazu, oft wiederkehrende Codebereiche (Berechnungen, Formen usw.) nur einmal eingeben zu müssen, und sie dann immer wieder aufrufen zu können.
In der Regel geben Funktionen etwas zurück (das Resultat der Berechnung). Deshalb werden die Rückgabewerte von Funktionen auch einem Datentyp zugeordnet (int, float usw.)
Beispiel:
void setup() {
float ergebnis = multipliziere(3.5, 2.0); // hier wird die funktion aufgerufen und der return-wert in der variable ergebnis gespeichert
println (ergebnis);
}
float multipliziere (float zahl1, float zahl2) {
return (zahl1*zahl2); // hier wird der Rückgabewert definiert
}
In diesem Beispiel wird das Ergebnis der Funktion multipliziere in der Variable ergebnis gespeichert und dann per println am Bildschirm ausgegeben.
Es Funktionen kann man von Variablen i. a. dadurch unterscheiden, dass bei ihrem Aufruf Parameter übertragen werden, die dann in einer runden Klammer angegeben werden.
frameRate….Variable
multipliziere (float zahl1, float zahl2)…Funktion
Wird eine Funktion, die beispielsweise eine komplexe Figur zeichnet, ohne Parameter aufgerufen, so bleiben die runden Klammern ohne Inhalt.
Die Funktionen setup() und draw()
Auch setup() und draw() sind Funktionen, allerdings welche die keine Werte zurückgeben. Solchen Funktionen wird eine void -Anweisung vorne angestellt.
void setup() ; void draw()
Außerdem haben diese beiden Funktionen in Processing eine spezielle Bedeutung, da die Funktion setup() immer als erste nach Programmstart genau ein mal ausgeführt wird, und dann (falls nicht anders definiert –> noLoop) die Funktion draw() immer wieder ausgeführt wird, bis das Programm selbst, oder der Benutzer das Programm stoppt.
Beispiel: Augen starte Applet
int mx,my; //definiert 2 globale variablen
void setup() {
size(400,400);
strokeWeight(3);
smooth();
}
void draw() {
background(102);
gesicht(); //zeichnet den kopf und den schnabel
augen(150,height/2); //zeichnet das linke auge
augen(250,height/2); //zeichnet das rechte auge
}
void gesicht() { //funktion ohne rückgabewert
fill(200);
ellipse (200,200,200,200);
fill(200);
bezier(150,250,150,350,150,350,250,250);
}
void augen(int x,int y) { //funktion ohne rückgabewert
fill(255);
ellipse(x,y,80,80);
mx=mouseX-x;
my=mouseY-y;
fill(50);
ellipse(x+mx/10,y+my/10,20,20);
}
Winkel und Wellen
Für die Erstellung von Grafiken sind Sinus-Kurven und Spiralen von essentieller Bedeutung. Mit Processing sind diese Formen sehr einfach umzustzen.
Zuerst aber etwas Mathematik. Winkel können in Grad ° und in Rad angegeben werden, wobei eine volle Umdrehung 360° oder 2 π (PI) Rad entspricht. π ist also 3,14. In Processing gibt es dafür die Konstanten PI, QUARTER_PI, HALF_PI und TWO_PI.
Zum Umrechnen dienen die Funktionen radians() – rechnet Grad in Rad, oder degrees() - rechnet Rad in Grad um.
sin und cos
Immer wenn in Processing ein Winkel anzugeben ist, wird dieser vom Programm in Rad erwartet. Wenn man lieber in Grad arbeitet, muss man ihn eben umrechnen (mit radians()).
Beispiel: Sinus starte Applet
size (900,100);
noStroke(); smooth();
float winkel = 0.0;
for (int x=0; x<= width; x+=1) { //die Schleife ist notwendig, um alle x-Werte zu zeichnen.
fill(0);
float y= 50 + (-sin(radians(winkel))*40.0);//berechnet die y-Werte für eine Sin-Kurve
rect(x,y,1,1);
fill(160);
y= 50 + (-cos(radians(winkel))*40.0);//berechnet die y-Werte für eine Cos-Kurve
rect(x,y,1,1);
winkel += 0.5; //definiert die Skalierung der Wellen
}
Mit der Sinus- und Cosinus-Funktion kann man auch Kreise und Spiralen aus Objekten generieren.
Beispiel: Kreis starte Applet
noStroke();
smooth();
size(500,500);
int radius =200;
for (int grad =0; grad<360; grad +=12) {
float winkel = radians(grad);float x = 250 + (cos(winkel)*radius);
float y =250 + (sin(winkel)*radius);
ellipse(x,y,15,15);
}
Beispiel Spirale: starte Applet
noStroke();
smooth();
size(500,500);
int radius =10;
for (int grad =0; grad<3600; grad +=12) {
float winkel = radians(grad);
float x = 250 + (cos(winkel)*radius);
float y =250 + (sin(winkel)*radius);
ellipse(x,y,15,15);
radius += 2;
}
Hier wird einfach für jeden Winkel der Radius erhöht. Das bewirkt, dass aus dem Kreis eine Spirale wird.
Aufgabe: Ändere das Programm so um, dass es eine schöne Spirale zeichnet.
Beispiel Vieleck: starte Applet
int ecken = 3; //Anzahl der Ecken!
int winkel;// wird später aus der Anzahl der Ecken berechnet
float x1, y1, x2, y2;
void setup() {
size(500, 500);
smooth();
}
void draw() {
//die 2 folgenden Anweisungen lassen die Objekte nach kurzer Zeit verblassen
fill(255, 20);
rect(0, 0, width, height);
//Aufruf der Funktion mit den Parametern:
//Eckenzahl, MauspositionX, MauspositionY, RadiusX, RadiusY
vieleck(ecken, mouseX, mouseY, 80, 80);
}
// Funktion zeichnet Vielecke
void vieleck (int seiten, int x, int y, int radiusX, int radiusY) {
// winkel entspricht dem Abstander der Ecken in Grad
// z.B.: 3 Ecken:120°; 4 Ecken:90° usw.
winkel = (int) 360/seiten;
//die Schleife wird der Anzahl der Ecken entsprechend aufgerufen
//z.B.: 3 mal beim 3-Eck oder 4 mal beim 4_eck
for (int grade=0; grade<360; grade+=winkel) {
// erster Punkt
x1 =sin(radians(grade))*radiusX+(x);
y1 =cos(radians(grade))*radiusY+(y);
// zweiter Punkt
x2 =sin(radians(grade+winkel))*radiusX+(x);
y2 =cos(radians(grade+winkel))*radiusY+(y);
//zeichnet Linie zwischen Punkt 1 und Punkt 2
line(x1, y1, x2, y2);
}
}
In diesem Beispiel wird eine Funktion verwendet, um ein Vieleck mit beliebig vielen Punkten um die Mausposition am Bildschirm zu zeichnen. Dies wird erreicht, indem man den vollen 360°-Winkel durch die Anzahl der Eckpunkte des Vieleckes teilt.
z.B.: 3 Eckpunkte: 360/3 = 120°
Danach beginnen wir in einer Schleife bei Null° und addieren jeweils den Winkel, der bei der vorherigen Division herausgekommen ist. Bei 360° angekommen brechen wir die Schleife ab.
bei 3 Eckpunkten gibt die Schleife nach der Reihe die Winkel 0,120,240 aus.
Wir berechnen dann aus dem Sinus und dem Cosinus der 3 Winkel die Koordinaten der 3 Punkte des Dreiecks.
Der erste Punkt kommt unter der aktuellen Mausposition zu liegen, da wir die X-Achse mit dem Sinus und die Y-Achse mit dem Cosinus berechnen. Bei Null° bleibt also der X-Wert gleich, da der Sin von 0 ebenfalls 0 ist. Der Cos von 0 ist aber 1 und somit verschiebt sich der erste Punkt in pos. Y-Richtung nach unten. Dann werden die weiteren 2 Punkte nach dem gleichen Schema berechnet.
Um dann noch aus den einzelnen Punkten eine Form zu bekommen, brauchen wir in jedem Schleifendurchlauf noch einen weiteren Punkt, der entweder dem aktuellen um den Winkel voraus- oder nacheilt.
Transformationen
Mit der Funktion translate(x ,y ); kann man das ganze Koordinatensystem verschieben. Werden danach Formen gezeichnet, addieren sich die x- und y-Koordinaten der Form und der translate()-Anweisung. Auch mehrere translate()- Anweidungen addieren sich.
Außerdem kann mit der Funktion pushMatrix() die Position des Koordinatensystems gespeichert werden. Mit der Umkehrfunktion popMatrix() wird das mit pushMatrix() gespeicherte Koordinatensystem wieder hergestellt. Die beiden Funktionen können nicht ohne die jeweils andere angewendet werden.
pushMatrix() und popMatrix() können bis zu 32 mal verschachtelt angewendet werden.
Beispiel: starte Applet
smooth();
size(500,500);
for (int i=0;i<300;i+=10) {
pushMatrix();
translate(i/2,i/2);
rect(0,0,10,10);
}
for (int i=0;i<300;i+=10) {
popMatrix();
fill(128,200);
rect(10,10,10,10);
}
Dieses Beispiel zeichnet in der ersten Schleife ein Rechteck immer an der gleichen Position im Koordinatensystem. Das Koordinatensystem wird aber bei jedem Schleifendurchlauf weiter verschoben und somit auch das gezeichnete Rechteck. In der zweiten der beiden Schleifen wird dann die Verschiebung des Koordinatensystems Schritt für Schritt zurückgenommen und das Rechteck immer um 20 Punkte verschoben gezeichnet.
Die Funktion scale()
Wie mit translate() die Position, kann man nun mit scale() die Größe eines Objekts ändern. Wert steht für einen Multiplikator, schreibe also 1.2 für 120% oder 0.5 für 50%.
scale(wert);
scale(wertx,werty);
Aufgabe: Verändere das obige Beispiel in der Art, dass es die Rechtecke in der ersten Schliefe vergößert und in der zweiten wieder verkleinert.
Die Funktion rotate()
Ähnlich wie bei sclale() und tranlate() kann man mit rotate() das Koordinatensystem verändern. In diesem Fall kann man es drehen.Der Winkel muss in Rad angegeben werden.
rotate(winkel);
Aufgabe: Drehe das Rechteck im obigen Beispiel in jedem Schleifendurchlauf.
Beispiel: starte Applet
void setup() {
smooth();
background(0);
stroke(255,20);
size(500,500);
translate(250,250);
frameRate(5);
}
void draw() {
translate(250,250);
strokeWeight(frameCount/2);
rotate(radians(frameCount*10));
line(0,0,100,0);
}
Viele Arten ein Programm zu erstellen
Viel Wege führen nach Rom. Das gilt auch für die Programmierung. In den Basics haben wir die Grundlagen der Programmierung kennengelernt und unser Augenmerk auf einzelne Befehle oder Programmstrukturen gerichtet. Diese Programme kann man relativ leicht von Grund auf neu schreiben. Je größer und komplexer ein Programm, desto höher ist die Wahrscheinlichkeit, dass zumindest Teile des Codes schon in anderen Programmen vorgekommen sind.
Es ist nichts dagegen einzuwenden, andere Programme, sofern sie keinem Copyright unterliegen, das dies untersagt, als Vorlagen für seine eigenen Projekte zu nutzen. Die einfachste Möglichkeit ein Programm zu verändern liegt darin, die Werte der Variablen zu verändern, wobei die Variationsmöglichkeiten hier natürlich sehr gering sind.
Die Collage-Technik besteht darin, Code-Schnippel aus verschieden Programmen in sein eigenes Projekt zu kopieren. Das ist ein sehr weit verbreiteter Weg, um zu fertigen Projekten zu kommen. Dabei sollte man aber darauf achten, nicht zu viel neuen Code auf einmal einzubinden, da mit der Anzahl der Fehler auch der Aufwand steigt, der nötig ist sie zu beheben.
Processing arbeitet mit sog. Sketches. Es ist sehr empfehlenswert für alle Funktionen eines Programms zuerst ein kleines Programm mit der entsprechenden Funktion zu schreiben und zu testen. Erst wenn alle Einzelteile funktionieren, geht man daran diese Teile zu einem komplexen Ganzen zu verbinden.
Datenfelder (Arrays)
In Variablen kann man nicht nur einzelne Werte speichern, sondern mehrere, die ein- oder zweidimensional organisiert sind. Das nennt man dann ein – oder zweidimensionale Arrays. Sie unterscheiden sich nur wenig von “normalen” Variablen und werden in der Form array[] (eindimensional) oder array [] [] (zweidimensional) deklariert. Der Datentyp (int, float, uws.) muss außerdem festgelegt werden.
float [] eindimensional = new float [200];
Die obige Anweisung deklariert ein eindimensionales Datenfeld mit dem Namen eindimensional, in dem 200 Werte gespeichert werden können (0-199). Aufgerufen werden die einzelnen Felder dann mit eindimensional[i], wobei i dann für eine Zahl von 0 bis 199 steht.
float[] [] zweidimensional = new float [200] [200];
Hier haben wir ein Beispiel für ein zweidimensionales Feld, das dann mit zweidimensional[i][i] mit Werten befüllt werden kann. So können die Werte dann natürlich auch wieder ausgelesen werden.
Beispiel: starte Applet
PImage foto;//deklariert die Variable foto;
int [] [] grau = new int [400] [400];
//hier wird ein zweidimensionales Array deklariert
void setup() {
size(400, 400);
smooth();
foto = loadImage("blume.JPG");
//weist der Variablen foto die datei blume.jpg zu
foto.loadPixels();
//sollte man aufrufen, bevor man die einzelen Pixel bearbeitet
//foto.pixels=sort(foto.pixels);
//wird diese Funktion aktiviert, werden die Farben sortiert ausgegeben
for (int gridX = 0; gridX < foto.width; gridX++) {
for (int gridY = 0; gridY < foto.height; gridY++) {
// überträgt die Farbinfo eines Pixels auf die Variable farbe
color farbe = foto.pixels[gridY*foto.width+gridX];
// wandelt die Farbinfo in einen Grauwert um
int grauwert =round(red(farbe)*0.222+green(farbe)*0.707+blue(farbe)*0.071);
grau [gridX] [gridY] = grauwert;//speichert den Grauwert in unser Array grau
}
}
}
void draw() {
background(0);
//image(foto,0,0);// gibt einen netten Überlagerungseffekt
//jetzt wird nur für jedes 10 Pixel der Helligkeitswert ausgelesen.
for (int gridX = 0; gridX < foto.width; gridX+=10) {
for (int gridY = 0; gridY < foto.height; gridY+=10) {
fill(grau [gridX] [gridY]);//gibt den Grauwert aus unserem Array aus
ellipse (gridX,gridY,9,9);
}
}
}
Dieses Beispiel liest die Farbinformationen jedes Pixels eines Bildes aus und wandelt sie in Grauwerte um. Dann werden Ellipsen mit den Grauwerten gezeichnet.
Arrays können durch Funktionen manipuliert werden.
append(Array-A, Wert) // hängt ein Element an
shorten(Array-A) // eliminiert das letzte Element
arrayCopy(Array-A,Array-B) // kopiert den Inhalt von Array A nach Array-B
reverse(Array-A) // kehrt die Reihenfolge der Elemente um
sort(Array-A) //sortiert die Elemente der Größe nach
concat(Array-A, Array-B) // hängt Array-B an Array-A an
Aufgabe: Erweitere das folgende Programm so, dass es:
- ein Element an A anhängt
- 2 Elemente kürzt
- die Elemente in ein neues Array B kopiert
- und die Elemente in umgekehrter Reihenfolge ausgibt.
int [] A = {1, 2, 3, 4, 5, 6};
for (int i=0; i<A.length; i++) { //*.length gibt die Anzahl der Elemente eines Arrays zurück (int)
println(A[i]);
}
Rekursionen
Rekursionen sind Funktionen, die sich selbst aufrufen. D.h. auch, das eine Funktion durch sich selbst definiert wird. GNU (Gnu’s not Unix) der freien Softwarebewegung von Richard Stallman ist eine Beispiel. Man kann sich eine Rekursion auch so vorstellen: Man steht zwischen zwei Spiegeln und sieht dann unendlich viele Wiederholungen der Bilder von Vorne und von Hinten. Was man damit alles machen kann ist nicht ganz einfach zu verstehen. Dazu vielleicht ein einfaches Beispiel.
Beispiel: starte Applet
void setup() {
size(700,100);
int x=width;
half_size(x);
}
void half_size (int xpos) {
if (xpos>1) {
int x= xpos*3/4;
line(x,0,x,height);
half_size(x);
}
}
In diesem Beispiel teilt die Funktion half_size eine Strecke in 3/4 und zeichnet dort eine Linie. danach ruft sich die Funktion selbst neu auf und teilt die verbleibende Strecke wieder in 2 Teile (bei 3/4 der Länge und zeichnet eine Linie. Und so weiter, bis nur noch 1 Pixel übrig ist. Dann wird das ganze beendet.
An dem Beispiel kann man schon ganz gut erkennen, dass Rekursionen die Gefahr bergen, dass sie unendlich weiterlaufen. Das muss durch irgendeinen geeigneten Mechanismus (in diesem Beispiel die Abfrage, ob die verbleibende Strecke noch größer ist als 1) verhindert werden.
Beispiel: starte Applet
void setup() {
size(600, 400);
//noStroke();
smooth();
teileBox(0, 0, 600, 400, 20);
//Funktion wird einmal aufgerufen
}
void teileBox(float x, float y, float breite, float hoehe, int grau) {
fill(grau,100);
rect(x, y, breite, hoehe);
int minSeite = 200;
//definiert ab welcher minimalen Breite/Höhe die Funktion stoppen soll.
if ((breite < minSeite) || (hoehe < minSeite)) {
return;
}
if (breite > hoehe) { //Wenn die Box breiter ist als hoch, dann wird sie horizontal geteilt!
float leftbreite = breite * random(0.2, 0.8); // Breite der linken Box
teileBox(x, y, leftbreite, hoehe, grau);
teileBox(x+leftbreite, y, breite-leftbreite, hoehe, grau+40);
} else { //Wenn die Box höher ist als breit, dann wird sie vertikal geteilt!
float tophoehe = hoehe * random(0.2, 0.8); // Höhe der oberen Box
teileBox(x, y, breite, tophoehe, grau);
teileBox(x, y+tophoehe, breite, hoehe-tophoehe, grau+40);
}
}
Dieses Beispielprogramm teilt Rechtecke je nachdem, ob sie höher oder breiter sind, horizontal und vertikal, bis eine minimale Breite bzw. Höhe erreicht ist.
Objekte und Klassen
Das Programmieren mit Objekten und Klassen nennt man objektorientiertes Programmieren. Es bietet den Vorteil einer viel besseren Übersicht bei längeren Programmen und ist hier auch dringend anzuraten.
Grundlegendes:
Eine Klasse ist eine allgemeine Vorlage für ein Objekt. Nehmen wir als Beispiel ein Auto. Die Vorstellung von einem Auto wäre dann die Klasse. Die Eigenschaften eines Autos sind in der Klasse definiert (Bsp.: Auto hat 4 Räder, 4 Lichter, ein Lenkrad usw.). Manche Eigenschaften können aber auch variieren, so dass z.B. manche Autos unterschiedlich viele Sitze oder Türen aufweisen (oder auch eine aktuelle Geschwindigkeit und Richtung). Diese Eigenschaften übermittelt man der Klasse, wenn nach ihrer Vorlage ein Objekt erstellt werden soll. Ein Auto hat aber nicht nur Eigenschaften, die in der Programmierung in Form von Variablen verarbeitet werden, sondern auch Funktionen. Man nennt diese Methoden. Mit Hilfe dieser Methoden kann man ein Objekt manipulieren. Das Auto könnte beispielsweise beschleunigen und somit ändert sich die aktuelle Geschwindigkeit. Es könnte auch nach links lenken und somit die Fahrtrichtung ändern.
Klasse: Auto
class Auto {
}
Eigenschaften: Farbe, Türen, Leistung usw.
class Auto {
color farbe;
int tueren;
float leistung;
}
Methoden: Lenken, Beschleunigen, Bremsen usw.
class Auto {
void lenken() {
// Funktion lenken
}
void beschleunigen() {
// Funktion beschleunigen
}
}
Objekt: VW Käfer (blau, 2 Türen, 32 PS uws.)
Ein Beispiel: Wir wollen ein Programm schreiben, das Ameisen erzeugt, die sich auf zufälligen Bahnen bewegen und Futter sammeln. Dabei sollten ganz einfache Regeln gelten: Findet eine Ameise Futter und trägt gerade kein Futter, soll sie das gefunden Futter aufnehmen. Das trägt sie dann, bis sie wieder auf Futter stößt. Hier soll sie das Futter, das sie gerade trägt fallen lassen.
Wir werden also diesmal objektorientiert vorgehen und brauchen für unser Projekt 2 Objekte: das Futter und die Ameisen. Wie müssen diese Objekte nun beschrieben werden? Fangen wir der Einfachheit halber mit der Beschreibung des Futters an. Dieses hat in unserem Beispiel eine sehr passive Rolle und sollte somit leicht zu beschreiben sein.
Wie wir wissen nennt man die Beschreibung eines Objektes Klasse (class). Die sieht nun bei unserem Futter so aus:
class Food {
float xPos;
float yPos;
float radius;
int traeger;
int frame=0;
color clr;
Food (float axPos, float ayPos, float aradius, int atraeger, color aclr) {
xPos = axPos;
yPos = ayPos;
radius = aradius;
traeger = atraeger;
clr = aclr;
}
Dabei werden im oberen Teil die benötigten Variablen definiert. Unten haben wir den sog. Konstruktor. Dieser weist den Variablen xPos, yPos usw. von uns definierte Werte zu. Der Konstruktor gibt im Gegensatz zu einer Funktion nichts zurück, nicht einmal void und wird immer, wenn ein neues Objekt erstellt wird automatisch aufgerufen. Somit ist die Definition der Klasse abgeschlossen.
Um ein Objekt der Klasse Food zu generieren müssen wir noch folgendes machen:
1. Wir erzeugen eine Variable der Klasse Food mit dem Namen Futter.
Food Futter;
2. Wir rufen eine Funktion mit dem Namen der Klasse auf und befüllen die Variable Futter mit einem Objekt der Klasse Food.
Futter=new Food();
3. Jetzt enthält unsere Variable Futter lauter Nullen für alle int, float und color – Variablen, die in ihr enthalten sind. Indem wir jetzt unseren Konstruktor aufrufen, befüllen wir unser Objekt mit den von uns gewünschten Werten. Dafür schreiben wir der besseren Übersichtlichkeit halber eine eigene Funktion.
void gibFutter() {
Futter = new Food[50];
int border = 50;
for (int f=0; f<Futter.length; f++) {
float xPos = random(border, width-border);
float yPos = random(border, height-border);
float radius = random(5, 5);
int traeger = 500;
color clr = color(50,200,0,120);
Futter[f] = new Food(xPos, yPos, radius, traeger, clr);
}
}
Diese Funktion nennen wir gibFutter. Sie erstellt in diesem Fall 20 Futterobjekte, die mit zufälligen Koordinaten versehen werden (und 50px Abstand zum Rand, damit sie auch von den Ameisen aufgenommen werden können).
Das wärs dann eigentlich. Fast das gleiche, aber mit ein wenig mehr Eigenschaften machen wir mit den Ameisen. Die Klasse inkl. dem Konstruktor sieht bei den Ameisen dann so aus:
class Animals {
PVector position;
PVector direction;
float spin = 0.10;
float radius;
boolean loaded;
int blocked;
int traegt;
color clr;
Animals (float theX, float theY,
float aradius, boolean aloaded, int ablocked, int atraegt, color aclr) {
position = new PVector (theX, theY);
direction = new PVector (10,10);
direction.x = random (-1, 1);
direction.y = random (-1, 1);
radius = aradius;
loaded = aloaded;
blocked = ablocked;
traegt = atraegt;
clr = aclr;
}
}
Methoden:
Um unseren Objekten jetzt noch sagen zu können, was sie machen sollen, müssen wir in den Klassendefinitionen noch etwas ergänzen. Und zwar sog. Methoden. Das sind Funktionen in der Klassendefinition, die Eigenschaften der Objekte ändern können. Z.B. sollten sich die Ameisen fortbewegen und am Bildschirm dargestellt werden können. Dafür haben wir 2 Methoden, nämlich die Methode render und die Methode move.
//Methode des Objekts Ameise
void render() {
fill(clr);
//der Code ab hier dient dem Zeichnen der Ameise
PVector normVector = new PVector(1,0);
float angle = direction.heading2D();
pushMatrix();
translate(position.x,position.y);
rotate(angle-PI);
strokeWeight(2);
line(0-radius/2,0-radius, 0+radius/2, 0+radius);
line(0+radius/2,0-radius, 0-radius/2, 0+radius);
line(0,0-radius, 0, 0+radius);
ellipse(0,0,radius*0.5,radius*0.5);
ellipse(0+radius-1,0,radius*1.5,radius);
ellipse(0-radius*0.7,0,radius,radius);
//ellipse(position.position.x, position.position.y, radius*2, radius*2);
popMatrix();
}
//Methode des Objekts Ameise
void move() {
direction.x += random (-spin, spin);
direction.y += random (-spin, spin);
direction.normalize ();
position.add (direction);
if (position.x < radius || position.x > (width-radius)) {
direction.x *= -1;
}
if (position.y < radius || position.y > (height-radius)) {
direction.y *= -1;
}
}
Der Aufruf einer solchen Methode sieht dann so aus:
Ameise[i].move();
Ameise[i].render();
Außerdem können zu jedem Zeitpunkt alle Variablen eines Objekts abgerufen werden. Ein Beispiel für eine Abfrage kommt im gesamten Quellcode unten vor.
Unser Beispiel ist bewusst eher einfach gehalten und könnte noch um viele nette Features erweitert werden.
Beispiel Ameisen: starte Applet
Animals [] Ameise;Food [] Futter; //Der Aufruf einer solchen //Methode sieht dann so aus: void setup() { size(400, 400,P2D); gibFutter(); buildAmeise(); frameRate(120); } void draw() { background(180); //die erste Schleife geht in jedem Frame alle Ameisen durch for (int i=0; i<Ameise.length; i++) { //damit weichen die Ameisen einader aus for (int u=0; u<Ameise.length; u++) { if ((Ameise[i].position.x < Ameise[u].position.x+20 && Ameise[i].position.x > Ameise[u].position.x-20 && Ameise[i].position.y < Ameise[u].position.y+20 && Ameise[i].position.y > Ameise[u].position.y-20) && u!=i) { Ameise[i].direction.x *= -1; Ameise[i].direction.y *= -1; } } Ameise[i].move(); Ameise[i].render(); Ameise[i].blocked-=1; //die zweite Schleife alle Futterpakete for (int f=0; f<Futter.length; f++) { if (Ameise[i].position.x < Futter[f].xPos+6 && Ameise[i].position.x > Futter[f].xPos-6 && Ameise[i].position.y < Futter[f].yPos+6 && Ameise[i].position.y > Futter[f].yPos-6) { //hierhin kommt man nur, wenn eine Ameise auf Futter gestoßen ist if(Ameise[i].loaded && Ameise[i].traegt!=f) { //hier kommt hin, was passiert, wenn eine schon beladene Ameise auf Futter trifft Ameise[i].blocked=30; //Anzahl der Frames, bevor die Ameise wieder Futter aufnehmen kann Ameise[i].loaded = false; Futter[f].traeger=500; } else { //das passiert, wenn die Ameise auf Futter gestoßen ist, nichts trägt und nicht gesperrt ist. if (Ameise[i].blocked<1) { Ameise[i].traegt= f; Futter[f].traeger= i; Futter[f].dragged(); Ameise[i].loaded = true; } } } Futter[f].render(); println(Futter[f].traeger); } } } //diese Funktion erzeugt die Ameisen void buildAmeise() { Ameise = new Animals[5]; int border = 50; for( int i =0;i<Ameise.length; i++) { float X = random(border, width-border); float Y = random(border, height-border); float theX = random(border, width-border); float theY = random(border, height-border); float radius = random(15, 15); boolean loaded = false; int blocked = 0; int traegt = 50; color clr = color(120,100); Ameise[i] = new Animals(theX, theY, radius, loaded, blocked, traegt, clr); } } //hier beginnt die Definition des Objekts Ameise void gibFutter() { Futter = new Food[50]; int border = 50; for (int f=0; f<Futter.length; f++) { float xPos = random(border, width-border); float yPos = random(border, height-border); float radius = random(5, 5); int traeger = 500; color clr = color(50,200,0,120); Futter[f] = new Food(xPos, yPos, radius, traeger, clr); } } class Animals { PVector position; PVector direction; float spin = 0.10; float radius; boolean loaded; int blocked; int traegt; color clr; Animals (float theX, float theY, float aradius, boolean aloaded, int ablocked, int atraegt, color aclr) { position = new PVector (theX, theY); direction = new PVector (10,10); direction.x = random (-1, 1); direction.y = random (-1, 1); radius = aradius; loaded = aloaded; blocked = ablocked; traegt = atraegt; clr = aclr; } //Methode des Objekts Ameise void render() { fill(clr); //der Code ab hier dient dem Zeichnen der Ameise PVector normVector = new PVector(1,0); float angle = direction.heading2D(); pushMatrix(); translate(position.x,position.y); rotate(angle-PI); strokeWeight(2); line(0-radius/2,0-radius, 0+radius/2, 0+radius); line(0+radius/2,0-radius, 0-radius/2, 0+radius); line(0,0-radius, 0, 0+radius); ellipse(0,0,radius*0.5,radius*0.5); ellipse(0+radius-1,0,radius*1.5,radius); ellipse(0-radius*0.7,0,radius,radius); //ellipse(position.position.x, position.position.y, radius*2, radius*2); popMatrix(); } //Methode des Objekts Ameise void move() { direction.x += random (-spin, spin); direction.y += random (-spin, spin); direction.normalize (); position.add (direction); if (position.x < radius || position.x > (width-radius)) { direction.x *= -1; } if (position.y < radius || position.y > (height-radius)) { direction.y *= -1; } } } //hier beginnt die Definition des Objekts Futter class Food { float xPos; float yPos; float radius; int traeger; int frame=0; color clr; Food (float axPos, float ayPos, float aradius, int atraeger, color aclr) { xPos = axPos; yPos = ayPos; radius = aradius; traeger = atraeger; clr = aclr; } //Methode des Objekts Futter void render() { fill(clr); ellipse(xPos, yPos, radius*2, radius*2); } //Methode des Objekts Futter void dragged() { if (Ameise[traeger].blocked<1) { xPos=Ameise[traeger].position.x; yPos=Ameise[traeger].position.y; } } }
Aufgabe: Programmiere basierend auf dem Ameisen-Beispiel ein kleines Autorennspiel.
Vektoren
Ein PVector ist ein Objekt, das 2- oder 3-dimensionale Vektoren beschreibt. Ein Vektor hat eine bestimmte Richtung und eine bestimmte Länge. Man kann auch sagen, er erstreckt sich vom Punkt A zum Punkt B. In der Programmierung verwenden wir Vektoren, um Bewegung zu generieren.
Der Vorteil bei der Verwendung von Vektoren liegt daran, dass man schon mit 2 Vektor- Variablen die aktuelle Geschwindigkeit und Richtung eines Objektes beschreiben kann und mit einem zweiten die Änderung derselben. Diese beiden müssen dann nur noch in jedem Frame addiert werden.
Beispiel: starte Applet
//hier werden die Vektoren erstelltPVector direction = new PVector(1,1); PVector position = new PVector(20,20); int radius = 15; void setup() { size(500,500); } void draw() { //für den Schweif fill(255,20); rect(0,0,width,height); //damit sich überhaupt was bewegt position.add (direction); //die nächsten Zeile lassen die Ellipse zurückprallen if (position.x < radius || position.x > (width-radius)) { direction.x *= -1; } if (position.y < radius || position.y > (height-radius)) { direction.y *= -1; } //zeichnet die Ellipse fill(50); ellipse(position.x,position.y,radius*2,radius*2); }
Das Ganze kann dann noch gut mit Zufallszahlen kombiniert werden.
Methoden:
- vector.set(x,y,z) … legt die x-, y- und z-Werte des Vektors fest
- vector.get() … gibt die x-, y- und z-Werte des Vektors zurück
- vector.mag() … gibt die Länge des Vektors zurück
- vector1.add(vector2) … Addiert 2 Vektoren
- vector1.sub(vector2) … Subtrahiert 2 Vektoren
- vector.nomalize() … Ändert die Länge auf 1

- vector1.anglebetween(vector2) … gibt den Winkel zwischen 2 Vektoren zurück
- vector.heading2D() … gibt die Richtung des Vektors zurück
Aufgabe: Verändere das Programm so, dass der Kreis zufällig seine Richtung leicht variiert.
PVector – Einführung von Daniel Shiffman





































































Letzte Kommentare