In diesem Tutorial lernst du fortgeschrittene Techniken, die dir ermöglichen, komplexere Grafiken zu erstellen:
- Mit Schleifen Muster und Raster erzeugen
- Den Modulo-Operator für wiederholende Muster nutzen
- Eigene Funktionen definieren und Code wiederverwenden
- Mit Transformationen Objekte verschieben, drehen und skalieren
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:
598 % 600 = 598
599 % 600 = 599
600 % 600 = 0 (springt zurück!)
601 % 600 = 1
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:
- Verwende eine for-Schleife, um viele Bäume zu zeichnen
- Variiere Position und Größe mit
random()
- 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:
push() - speichert den aktuellen Zustand (Position, Farben, etc.)
pop() - stellt den gespeicherten Zustand wieder her
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:
rotate(winkel) - dreht um den Ursprung
scale(faktor) - vergrößert oder verkleinert
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:
- ✓ for-Schleifen für wiederholte Zeichenbefehle
- ✓ Verschachtelte Schleifen für Raster und Muster
- ✓ Eigene Funktionen für wiederverwendbaren Code
- ✓ Parameter für flexible Funktionen
- ✓ translate() zum Verschieben des Ursprungs
- ✓ push() / pop() zum Isolieren von Transformationen
- ✓ rotate() / scale() zum Drehen und Skalieren
Nächste Schritte
- Kombiniere diese Techniken zu komplexeren Projekten
- Experimentiere mit verschiedenen Mustern und Animationen
- Erstelle generative Kunst mit Schleifen und Transformationen
- Lerne mehr über Arrays für viele bewegte Objekte
p5.js ist eine freie und quelloffene JavaScript-Bibliothek für kreative Programmierung