Fortgeschrittene Techniken

Schleifen, Funktionen und Transformationen

Tutorial für kreative Programmierung mit p5.js

In diesem Tutorial lernst du fortgeschrittene Techniken, die dir ermöglichen, komplexere Grafiken zu erstellen:

Voraussetzungen: Du solltest die Grundlagen von p5.js kennen (Canvas, Formen, Farben, Variablen, Animationen).

Stell dir vor, du möchtest 10 Kreise nebeneinander zeichnen. Ohne Schleifen müsstest du 10 Mal circle() schreiben:

circle(50, 200, 30); circle(100, 200, 30); circle(150, 200, 30); // ... und so weiter - das ist mühsam!

Mit einer for-Schleife geht das viel einfacher:

function setup() { createCanvas(600, 400); } function draw() { background(240); fill(100, 150, 255); noStroke(); // Diese Schleife läuft von i=0 bis i=9 (also 10 Mal) for (let i = 0; i < 10; i = i + 1) { circle(50 + i * 55, 200, 40); } }

Eine for-Schleife besteht aus drei Teilen:

for (let i = 0; i < 10; i = i + 1) { // Code, der wiederholt wird }
  • let i = 0 - Start: Die Zählvariable beginnt bei 0
  • i < 10 - Bedingung: Solange i kleiner als 10 ist, weitermachen
  • i = i + 1 - Schritt: Nach jedem Durchlauf i um 1 erhöhen

Kurzschreibweise: Statt i = i + 1 kannst du auch i++ schreiben.

Experimentiere mit dem Code oben:

  • Ändere die Anzahl der Kreise (z.B. 20 statt 10)
  • Ändere den Abstand zwischen den Kreisen
  • Mache die Größe der Kreise abhängig von i, z.B. circle(..., ..., i * 5)

Möchtest du ein Raster oder Gitter zeichnen, brauchst du eine Schleife in einer Schleife - eine verschachtelte Schleife:

function setup() { createCanvas(600, 400); } function draw() { background(240); // Äußere Schleife: Zeilen (y-Richtung) for (let y = 0; y < 8; y++) { // Innere Schleife: Spalten (x-Richtung) for (let x = 0; x < 12; x++) { fill(x * 20, y * 30, 150); circle(50 + x * 45, 50 + y * 45, 30); } } } Die äußere Schleife durchläuft die Zeilen, die innere Schleife die Spalten. Für jede Zeile werden alle Spalten gezeichnet, bevor zur nächsten Zeile gewechselt wird.

Mit verschachtelten Schleifen lassen sich interessante Muster erzeugen:

function setup() { createCanvas(600, 400); noStroke(); } function draw() { background(240); let groesse = 50; for (let y = 0; y < 8; y++) { for (let x = 0; x < 12; x++) { // Abwechselnde Farben durch (x + y) % 2 if ((x + y) % 2 == 0) { fill(50); // Dunkel } else { fill(200); // Hell } rect(x * groesse, y * groesse, groesse, groesse); } } }

Erstelle ein eigenes Muster mit verschachtelten Schleifen. Ideen:

  • Punkte, deren Größe von der Position abhängt
  • Ein Farbverlauf über das Raster
  • Kreise und Quadrate im Wechsel

Im Schachbrett-Beispiel oben hast du bereits (x + y) % 2 gesehen. Der Modulo-Operator % ist sehr nützlich für wiederholende Muster und Animationen.

Der Modulo-Operator % berechnet den Rest einer Division:

  • 5 % 2 = 1 (5 geteilt durch 2 = 2 Rest 1)
  • 6 % 2 = 0 (6 geteilt durch 2 = 3 Rest 0)
  • 10 % 3 = 1 (10 geteilt durch 3 = 3 Rest 1)
  • 7 % 7 = 0 (7 geteilt durch 7 = 1 Rest 0)

Wichtig: Das Ergebnis ist immer kleiner als der Teiler!

Modulo für wiederholende Animationen

Da frameCount immer größer wird, können wir mit Modulo einen Wert erzeugen, der sich immer wiederholt:

// frameCount % 600 ergibt immer einen Wert zwischen 0 und 599 wolkeX = frameCount % 600;

Wenn frameCount den Wert 600 erreicht, springt das Ergebnis automatisch auf 0 zurück:

function setup() { createCanvas(600, 400); } function draw() { background("skyblue"); // Modulo sorgt dafür, dass wolkeX immer zwischen 0 und 599 bleibt let wolkeX = frameCount % 600; // Wolke zeichnen fill(255); noStroke(); ellipse(wolkeX, 100, 70, 40); // Anzeige der Werte fill(0); text("frameCount: " + frameCount, 10, 20); text("wolkeX (frameCount % 600): " + wolkeX, 10, 40); }

Statt dieser if-Bedingung:

wolkeX = wolkeX + 1; if (wolkeX > 600) { wolkeX = 0; }

reicht mit Modulo eine einzige Zeile:

wolkeX = frameCount % 600;

Geschwindigkeit anpassen

Du kannst die Geschwindigkeit der Animation anpassen, indem du frameCount multiplizierst:

function setup() { createCanvas(600, 400); } function draw() { background("skyblue"); // Langsame Wolke (frameCount wird halbiert) let wolke1 = (frameCount * 0.5) % 600; // Normale Wolke let wolke2 = frameCount % 600; // Schnelle Wolke (frameCount wird verdoppelt) let wolke3 = (frameCount * 2) % 600; // Wolken zeichnen fill(255); noStroke(); ellipse(wolke1, 80, 70, 40); // langsam ellipse(wolke2, 160, 70, 40); // normal ellipse(wolke3, 240, 70, 40); // schnell // Beschriftung fill(0); text("langsam (×0.5)", 10, 85); text("normal (×1)", 10, 165); text("schnell (×2)", 10, 245); }

Blinkende Objekte

Modulo eignet sich auch für blinkende oder pulsierende Effekte:

function setup() { createCanvas(600, 400); } function draw() { background(50); // frameCount % 60 ergibt Werte von 0-59 // Wenn der Wert kleiner als 30 ist: Licht an // Wenn der Wert 30 oder größer ist: Licht aus if (frameCount % 60 < 30) { fill(255, 255, 0); // Gelb (an) } else { fill(100, 100, 50); // Dunkelgelb (aus) } // Lampe zeichnen circle(300, 200, 100); // Anzeige fill(255); text("frameCount % 60 = " + (frameCount % 60), 10, 20); }
  • Endlos-Animationen: x = frameCount % breite
  • Blinken: if (frameCount % 60 < 30)
  • Abwechselnde Farben: if (i % 2 == 0)
  • Alle X Frames etwas tun: if (frameCount % 10 == 0)

Experimentiere mit dem Modulo-Operator:

  • Erstelle eine Wolke, die von rechts nach links wandert (Tipp: 600 - frameCount % 600)
  • Erstelle ein Objekt, das zwischen zwei Positionen hin- und herspringt
  • Erstelle eine Animation, bei der alle 2 Sekunden (120 Frames) etwas passiert

Wenn du einen bestimmten Code-Abschnitt mehrfach verwenden möchtest, kannst du eine eigene Funktion erstellen. Das macht deinen Code übersichtlicher und spart Wiederholungen.

Beispiel: Eine Funktion, die einen einfachen Baum zeichnet:

function setup() { createCanvas(600, 400); } function draw() { background(135, 206, 235); // Himmel // Wiese fill(34, 139, 34); rect(0, 300, 600, 100); // Baum zeichnen durch Aufruf unserer Funktion zeichneBaum(); } // Eigene Funktion: zeichnet einen Baum function zeichneBaum() { // Stamm fill(139, 69, 19); rect(280, 220, 40, 80); // Krone fill(34, 139, 34); ellipse(300, 180, 120, 100); } function funktionsName() { // Anweisungen, die ausgeführt werden sollen }

Die Funktion wird durch ihren Namen aufgerufen: funktionsName();

Funktionsnamen sollten beschreiben, was die Funktion tut (z.B. zeichneBaum, bewegeWolke).

Das Problem: Unser Baum steht immer an der gleichen Stelle. Um mehrere Bäume an verschiedenen Positionen zu zeichnen, brauchen wir Parameter.

Mit Parametern kannst du Werte an eine Funktion übergeben. So wird die Funktion flexibler:

function setup() { createCanvas(600, 400); } function draw() { background(135, 206, 235); // Wiese fill(34, 139, 34); rect(0, 300, 600, 100); // Mehrere Bäume an verschiedenen Positionen zeichneBaum(100, 300); zeichneBaum(300, 300); zeichneBaum(500, 300); } // Funktion mit Parametern für die Position function zeichneBaum(x, y) { // Stamm (Position relativ zu x, y) fill(139, 69, 19); rect(x - 20, y - 80, 40, 80); // Krone fill(34, 139, 34); ellipse(x, y - 120, 120, 100); }

Du kannst beliebig viele Parameter hinzufügen, zum Beispiel für die Größe:

function setup() { createCanvas(600, 400); } function draw() { background(135, 206, 235); // Wiese fill(34, 139, 34); rect(0, 300, 600, 100); // Bäume in verschiedenen Größen zeichneBaum(80, 300, 0.5); // Klein zeichneBaum(200, 300, 1.0); // Normal zeichneBaum(350, 300, 1.5); // Groß zeichneBaum(500, 300, 0.8); // Mittel } function zeichneBaum(x, y, groesse) { // Stamm fill(139, 69, 19); rect(x - 20 * groesse, y - 80 * groesse, 40 * groesse, 80 * groesse); // Krone fill(34, 139, 34); ellipse(x, y - 120 * groesse, 120 * groesse, 100 * groesse); }

Kombiniere Schleifen und Funktionen:

  1. Verwende eine for-Schleife, um viele Bäume zu zeichnen
  2. Variiere Position und Größe mit random()
  3. Bonus: Füge einen Parameter für die Farbe der Krone hinzu

Bisher haben wir Positionen direkt in den Zeichenbefehlen angegeben. Mit translate() können wir stattdessen den Ursprung verschieben.

Der Ursprung (0, 0) liegt normalerweise oben links. Mit translate(x, y) verschieben wir ihn:

function setup() { createCanvas(600, 400); } function draw() { background(240); // Ursprung in die Mitte verschieben translate(300, 200); // Jetzt ist (0, 0) in der Mitte! fill(255, 100, 100); circle(0, 0, 100); // Kreis in der "Mitte" // Weitere Kreise relativ zur neuen Mitte fill(100, 255, 100); circle(80, 0, 50); circle(-80, 0, 50); circle(0, 80, 50); circle(0, -80, 50); } translate() ist besonders nützlich, wenn du komplexe Objekte zeichnest, die du als Ganzes verschieben möchtest.

Ein Problem mit translate(): Die Verschiebung bleibt bestehen und addiert sich!

Mit push() und pop() kannst du Transformationen isolieren:

function setup() { createCanvas(600, 400); } function draw() { background(240); // Erstes Objekt push(); translate(150, 200); fill(255, 100, 100); rect(-40, -40, 80, 80); pop(); // Zurück zum ursprünglichen Zustand // Zweites Objekt - unabhängig vom ersten push(); translate(300, 200); fill(100, 255, 100); ellipse(0, 0, 100, 60); pop(); // Drittes Objekt push(); translate(450, 200); fill(100, 100, 255); triangle(-40, 40, 40, 40, 0, -40); pop(); }

Verwende push() und pop() immer dann, wenn du:

  • Transformationen nur auf bestimmte Objekte anwenden möchtest
  • Mehrere unabhängige Objekte zeichnest
  • In Schleifen Objekte mit verschiedenen Transformationen zeichnest

Neben translate() gibt es weitere Transformationen:

In p5.js werden Winkel standardmäßig in Radiant angegeben:

  • Ein voller Kreis = 2π (ca. 6.28)
  • Ein halber Kreis = π (ca. 3.14)
  • Ein Viertelkreis = π/2 (ca. 1.57)

Für Grad verwende radians(grad), z.B. rotate(radians(45)) für 45°.

function setup() { createCanvas(600, 400); } function draw() { background(135, 206, 235); // Ursprung zur Sonnenposition verschieben translate(300, 200); // Langsam rotieren (frameCount für Animation) rotate(frameCount * 0.01); // Sonnenstrahlen zeichnen stroke(255, 200, 0); strokeWeight(4); for (let i = 0; i < 12; i++) { line(0, 60, 0, 100); rotate(radians(30)); // 360° / 12 = 30° pro Strahl } // Sonnenkörper (ohne Rotation) noStroke(); fill(255, 220, 0); circle(0, 0, 100); }

Mit scale() kannst du Objekte vergrößern oder verkleinern:

function setup() { createCanvas(600, 400); } function draw() { background(240); // Original-Größe push(); translate(100, 200); fill(255, 100, 100); zeichneBlume(); pop(); // 1.5-fach vergrößert push(); translate(300, 200); scale(1.5); fill(100, 255, 100); zeichneBlume(); pop(); // 0.5-fach (halb so groß) push(); translate(500, 200); scale(0.5); fill(100, 100, 255); zeichneBlume(); pop(); } function zeichneBlume() { // Blütenblätter for (let i = 0; i < 6; i++) { ellipse(0, -30, 25, 50); rotate(radians(60)); } // Mitte fill(255, 255, 0); circle(0, 0, 30); }

Erstelle eine Animation mit Transformationen:

  • Ein Windrad, das sich dreht
  • Ein Objekt, das größer und kleiner wird (pulsiert)
  • Planeten, die um eine Sonne kreisen

Jetzt wenden wir alle gelernten Techniken an! Die folgenden Beispiele kombinieren Schleifen, eigene Funktionen und Transformationen zu beeindruckenden Ergebnissen.

Beispiel 1 - Spirograph

function setup() { createCanvas(600, 400); } function draw() { background(20, 20, 40); // Ursprung in die Mitte translate(300, 200); // Äußerer Ring aus Kreisen for (let i = 0; i < 36; i++) { push(); rotate(radians(i * 10 + frameCount * 0.5)); // Farbe wechselt mit Position let farbe = map(i, 0, 36, 0, 255); stroke(farbe, 100, 255 - farbe); strokeWeight(2); noFill(); // Innerer Ring for (let j = 0; j < 6; j++) { let radius = 50 + j * 25; ellipse(radius, 0, 30, 15); } pop(); } }

Beispiel 2 - Animierter Wald

function setup() { createCanvas(600, 400); } function draw() { // Himmel mit Farbverlauf-Effekt background(135, 180, 220); // Sonne fill(255, 220, 100); noStroke(); circle(500, 80, 60); // Boden fill(80, 140, 60); rect(0, 300, 600, 100); // Hintere Baumreihe (kleiner, dunkler) for (let i = 0; i < 8; i++) { zeichneBaum(50 + i * 80, 310, 0.6, color(30, 90, 30)); } // Vordere Baumreihe (größer, heller) for (let i = 0; i < 6; i++) { zeichneBaum(80 + i * 100, 350, 1.0, color(50, 130, 50)); } // Grashalme im Vordergrund stroke(60, 120, 40); strokeWeight(2); for (let i = 0; i < 60; i++) { let x = i * 10 + 5; let schwingung = sin(frameCount * 0.05 + i * 0.3) * 5; line(x, 400, x + schwingung, 380); } } function zeichneBaum(x, y, groesse, farbe) { // Wind-Animation let wind = sin(frameCount * 0.03 + x * 0.01) * 3 * groesse; push(); translate(x, y); scale(groesse); // Stamm fill(100, 70, 40); noStroke(); rect(-10, -80, 20, 80); // Krone (mehrere Dreiecke) fill(farbe); triangle(-50 + wind, -80, 0 + wind, -180, 50 + wind, -80); triangle(-40 + wind * 0.8, -130, 0 + wind * 0.8, -210, 40 + wind * 0.8, -130); triangle(-30 + wind * 0.6, -170, 0 + wind * 0.6, -230, 30 + wind * 0.6, -170); pop(); }

Beispiel 3 - Planetensystem

function setup() { createCanvas(600, 400); } function draw() { background(10, 10, 30); // Sterne im Hintergrund fill(255); noStroke(); for (let i = 0; i < 50; i++) { // Pseudo-Zufall basierend auf Index (bleibt gleich) let x = (i * 127) % 600; let y = (i * 89) % 400; circle(x, y, 2); } // Ursprung in die Mitte translate(300, 200); // Sonne fill(255, 200, 50); circle(0, 0, 60); // Planet 1 - Merkur (schnell, nah) zeichnePlanet(50, frameCount * 3, 10, color(180, 180, 180)); // Planet 2 - Venus (mittel) zeichnePlanet(90, frameCount * 2, 15, color(230, 180, 100)); // Planet 3 - Erde mit Mond push(); rotate(radians(frameCount * 1.5)); translate(140, 0); fill(100, 150, 255); circle(0, 0, 20); // Mond der Erde rotate(radians(frameCount * 5)); fill(200); circle(25, 0, 6); pop(); // Planet 4 - Mars zeichnePlanet(190, frameCount * 1, 12, color(200, 100, 80)); // Planet 5 - Jupiter (groß, langsam) zeichnePlanet(260, frameCount * 0.5, 35, color(220, 180, 130)); } function zeichnePlanet(abstand, winkel, groesse, farbe) { push(); rotate(radians(winkel)); translate(abstand, 0); fill(farbe); noStroke(); circle(0, 0, groesse); pop(); }

Beispiel 4 - Generatives Muster

function setup() { createCanvas(600, 400); } function draw() { background(240); translate(300, 200); // Mehrere Ringe mit Quadraten for (let ring = 1; ring <= 5; ring++) { let anzahl = ring * 8; let radius = ring * 35; for (let i = 0; i < anzahl; i++) { push(); // Position im Ring let winkel = (360 / anzahl) * i + frameCount * (0.5 / ring); rotate(radians(winkel)); translate(radius, 0); // Eigene Rotation des Quadrats rotate(radians(frameCount * 2 * ring)); // Farbe basierend auf Ring und Position let r = map(ring, 1, 5, 50, 255); let g = map(i, 0, anzahl, 100, 200); let b = 200; fill(r, g, b, 180); stroke(r - 50, g - 50, b - 50); strokeWeight(1); let groesse = map(ring, 1, 5, 15, 8); rectMode(CENTER); rect(0, 0, groesse, groesse); pop(); } } }

Beispiel 5 - Feuerwerk

let funken = []; function setup() { createCanvas(600, 400); } function draw() { // Halbtransparenter Hintergrund für Nachleuchten background(20, 20, 40, 50); // Alle Funken zeichnen und aktualisieren for (let i = funken.length - 1; i >= 0; i--) { let f = funken[i]; // Funke zeichnen push(); translate(f.x, f.y); rotate(radians(f.winkel)); fill(f.r, f.g, f.b, f.alpha); noStroke(); ellipse(0, 0, f.groesse, f.groesse * 2); pop(); // Funke bewegen f.x = f.x + cos(radians(f.winkel)) * f.speed; f.y = f.y + sin(radians(f.winkel)) * f.speed; f.y = f.y + 0.5; // Schwerkraft f.alpha = f.alpha - 3; f.groesse = f.groesse * 0.98; // Verblasste Funken entfernen if (f.alpha <= 0) { funken.splice(i, 1); } } // Anleitung fill(255); textAlign(CENTER); text("Klicke für Feuerwerk!", 300, 380); } function mousePressed() { // Explosion erstellen let anzahl = 50; let basisFarbe = random(255); for (let i = 0; i < anzahl; i++) { funken.push({ x: mouseX, y: mouseY, winkel: random(360), speed: random(2, 8), groesse: random(5, 15), r: basisFarbe + random(-50, 50), g: random(100, 255), b: 255 - basisFarbe + random(-50, 50), alpha: 255 }); } }

Beispiel 6 - Kaleidoskop

function setup() { createCanvas(600, 400); } function draw() { background(10); translate(300, 200); // 8 gespiegelte Segmente for (let i = 0; i < 8; i++) { push(); rotate(radians(i * 45)); // Spiegelung bei jedem zweiten Segment if (i % 2 == 1) { scale(-1, 1); } // Muster basierend auf Mausposition zeichneMuster(); pop(); } } function zeichneMuster() { // Position relativ zur Maus let mx = mouseX - 300; let my = mouseY - 200; noStroke(); // Mehrere Formen basierend auf Mausposition for (let j = 0; j < 5; j++) { let abstand = 30 + j * 30; let winkel = frameCount * 0.02 + j * 0.5; let x = cos(winkel) * abstand + mx * 0.1; let y = sin(winkel) * abstand * 0.5; // Farbe basierend auf Position fill( 150 + sin(frameCount * 0.05 + j) * 100, 100 + j * 30, 200, 200 ); ellipse(x, y, 15 + j * 3, 15 + j * 3); } // Linien zur Mitte stroke(255, 100); strokeWeight(1); line(0, 0, mx * 0.3, my * 0.3); }

Diese Beispiele zeigen, wie du die gelernten Konzepte kombinieren kannst:

  • Schleifen: Für wiederholte Muster und viele Objekte
  • Verschachtelte Schleifen: Für komplexe Raster und Ringe
  • Eigene Funktionen: Für wiederverwendbare Objekte (Bäume, Planeten)
  • translate() + rotate(): Für Kreisbewegungen und Symmetrien
  • push() / pop(): Für isolierte Transformationen
  • frameCount: Für kontinuierliche Animationen

Tipp: Experimentiere mit den Zahlen! Kleine Änderungen können große visuelle Effekte haben.

Erstelle dein eigenes generatives Kunstwerk! Ideen:

  • Ein animiertes Mandala mit rotierenden Elementen
  • Eine Unterwasserszene mit Fischen (eigene Funktion für Fische)
  • Ein Sternenhimmel mit Sternschnuppen
  • Geometrische Muster, die auf die Maus reagieren

Du hast wichtige fortgeschrittene Techniken gelernt:

Nächste Schritte

p5.js ist eine freie und quelloffene JavaScript-Bibliothek für kreative Programmierung