Schnellstart mit der Zeichenmaschine¶
Um die Zeichenmaschine in einem Projekt zu nutzen ist nicht mehr nötig, als die JAR-Datei der aktuellen Version herunterzuladen und dem Classpath hinzuzufügen. Eine Beschreibung für verschiedene Entwicklungsumgebungen findet sich im Abschnitt Installation.
Die Basisklasse¶
Eine Zeichenmaschine wird immer als Unterklasse von Zeichenmaschine
erstellt.
Die gezeigte Klasse ist schon eine lauffähige Zeichenmaschine und kann gestartet werden.
main Methode
Bei einigen Entwicklungsumgebungen muss noch eine main
Methode erstellt
werden, um die Zeichenmaschine zu starten:
Es öffnet sich ein Zeichenfenster in einer vordefinierten Größe. Um die Abmessungen und den Titel des Fensters zu ändern, legen wir einen Konstruktor an.
Quelltext
Starten wir das Projekt, wird eine Zeichenfläche in der Größe 800-mal-800 Pixel erstellt und in einem Fenster mit dem Titel „Shapes“ angezeigt.
Formen zeichnen¶
Eine Zeichenmaschine hat verschiedene Möglichkeiten, Inhalte in das
Zeichenfenster zu zeichnen. Um ein einfaches statisches Bild zu erzeugen,
überschreiben wir die draw()
Methode.
Quelltext
Wir sehen einen gelben Kreis (ohne Konturlinie) auf einem blauen Hintergrund.
Vorbereitung der Zeichenfläche¶
Im Beispiel oben setzen wir die Hintergrundfarbe auf Blau, die Füllfarbe auf
Gelb und deaktivieren die Konturlinie. Wenn diese Einstellungen für alle
Zeichenobjekte gleich bleiben, können wir sie statt in draw()
auch in die setup()
Methode schreiben. Diese bereitet
die Zeichenfläche vor dem ersten Zeichnen vor.
Quelltext
public class Shapes extends Zeichenmaschine {
public Shapes() {
super(800, 800, "Shapes");
}
@Override
public void setup() {
background.setColor(BLUE);
drawing.setFillColor(255, 223, 34);
drawing.noStroke();
}
@Override
public void draw() {
for( int i = 0; i < 10; i++ ) {
drawing.circle(
random(0, canvasWidth),
random(0, canvasHeight),
random(50, 200)
);
}
}
}
Im Beispiel setzen wir nun die Grundeinstellungen in der setup()
Methode. In
draw()
werden zehn gelbe Kreise an Zufallskoordinaten gezeichnet.
Mit canvasWidth
und
canvasHeight
kannst du in der Zeichenmaschine
auf die aktuelle Größe der Zeichenfläche zugreifen.
random(int, int)
erzeugt eine Zufallszahl
innerhalb der angegebenen Grenzen.
Interaktionen mit der Maus: Whack-a-mole¶
Mit der Zeichenmaschine lassen sich Interaktionen mit der Maus leicht umsetzen. Wor wollen das Beispielprogramm zu einem Whac-A-Mole Spiel erweitern.
Auf der Zeichenfläche wird nur noch ein gelber Kreis an einer zufälligen Stelle angezeigt. Sobald die Spieler:in auf den Kreis klickt, soll dieser an eine neue Position springen.
Damit wir den Kreis an eine neue Position springen lassen können, müssen wir
zufällige x
- und y
-Koordinaten generieren. Dazu erstellen wir zunächst zwei
Objektvariablen für die Koordinaten, die in der setup()
Methode mit
zufälligen Werte initialisiert werden. Diese benutzen wir, um die draw
()
Methode anzupassen.
Quelltext
public class Shapes extends Zeichenmaschine {
private int moleRadius = 20;
private int moleX;
private int moleY;
public Shapes() {
super(800, 800, "Shapes");
}
@Override
public void setup() {
background.setColor(BLUE);
drawing.setFillColor(255, 223, 34);
drawing.noStroke();
moleX = random(50, canvasWidth - 50);
moleY = random(50, canvasHeight - 50);
}
@Override
public void draw() {
drawing.clear();
drawing.circle(moleX, moleY, moleRadius);
}
}
Als Nächstes prüfen wir bei jedem Mausklick, ob die Mauskoordinaten innerhalb
des gelben Kreises (des Maulwurfs) liegen. Die Mauskoordinaten sind jederzeit
über die Variablen mouseX
und mouseY
abrufbar. Um zu prüfen, ob diese
Koordinaten innerhalb des Kreises liegen, vergleichen wir den Abstand zwischen
Kreismittelpunkt (moleX, moleY)
und den Mauskoordinaten
(mouseX, mouseY)
mit dem Radius des Kreises (im Bild grün). Ist die Entfernung
kleiner als der Radius (blauer Kreis), wurde innerhalb des Kreises geklickt.
Sonst außerhalb (roter Kreis).
Den Abstand vom Mittelpunkt zur Maus lässt sich mithilfe des Satzes des
Pythagoras leicht selber berechnen. Die Zeichenmaschine kann uns diese Arbeit
aber auch abnehmen und stellt eine Methode dafür bereit
(distance(double, double, double, double)
).
Um auf einen Mausklick zu reagieren, ergänzen wir die
mouseClicked()
Methode:
@Override
public void mouseClicked() {
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
moleX = random(50, canvasWidth - 50);
moleY = random(50, canvasHeight - 50);
redraw();
}
}
Quelltext
public class Shapes extends Zeichenmaschine {
private int moleRadius = 20;
private int moleX;
private int moleY;
public Shapes() {
super(800, 800, "Shapes");
}
@Override
public void setup() {
background.setColor(BLUE);
drawing.setFillColor(255, 223, 34);
drawing.noStroke();
moleX = random(50, canvasWidth - 50);
moleY = random(50, canvasHeight - 50);
}
@Override
public void draw() {
drawing.clear();
drawing.circle(moleX, moleY, moleRadius);
}
@Override
public void mouseClicked() {
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
moleX = random(50, canvasWidth - 50);
moleY = random(50, canvasHeight - 50);
redraw();
}
}
}
Der Aufruf von redraw()
zeichnet
die Zeichenfläche neu, indem die draw()
Methode erneut aufgerufen wird.
Du solltest draw()
niemals direkt aufrufen.
Nun springt der Kreis an eine andere Stelle, wenn er direkt mit der Maus angeklickt wird.
Ein paar Details zur Zeichenmaschine¶
Die Zeichenmaschine wurde stark von der kreativen Programmierumgebung Processing inspiriert. Wenn Du Processing schon kennst, dann werden Dir einige der Konzepte der Zeichenmaschine schon bekannt vorkommen.
Farben¶
Farben können auf verschiedene Weisen angegeben werden. Unser Beispiel nutzt bisher zwei Arten:
- Die einfachste Möglichkeit sind die Farbkonstanten
wie
BLUE
oderRED
. Im Beispiel setzen wir den Hintergrund auf die FarbeBLUE
. - Farben werden häufig im RGB-Farbraum definiert. Dazu wird jeweils der Rot-,
Grün- und Blauanteil der Farbe als Wert zwischen 0 und 255 angegeben. Im
Beispiel setzen wir die Farbe der Kreise auf
255, 223, 34
, also viel Rot und Grün und nur ein wenig Blau.
Ebenen¶
Die Zeichenfläche besteht aus einzelnen Ebenen
, die
auf übereinander liegen. Bis auf die unterste Ebene sind die Ebenen zunächst
durchsichtig, wodurch die Zeichnungen unterer Ebenen durchscheinen.
Eine Zeichenmaschine besitzt zu Beginn drei Ebenen:
- Die unterste Ebene ist ein
ColorLayer
, die nur aus einer Farbe (oder einem Farbverlauf) besteht und keine durchsichtigen Bereiche besitzt. Im Beispiel setzen wir diese Ebene auf die FarbeBLUE
. - Die nächste Ebene ist ein
DrawingLayer
, auf die wir unsere Formen zeichnen können. Die Ebene ist zunächst komplett durchsichtig. - Die oberste Ebene ist ein
ShapesLayer
, die zur Darstellung von Form-Objekten der KlasseShape
genutzt werden kann.
Du kannst einer Zeichenfläche aber auch beliebige neue oder selbst programmierte Ebenen hinzufügen.
Ablauf¶
Die Zeichenmaschine ruft nach dem Start die Methoden in einem festen Ablauf auf.
Erstellst Du eine Zeichenmaschine (beziehungsweise ein Objekt einer
Unterklasse), dann wird zuerst die setup()
Methode ausgeführt. Danach folgt einmalig die
draw()
Methode und dann endet das Hauptprogramm.
Die Eingaben der Maus werden in einem parallelen Ablauf (einem Thread)
abgefangen und daraufhin die mouseClicked()
Methode aufgerufen. In unserem Programm prüfen wir, ob mit der Maus
innerhalb des Kreises geklickt wurde und rufen dann
redraw()
auf, woraufhin ein weiteres Mal
draw()
ausgeführt wird.
In Processing wird dies der "statische" Modus genannt, weil das Programm nur
einmal abläuft und dann stoppt. "Statisch" trifft es nicht ganz, da das Programm
ja zum Beispiel durch Mauseingaben auch verändert werden kann. Wichtig ist aber,
dass mit redraw()
die Zeichenfläche manuell neu gezeichnet werden muss, damit
sich der Inhalt ändert.
Dynamische Programme¶
Wir wollen unser kleines Spiel dynamischer machen, indem die Kreise nur drei Sekunden angezeigt werden und dann von selbst an einen neuen Ort springen. Schafft man es, den Kreis in dieser Zeit anzuklicken, bekommt man einen Punkt.
Als zusätzliche Herausforderung lassen wir jeden Kreis in den drei Sekunden immer kleiner werden.
Die Update-Draw-Schleifen¶
Bisher hat die Zeichenmaschine einmalig draw()
aufgerufen und dann nur noch
auf Benutzereingaben mit der Maus reagiert. Nun wollen wir das gezeichnete Bild
aber laufend anpassen. Der Kreis soll schrumpfen und nach 3 Sekunden
verschwinden.
Dazu ergänzen wir ein update(double)
Methode in unserem Programm. Nun schaltet die Zeichenmaschine in einen
dynamischen Modus und startet die Update-Draw-Schleife. Das beduetet, nach
Aufruf von setup()
wird fortlaufend immer wieder zuerst update()
und dann
draw()
aufgerufen.
Jeder Aufruf der draw()
Methode zeichnet nun die Zeichenfläche neu. Jedes
Bild, das gezeichnet wird (auch, wenn es genauso aussieht, wie das davor), nennt
man ein Frame. Von Videospielen kennst Du vielleicht schon den Begriff "
Frames per second" (Fps, dt. Bilder pro Sekunde). Er bedeutet, wie oft das
Spiel neue Frames zeichnet, oder in der Zeichenmaschine, wie oft draw()
pro
Sekunde aufgerufen wird. Normalerweise passiert dies genau 60-mal pro Sekunde.
Lebenszeit eines Kreises¶
Jeder Kreis soll drei Sekunden zu sehen sein. Daher fügen wir eine neue
Objektvariable namens moleTime
ein, die zunächst auf drei steht. Da wir auch
Bruchteile von Skeunden abziehen wollen, wählen wir als Datentyp double
:
Der Parameter delta
, der update()
Methode ist der Zeitraum in Sekunden, seit
dem letzten Frame. Subtrahieren wir diesen Wert bei jedem Frame von moleTime
,
wird der Wert immer kleiner. Dann müssen wir nur noch prüfen, ob er kleiner Null
ist und in dem Fall den Kreis auf eine neue Position springen lassen.
Die Größe des Kreises passen wir so an, dass der Anteil der vergangenen Zeit die Größe des Kreises bestimmt:
Quelltext
import schule.ngb.zm.Zeichenmaschine;
public class Shapes extends Zeichenmaschine {
private int moleRadius = 20;
private int moleX;
private int moleY;
private double moleTime;
public Shapes() {
super(800, 800, "Shapes");
}
@Override
public void setup() {
background.setColor(BLUE);
drawing.setFillColor(255, 223, 34);
drawing.noStroke();
randomizeMole();
}
private void randomizeMole() {
moleX = random(moleRadius*2, canvasWidth - moleRadius*2);
moleY = random(moleRadius*2, canvasHeight - moleRadius*2);
moleTime = 3.0;
}
@Override
public void update( double delta ) {
moleTime -= delta;
if( moleTime <= 0 ) {
randomizeMole();
}
}
@Override
public void draw() {
drawing.clear();
drawing.circle(moleX, moleY, moleRadius * (moleTime / 3.0));
}
@Override
public void mouseClicked() {
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
randomizeMole();
redraw();
}
}
public static void main( String[] args ) {
new Shapes();
}
}
Der Maulwurf muss mittlerweile an drei verschiedenen Stellen im Programm
auf eine zufällige Position gesetzt werden (am Anfang, wenn er angeklickt
wurde und wenn die drei Sekunden abgelaufen sind). Daher wurde das Versetzen
in eine eigene Methode randomizeMole()
ausgelagert.
Punktezähler¶
Zum Schluss wollen wir noch bei jedem Treffer mit der Maus die Punkte Zählen und als Text auf die Zeichenfläche schreiben.
Dazu ergänzen wir eine weitere Objektvariable score
, die in mouseClicked()
erhöht wird, falls der Kreis getroffen wurde.
Um den Punktestand anzuzeigen ergänzen wir in draw()
einen Aufruf von
DrawingLayer.text(String, double, double, Direction)
mit dem Inhalt von score
und den Koordinaten, an denen der Text gezeigt
werden soll.
Die Zeichenebene zeichnet im Moment alle Formen und Text ausgehend vom Zentrum
der Form. Damit der Text 10 Pixel vom Rand entfernt links oben angezeigt wird,
können wir der Text Methode (und allen anderen, die etwas zeichnen) eine Richtung
übergeben, die festlegt, von
welchem Ausgangspunkt (oder Ankerpunkt) die Form gezeichnet werden soll.
Quelltext
import schule.ngb.zm.Zeichenmaschine;
public class Shapes extends Zeichenmaschine {
private int moleRadius = 20;
private int moleX;
private int moleY;
private double moleTime;
private int score = 0;
public Shapes() {
super(800, 800, "Shapes");
}
@Override
public void setup() {
background.setColor(BLUE);
drawing.noStroke();
drawing.setFontSize(24);
randomizeMole();
}
private void randomizeMole() {
moleX = random(moleRadius*2, canvasWidth - moleRadius*2);
moleY = random(moleRadius*2, canvasHeight - moleRadius*2);
moleTime = 3.0;
}
@Override
public void update( double delta ) {
moleTime -= delta;
if( moleTime <= 0 ) {
randomizeMole();
}
}
@Override
public void draw() {
drawing.clear();
drawing.setFillColor(255, 223, 34);
drawing.circle(moleX, moleY, moleRadius * (moleTime / 3.0));
drawing.setFillColor(BLACK);
drawing.text("Punkte: " + score, 10, 10, NORTHWEST);
}
@Override
public void mouseClicked() {
if( distance(moleX, moleY, mouseX, mouseY) < moleRadius ) {
score += 1;
randomizeMole();
redraw();
}
}
public static void main( String[] args ) {
new Shapes();
}
}
Wie es weitergehen kann¶
In diesem Schnellstart-Tutorial hast Du die Grundlagen der Zeichenmaschine gelernt. Um weiterzumachen, kannst Du versuchen, das Whack-a-mole Spiel um diese Funktionen zu erweitern:
- Mehrere "Maulwürfe" gleichzeitig.
- Unterschiedliche Zeiten pro Maulwurf.
Wenn Du mehr über die Möglichkeiten lernen möchtest, die Dir die Zeichenmaschine bereitstellt, kannst Du Dir die weiteren Tutorials in dieser Dokumentation ansehen. Ein guter Startpunkt ist das Aquarium.
Viele verschiedene Beispiele, ohne detailliertes Tutorial, findest Du in der Kategorie Beispiele und auf GitHub im Repository jneug/zeichenmaschine-examples.