Ball-Spiele

http://sites.prenninger.com/arduino-uno-r3/ball-spiele

http://www.linksammlung.info/

http://www.schaltungen.at/

                                                                                             Wels, am 2015-01-20

BITTE nützen Sie doch rechts OBEN das Suchfeld  [                                                              ] [ Diese Site durchsuchen]

DIN A3 oder DIN A4 quer ausdrucken
**********************************************************************************
DIN A4  ausdrucken
*********************************************************

                Einfaches Ball-Spiel Programmieren

game_controlpanel_1a.pde  (Hauptprogramm)
Ball_1a.pde                        (4. Unterprogramme)
Bar_1a.pde
Controlpanel_1a.pde
Hole_1a.pde



Dreieck nach unten > New Tab > Name for new file: Ball, Bar, Controlpanel Hole





Weißen Ball im schwarzen Loch versenken



Die Abbildung zeigt einen Screenshot des Spiels.

Im Zeitbalken rechts unten wächst während eines Spiels ein roter Balken von links nach rechts.
Wenn der rote Balken den gesamten Zeitbalken ausfüllt, ist die Spielzeit abgelaufen.
Die Treffer des aktuellen Spiels werden im Zähler links unten gezählt.
In der Highscore-Anzeige links oben wird das momentan beste Ergebnis der Spielserie angezeigt.
Über den Button in der Mitte unten wird das Spiel gestartet, gestoppt und zurückgesetzt.

Hinweis:

Wenn man nur an der Verknüpfung von Processing mit den oben angeführten Komponenten
(Arduino bis Javascript) interessiert ist, kann man das Basisprogramm auf die Drehung des Balkens reduzieren.



EMPFÄNGER:  Processing  game_controlpanel_1a.pde


Komplette Definition des Haupt-Programmes game_controlpanel_1a:

Processing-Sketch: game_controlpanel_1a.pde

int widthPlayground = 400;
int heightPlayground = 400;
int yBaseline = 350;
int radiusBall = 10;             // Radius des Balls
int speedBall = 5;               // Fallgeschwindigkeit in px
int radiusHole = radiusBall*2;

Bar bar; 
Ball ball;                       // Deklaration der Variablen ball vom Typ Ball
Hole hole;
Controlpanel controlPanel;       // Deklaration der Variablen controlPanel vom Typ ControlPanel
void setup() {
  size(widthPlayground, heightPlayground);
  bar = new Bar(width/2, yBaseline, 80, 10, 255); // erzeugen einer neuen Instanz der Klasse Ball mit konkreten Eingabewerten
  ball = new Ball(width/2, -radiusBall, radiusBall, 0, 0, 255); 
  hole = new Hole(widthPlayground, yBaseline, 2*radiusHole, radiusHole);
  controlPanel = new Controlpanel (30);           // erzeugen einer neuen Instanz der Klasse Controlpanel mit konkretem Eingabewert
}

void draw() {
  bar.calculateAngle(); 
  ball.calculatePosition(bar);    // Neuberechung der Position des Balls relativ zum Balken
                                  // wenn der Ball außerhalb des Spielfeldes liegt, wird er zurückgesetzt
  if (ball.outOfBoundaries()) {
    ball.setPosition(width/2, -radiusBall);
    ball.setSpeed(0, speedBall);
  } 
                                  // wenn der Ball in das Loch "gefallen" ist, wird er zurückgesetzt und eine neue Loch-Instanz wird erzeugt
  else if (ball.inHole(hole)) {
    ball.setPosition(width/2, -radiusBall);
    ball.setSpeed(0, speedBall);
    hole = new Hole(widthPlayground, yBaseline, 2*radiusHole, radiusHole);
    controlPanel.increaseScore(); // erhöhen des Counters um 1
  }
                                   // Anzeigeteil
  displayPlayground();
  controlPanel.display();         // Anzeige des Controlpanels
  bar.display();
  hole.display();  
  ball.display();
}

void displayPlayground() {
  background(128);
  stroke(180);
  strokeWeight(1);
  line(0, yBaseline, width, yBaseline);
}

                                   // Funktion für das Drücken der Maustaste
void mousePressed () {
  controlPanel.pressStatusBtn();
}


*********************************************************
Tutorial: Bar Game

von Gerhard Funk
gerhard.funk@ufg.ac.at

und Andreas ZIngerle (Arduino-Teil)
andreas.zingerle@ufg.ac.at

In diesem Tutorial wird schrittweise ein einfaches Spiel in Processing entwickelt.
Anschließend wird das Spiel modifiziert, sodass es mit Hilfe eines Arduinoboards über einen Drehregler gesteuert werden kann,
über TUIO auf einem Multitouchtable spielbar ist, über eine Kinect durch Körperbewegungen beieinflusst wird,
auf einem Android-Smartphone läuft und über Javescript in einer Webseite eingebunden werden kann.


Tutorial: Bar Game

In diesem Tutorial wird schrittweise ein einfaches Spiel in Processing entwickelt:
Man muss versuchen die herabfallenden Kreise (“Bälle”) über den Balken so umzulenken,
dass die am Balken reflektierten Bälle in das Loch treffen. Ziel ist es, in einer bestimmten Zeit möglichst viele Bälle zu versenken.
Der Balken kann durch horizontales Bewegen der Maus im unteren Bereich des Spielfeldes
oder über die Links/Rechts-Pfeiltasten auf der Tastatur gedreht werden > zum Basistutorial.

Hier kann das Spiel auf einer eigenen Seite aufgerufen und ausprobiert werden.

Anschließend wird das Spiel modifiziert, sodass es
• mit Hilfe eines Arduinoboards über einen Drehregler gesteuert werden kann,
• über TUIO auf einem Multitouchtable spielbar ist,
• über eine Kinect durch Körperbewegungen beieinflusst wird,
• auf einem Android-Smartphone läuft und
• über Javescript in einer Webseite eingebunden werden kann.
Über die Menü- und Untermenüpunkte können die einzelnen Tutorials aufgerufen werden.

http://www.time.ufg.ac.at/intime/projekt012/processing/




*********************************************************

Processing   Zentraler Link: Processing.org

In diesem Abschnitt wird schrittweise das Spiel in Processing entwickelt.
Dieses Programm ist die Grundlage für die weiteren Tutorials, in denen das Basisprogramm entsprechend adaptiert und erweitert wird.
Hier kann man das Spiel aufrufen und ausprobieren.
Einsteigern wird empfohlen mit diesem Tutorial zu beginnen und es in der vorgesehenen Reihenfolge durchzuarbeiten:
  1. Spielfeld
  2. Balken (Bar)  Unterprogramm
  3. Loch (Hole)  Unterprogramm
  4. Ball  Unterprogramm
  5. Zähler und Controlpanel  Unterprogramm

Download aller Processing-Sketches dieses Tutorials.

http://www.time.ufg.ac.at/intime/projekt012/processing/



*********************************************************

http://www.time.ufg.ac.at/intime/projekt012/processing-spielfeld/

1. Spielfeld

Download Processing-Sketch

Im 1. Schritt programmieren wir die Spielfläche.
Da die Breite und Höhe der Fläche in den einzelnen Anwendungsfällen variiert,
werden dafür eigene Variablen vom Typ integer eingeführt, um die Werte dann rasch anpassen zu können.
Ebenso wird der y-Abstand der Basislinie vom oberen Spielfeldrand in einer eigenen Variablen gespeichert.
Auf dieser Linie wird später der Balken platziert.
int widthPlayground, heightPlayground, yBaseline;
In der setup-Funktion erhalten die Variablen ihren Wert.
Diese Größe des Ausgabefensters entspricht der Größe des Spielfeldes.


void setup() {
  widthPlayground = 400;
  heightPlayground = 400;
  yBaseline = 350;
  size(widthPlayground, heightPlayground);
}


In der draw-Funktion wird die Spielfläche gezeichnet: 
ein grauer Hintergrund mit einer hellgrauen Basislinie in der Strichstärke 1px.
Die y-Werte des Anfangs- und Endpunkts der Linie sind durch yBaseline bestimmt.

void draw() {
  background(128);
  stroke(180);
  strokeWeight(1);
  line(0, yBaseline, width, yBaseline);
}

Da in die draw-Funktion noch viele Anweisungen hineinkommen,
macht es Sinn das Zeichnen des Spielfeldes in einer eigenen Funktion displayPlayground zusammenzufassen
und in der draw-Funktion diese Funktion aufzurufen.
void draw() {
  displayPlayground();
}

void displayPlayground() {
  background(128);
  stroke(180);
  strokeWeight(1);
  line(0, yBaseline, width, yBaseline);
}


Wenn wir später die Darstellung des Spielfeldes ändern wollen, brauchen wir nur die Anweisungen in dieser Funktion umschreiben.
Somit ergibt sich folgender Gesamtcode für das Hauptprogramm:


// Processing-Sketch: Processing Spielflaeche_1b.pde
int widthPlayground, heightPlayground, yBaseline;

void setup() {
  widthPlayground = 400;
  heightPlayground = 400;
  yBaseline = 350;
  size(widthPlayground, heightPlayground);
}

void draw() {
  displayPlayground();
}

void displayPlayground() {
  background(128);
  stroke(180);
  strokeWeight(1);
  line(0, yBaseline, width, yBaseline);
}

Weiter geht es mit dem Balken.


*********************************************************

http://www.time.ufg.ac.at/intime/projekt012/processing-balken/

2. Balken (Bar)

Download Processing-Sktetch

Im 2. Schritt programmieren wir den Balken an dem die herabfallenden Kreise reflektiert werden.
Der Balken wird in der Mitte der Basislinie platziert.
Er lässt sich durch eine horizontale Mausbewegung unterhalb der Basislinie
ODER über die Pfeiltasten <- und -> nach links und rechts drehen.
Für den Balken definieren wir eine eigene class Bar
Dazu wird aus Gründen der Übersichtlichkeit ein neuer Karteireiter (New Tab) mit dem Namen Bar erzeugt,
und in diesen wird die Klassendefinition hineingeschrieben.
Klassennamen (z.B. Bar) beginnen immer mit einem Großbuchstaben!

Zuerst muss man sich überlegen, durch welche Eigenschaften der Balken definiert wird.
In unserem Beispiel sind das
• x- und y-Position des Balkenzentrums
• Breite und Höhe des Balkens
• der Drehwinkel zwischen -45° und +45°
• und die Füllfarbe (ist nicht unbedingt notwendig).

Weiters werden neben der Konstruktor-Funktion zwei weitere Funktionen (Methoden) benötigt.
Nämlich eine Funktion für die Darstellung des Balkens
und eine für die Berechung des Drehwinkels in Abhänigkeit von der Mausbewegung bzw. vom Drücken der Pfeiltasten.
Somit können wir folgendes Grundgerüst ohne viel nachzudenken hinschreiben:

class Bar {
  // Klassen-Eigenschaften
  float x, y;              // Postion des Balkenzentrums
  int barWidth, barHeight; // Breite und Höhe des Balkens
  float angle;             // Drehwinkel zwischen -45° und +45°
  color c;                 // Füllfarbe
                           
  Bar (float x, float y, int w, int h, color c) {  // Konstruktor-Funktion
  }                          
  void calculateAngle() {  // Funktion zur Berechnung des Drehwinkels
  }
  void display () {        // Funktion zur Darstellung des Balkens
  }
}

Als nächstes werden wir die drei Funktionen mit Anweisungen füllen.


Konstruktorfunktion

Beim Erzeugen einer Instanz der Klasse Bar übergeben wir 4 Parameter.
Diese werden in der Klammer neben dem Konstruktornamen (= Klassennamen – Großschreibung beachten!) mit ihrem jeweiligen Typ aufgelistet.
Dabei legt man selbst eine Reihenfolge fest, an die man sich beim Erzeugen einer Instanz halten muss.
Bar (float x, float y, int w, int h, color c)
In x und y wird die Startposition des Balkens übergeben, in w und h die Breite und Höhe des Balkens und in c die Füllfarbe.
Die Werte, die man über diese Parameter an die Konstruktor-Funktion übergibt, werden nun in den Eigenschaften der Klasse abgespeichert.
Beginnen wir mit der x-Position des Balkens:
Diese Eigenschaft wird in der Klassen-Variablen x gespeichert. Der Übergabeparameter hat ebenfalls den Namen x.

D.h. wir müssten x = x;  schreiben.
Der Compiler weiß jedoch nicht, welches x jetzt gemeint ist.
Daher muss man eine Unterscheidung treffen indem man das Schlüpsselwort this verwendet.
Mit this verweist man immer auf das Objekt (die Instanz) in der this verwendet wird.
Es ist sozusagen eine Referenz auf sich selbst, in der der eigene “Name” abgespeichert ist.
Siehe: http://processing.org/reference/this.html

Wir können nun this.x schreiben und meinen damit die Klasseneigenschaft x von dem Objekt, das über this auf sich selbst verweist.
Somit muss man richtig schreiben this.x = x;

Auf der rechten Seite steht der Eingabeparameter x, dessen Wert an die Klassen-Eigenschaft x übergeben wird.

Die Konstruktor-Funktion ist somit wie folgt definiert:

// Konstruktor-Funktion
Bar (float x, float y, int w, int h, color c) {
  this.x = x;
  this.y = y;
  this.barWidth = w;
  this.barHeight = h;
  this.c = c;
  this.angle = 0.0;
}

Wenn es zu keinem Namenskonflikt kommt, muss man this nicht schreiben.
D.h. man könnte auch folgendes schreiben:
// Konstruktor-Funktion
Bar (float x, float y, int w, int h, color c) {
  this.x = x;
  this.y = y;
  barWidth = w;
  barHeight = h;
  this.c = c;
  angle = 0.0;
}
Der Wert für den Drehwinkel wird nicht über einen Eingabeparameter definiert, sondern im Konstruktor auf 0 gesetzt.
D.h. der Balken liegt am Beginn horizontal und ist nicht gedreht.

display-Funktion
In Klassen, die visuelle Objekte definieren, sollte man immer in einer display-Funktion beschreiben, wie das Objekt am Screen angezeigt wird.
Über diese Funktion kann man jederzeit das Aussehen des Objekts verändern.
In unserem Fall hat die display-Funktion keine Eingabeparameter, weil sie direkt die Eigenschaften der Klasse verwendet.
Innerhalb einer Klasse kann man aus einer Funktion heraus jederzeit auf die Eigenschaftsvariablen zugreifen.
(Vorausgesetzt, dass diesen bereits ein Wert zugewiesen wurde, was in der Regel in der setup-Funktion passiert).


// Funktion zur Darstellung des Balkens
void display () {
  fill(c);                         // Füllfarbe
  noStroke();                      // kein Linien / Umrisse
  pushMatrix();                    // sichern des aktuellen Koordinatensystems
  translate(x, y);                 // verschieben des Koordinatenursprungs auf die gewünschte Position des Balkenzentrums
  rotate(angle);                   // drehen des Koordinatensystems im neuen Ursprung um den Winkel angle
  rectMode(CENTER);                // Bezugspunkt für das Zeichnen des Rechtecks ist das Zentrum des Rechtecks
  rect(0, 0, barWidth, barHeight); // das Rechteck zeichnen
  popMatrix();                     // das gesicherte Koordinatensystem wiederherstellen
} 

calculateAngle-Funktion

Diese Funktion ist die komplzierteste in dieser Klasse und berechnet den Winkel

um den der Balken gedreht wird in Abhängigkeit von der Mausposition bzw. den gedrückten Pfeiltasten.

Den Balken mit der Maus drehen:




Wenn man den Mauszeiger im Bereich unterhalb der Grundlinie waagrecht bewegt, kann der Balken um seinen Mittelpunkt gedreht werden.
Die Maus wurde horizontal bewegt, wenn sich der aktuelle x-Wert der Mausposition vom vorhergehenden unterscheidet.

mouseX != pmouseX
Ganz links, wenn mouseX gleich 0 ist, ist der Balken um -45° (im Bogenmaß -PI/4) gedreht.
Ganz rechts, wenn mouseX gleich width ist, ist der Balken um 45° (im Bogenmaß +PI/4) gedreht.
Somit wird über die map-Funktion der aktuelle mouseX-Wert aus dem Intervall [0, width] auf das Intervall [-PI/4, +PI/4] für den Drehwinkel gemappt.
angle = map(mouseX,0,width,-QUARTER_PI,QUARTER_PI);
Die ganze Funktion, mit der über die Maus der Balken gedreht werden kann, schaut daher so aus:

// Funktion zur Berechnung des Drehwinkels
void calculateAngle() {
  if (mouseX != pmouseX && mouseY > y) {                    // TRUE, wenn die Maus horizontal bewegt wurde UND unter dem Balkenzentrum liegt
    angle = map(mouseX, 0, width, -QUARTER_PI, QUARTER_PI); // mappen des mouseX-Wertes
  }
}

Den Balken mit den Pfeiltasten drehen:

Wenn die Maus nicht horizontal bewegt wurde, wird im else-Zweig überprüft, ob die linke oder rechte Pfeiltaste gedrückt ist.
Wenn das der Fall ist, wird der Drehwinkel angle um eine vorgegebene Schrittweite reduziert (linke Pfeiltaste) oder erhöht (rechte Pfeiltaste).
Allerdings nur so weit, bis die untere bzw. obere Grenze von -PI/4 bzw +PI/4 erreicht ist. Diese Begrenzung erfolgt mit der Funktion constrain.


     

Über    keyPressed == true && key == CODED    kann abfragen, ob eine Sondertaste gedrückt wurde.

Siehe http://www.processing.org/reference/keyCode.html

Wenn eine Sondertaste gedrückt ist, kann man mit  keyCode == LEFT bzw. keyCode == RIGHT  prüfen, ob es die linke bzw. rechte Taste ist.

Die vollstängige Funktion calculateAngle ist wie folgt definiert:


// Funktion zur Berechnung des Drehwinkels
void calculateAngle() {
  if (mouseX != pmouseX && mouseY > y) {                      // TRUE, wenn die Maus horizontal bewegt wurde UND unter dem Balkenzentrum liegt
    angle = map(mouseX, 0, width, -QUARTER_PI, QUARTER_PI);   // mappen des mouseX-Wertes
  } else if (keyPressed == true && key == CODED) {            // prüfen, ob eine Sondertaste gedrückt ist
    float step = 0.005;                                       // Schrittweite in Bodenmaß für einen Drehschritt definieren
    if (keyCode == LEFT) {                                    // prüfen, ob linke Pfeiltaste gedrückt ist
      angle = constrain(angle-step, -QUARTER_PI, QUARTER_PI); // Winkel um die Schrittweite reduzieren und auf [-PI/4, +PI/4] beschränken
    }
    if (keyCode == RIGHT) {                                   // prüfen, ob rechte Pfeiltaste gedrückt ist
      angle = constrain(angle+step, -QUARTER_PI, QUARTER_PI); // Winkel um die Schrittweite erhöhen und auf [-PI/4, +PI/4] beschränken
    }
  }
}

Komplette Definition der Klasse Bar:

// Processing-Sketch: Bar_1a.pde

class
Bar {
  // Klassen-Eigenschaften   float x, y; // Postion des Balkenzentrums   int barWidth, barHeight; // Breite und Höhe des Balkens   float angle; // Drehwinkel zwischen -45° und +45°   color c; // Füllfarbe   // Konstruktor-Funktion   Bar (float x, float y, int w, int h, color c) {     this.x = x;     this.y = y;     this.barWidth = w;     this.barHeight = h;     this.angle = 0.0;     this.c = c;   }   // Funktion zur Berechnung des Drehwinkels   void calculateAngle() {     if (mouseX != pmouseX && mouseY > y) { // TRUE, wenn die Maus horizontal bewegt wurde UND unter dem Balkenzentrum liegt       angle = map(mouseX, 0, width, -QUARTER_PI, QUARTER_PI); // mappen des mouseX-Wertes     } else if (keyPressed == true && key == CODED) { // prüfen, ob eine Sondertaste gedrückt ist       float step = 0.005; // Schrittweite in Bodenmaß für einen Drehschritt definieren       if (keyCode == LEFT) { // prüfen, ob linke Pfeiltaste gedrückt ist         angle = constrain(angle-step, -QUARTER_PI, QUARTER_PI); // Winkel um die Schrittweite reduzieren und auf [-PI/4, +PI/4] beschränken       }       if (keyCode == RIGHT) { // prüfen, ob rechte Pfeiltaste gedrückt ist         angle = constrain(angle+step, -QUARTER_PI, QUARTER_PI); // Winkel um die Schrittweite erhöhen und auf [-PI/4, +PI/4] beschränken       }     }   }   // Funktion zur Darstellung des Balkens   void display () {     fill(c); // Füllfarbe     noStroke(); // kein Umriss     pushMatrix(); // sichern des aktuellen Koordinatensystems     translate(x, y); // verschieben des Koordinatenursprungs auf die gewünschte Position des Balkenzentrums     rotate(angle); // drehen des Koordinatensystems im neuen Ursprung um den Winkel angle     rectMode(CENTER); // Bezugspunkt für das Zeichnen des Rechtecks ist das Zentrum des Rechtecks     rect(0, 0, barWidth, barHeight); // das Rechteck zeichnen     popMatrix(); // das gesicherte Koordinatensystem wiederherstellen   } }


Erzeugen einer Instanz der Klasse Bar im Hauptprogramm

Nachdem die Klasse definiert ist, können wir im Hauptprogramm ganz oben eine Variable bar (der Name ist frei erfunden,

Kleinschreibung ist wichtig) vom Typ Bar (das ist unsere Klasse, Großschreibung ist wichtig) deklarieren.

Bar bar;

In der setup-Funktion wird der Konstruktur mit konkreten Eingabewerten aufgerufen.

Dadurch wird eine eindeutige Instanz der Klasse Bar erzeugt, die über die Variable bar angesprochen werden kann.

bar = new Bar(width/2,yBaseline,80,10,255);

Wichtig! Die Reihenfolge der Parameter muss genau eingehalten werden.

In unserem Fall ist das Zentrum des Balkens im Punkt (width/2, yBaseline), der Balken ist 80px breit, 10px hoch und hat die Farbe Weiß.
In der draw-Funktion muss regelmäßig der neue Drehwinkel berechnet und anschließend der Balken angezeigt werden.
bar.calculateAngle();
bar.display();


Punktnotation:

Mit Instanzvariable.Eigenschaftsvarible bzw. Instanzvariable.Funktionsname(…)

kann man auf die Eigenschaften und Funktionen (Methoden) einer Klasse zugreifen.

z.B. bar.display() ruft die Funktion display der Instanz bar auf.


Das erweiterte Hauptprogramm:

Die Erweiterungen sind mit einem Kommentar versehen.


int widthPlayground, heightPlayground, yBaseline;
Bar bar; // Deklaration der Variablen bar vom Typ Bar

void setup() {
  widthPlayground = 400;
  heightPlayground = 400;
  yBaseline = 350;
  size(widthPlayground, heightPlayground);
  bar = new Bar(width/2, yBaseline, 80, 10, 255); // erzeugen einer neuen Instanz der Klasse Bar mit konkreten Eingabewerten
}

void draw() {
  bar.calculateAngle(); // Berechung der aktuellen Drehwinkels für den Balken
  displayPlayground();
  bar.display(); // Anzeige des Balkens
}

void displayPlayground() {
  background(128);
  stroke(180);
  strokeWeight(1);
  line(0, yBaseline, width, yBaseline);
}

Weiter geht es mit dem Loch, in das man den Ball versenken muss.




*********************************************************

http://www.time.ufg.ac.at/intime/projekt012/processing-loch/

3. Loch (Hole)


Download Processing-Sketch





Im 3. Schritt programmieren wir für das Loch, in das der Ball versenkt werden muss, eine eigene Klasse Hole.
Dazu wird wieder ein neuer Karteireiter (New Tab) mit dem Namen Hole angelegt und in diesen die Klassendefinition hineingeschrieben.

Das Loch ist durch folgende Eigenschaften bestimmt:
• x- und y-Position des Lochzentrums,
• Radius des Lochs.

Zusätzlich zur Konstruktor-Funktion wird eine display-Funktion für die Darstellung des Lochs definiert

und eine Funktion setRandomPosition mit deren Hilfe das Loch zufällig auf dem Spielfeld platziert wird.
Wir haben daher folgendes Grundrüst für die Klasse:


Komplette Definition der Klasse Hole:

// Processing-Sketch: Hole_1a.pde

class Hole {
  float x, y;
  int radius;

  Hole (int w, int h, int margin, int r) {
    radius = r;
    setRandomPosition(w, h, margin);
  }

  void setRandomPosition (int w, int h, int margin) {
    x = random(margin, w-margin);            // zufällige x-Position unter Berücksichtigung des Randes
                                             // Wenn die x-Position im mittleren Korridor liegt,
                                             // dann wird der x-Wert nach links oder rechts verschoben.
    if (w/2-margin < x && x <= w/2) {        // x liegt in der linken Hälfte des Korridors
      x = w/2-margin;                        // x an den linken Rand des Korridors verschieben
    } else if (w/2 < x && x < w/2+margin) {  // x liegt in der rechten Hälfte des Korridors
      x = w/2+margin;                        // x an den rechten Rand des Korridors verschieben
    }
    y = random(margin, h-margin);            // zufällige y-Position unter Berücksichtigung des Randes
  }    

  void display() {
    fill(50);
    stroke(180);
    strokeWeight(2);
    ellipseMode(RADIUS);                     // der Mittelpunkt der Ellipse ist der Bezugspunkt
    ellipse(x, y, radius, radius);
  }
}
Hole (int w, int h, int margin, int r) {   radius = r;   setRandomPosition(w, h, margin); }
Die Positionsvariblen x und y haben hier den Typ float, weil die random-Funktion eine float-Zahl liefert.

Konstruktorfunktion
An den Konstruktor werden 4 Eingabeparameter übergeben:
Die Weite w und Höhe h des Spielfeldes, ein Randabstand margin und der Lochradius r.
Durch den Randabstand wird gesteuert, wie weit weg vom Spielfeldrand ein Loch mindestens liegen muss.
Im Konstruktor wird der Radius gesetzt und die Funktion setRandomPosition aufgerufen, die den Eigenschaften x und y Werte zuweist.

Hole (int w, int h, int margin, int r) {
  radius = r;
  setRandomPosition(w, h, margin);
}

display-Funktion

In dieser Funktion wird ein Kreis mit fixer Füllfarbe und Umrißline mit dem Mittelpunkt (x,y) gezeichnet.

void display() {
  fill(50);
  stroke(180);
  strokeWeight(2);
  ellipseMode(RADIUS); // der Mittelpunkt der Ellipse ist der Bezugspunkt
  ellipse(x, y, radius, radius);
}

Funktion setRandomPosition




Diese Funktion erhält die Breite und Höhe des gesamten Spielfeldes als Eingabe (blaues Rechteck)
und einen Randabstand, der bestimmt wie weit die Löcher vom Rand mindestens entfernt sein müssen. Zusätzlich muss verhindert werden,
dass die Löcher im mittleren Korridor liegen, wo die Bälle herunterfallen.
Sonst würden sie gleich direkt (ohne Reflexion am Balken) ins Loch fallen.
Die roten Flächen in der obigen Abbildung zeigen die Bereiche in denen der Mittelpunkt des Lochs zufällig platziert werden kann.
Die blassen Löcher liegen an ungültigen Stellen.

void setRandomPosition (int w, int h, int margin) {
  x = random(margin, w-margin); // zufällige x-Position unter Berücksichtigung des Randes
  // Wenn die x-Position im mittleren Korridor liegt,
  // dann wird der x-Wert nach links oder rechts verschoben.
  if (w/2-margin < x && x <= w/2) { // x liegt in der linken Hälfte des Korridors
    x = w/2-margin; // x an den linken Rand des Korridors verschieben
  } else if (w/2 < x && x < w/2+margin) { // x liegt in der rechten Hälfte des Korridors
    x = w/2+margin; // x an den rechten Rand des Korridors verschieben
  }
  y = random(margin, h-margin); // zufällige y-Position unter Berücksichtigung des Randes
}

In unserem Fall beträgt die Korridorbreite 2*margin.

Erzeugen einer Instanz der Klasse Hole im Hauptprogramm
Nachdem die Klasse definiert ist, können wir im Hauptprogramm ganz oben eine Variable hole (der Name ist frei erfunden,
Kleinschreibung ist wichtig) vom Typ Hole (das ist unsere Klasse, Großschreibung ist wichtig) deklarieren.

Hole hole;
Zusätzlich wurde eine Variable radiusHole vom Typ int angelegt und mit einem fixen Wert initialisiert.
int radiusHole = 20;
In der setup-Funktion wird der Konstruktur mit konkreten Eingabewerten aufgerufen.
Dadurch wird eine eindeutige Instanz der Klasse Hole erzeugt, die über die Variable hole angesprochen werden kann.
hole = new Hole(widthPlayground, yBaseline, 2*radiusHole, radiusHole);
Wichtig! Die Reihenfolge der Parameter muss genau eingehalten werden.
In unserem Fall entspricht der Randabstand (3. Parameter) dem Lochdurchmesser.

In der draw-Funktion muss das Loch angezeigt werden.

hole.display();

Das erweiterte Hauptprogramm:

Die Erweiterungen sind mit einem Kommentar versehen.

int widthPlayground = 400;
int heightPlayground = 400;
int yBaseline = 350;
int radiusHole = 20; // Radius des Lochs

Bar bar;
Hole hole; // Deklaration der Variablen hole vom Typ Hole

void setup() {
  size(widthPlayground, heightPlayground);
  bar = new Bar(width/2, yBaseline, 80, 10, 255);
  hole = new Hole(widthPlayground, yBaseline, 2*radiusHole, radiusHole); // erzeugen einer neuen Instanz der Klasse Hole mit konkreten Eingabewerten
}

void draw() {
  bar.calculateAngle();
  displayPlayground();
  bar.display();
  hole.display();  // Anzeige des Lochs
}

void displayPlayground() {
  background(128);
  stroke(180);
  strokeWeight(1);
  line(0, yBaseline, width, yBaseline);
}

In diesem Programm wurde gegenüber dem Hauptprogramm des vorigen Abschnitts Balken noch eine Veränderung vorgenommen:
Alle Variablen, die wesentliche Grundparameter des Spiels speichern, wurden ganz oben hingeschrieben (Zeile 1 – 4) und mit Startwerten initialisiert.
Das muss nicht erst in der setup-Funkktion erfolgen.
Dadurch sieht man alle Parameter auf ein Blick und kann die Ausgangswerte leicht und schnell verändern.
Weiter geht es mit dem Ball.



*********************************************************

http://www.time.ufg.ac.at/intime/projekt012/processing-ball/

4. Ball


Download Processing-Sketch



Im 4. Schritt wird eine Klasse für den Ball (dargestellt als Kreisfläche) erstellt,
der von der Mitte des oberen Spielfeldrandes herabfällt und vom Balken reflektiert wird.
Sobald der Ball in das Loch fällt oder das Spielfeld verlässt, fällt der nächste Ball vom oberen Rand herunter.
Dazu legen wir eine eigene Klasse Ball in einem neuen Karteireiter an.

Ein Ball ist durch folgende Eigenschaften bestimmt:
• x- und y-Position des Ballmittelpunkts
• Radius und Farbe des Balls
• und seine Geschwindigkeit, die durch den Geschwindigkeitsvektor (vx,vy) beschrieben wird.

Neben der Konstruktor-Funktion gibt es
• eine display-Funktion für die Darstellung des Balls
• eine Funktion calculatePosition mit deren Hilfe die Position des sich bewegenden Balls immer wieder neu berechnet wird
• eine Funktion setPosition zum Zurücksetzen des Balls an die Startposition
• und eine Funktion setSpeed zum Einstellen der Geschwindigkeit, womit der Ball auch gestoppt werden kann.



Komplette Definition der Klasse Ball:

// Processing-Sketch: Ball_1a.pde

class Ball {
  float x, y; // Position des Balls
  float vx, vy; // Geschwindigkeit des Balls
  int radius; // Ballradius
  color c; // Ballfarbe

  Ball (float x, float y, int r, float vx, float vy, color c) {
    this.x = x;
    this.y = y;
    this.radius = r;
    this.vx = vx;
    this.vy = vy;
    this.c = c;
  }

  void calculatePosition(Bar bar) {
    // neue Position des Balls berechnen
    x += vx;
    y += vy;
    // Koordinaten des Balls relativ zum Balkenursprung berechnen
    float dx = x - bar.x;
    float dy = y - bar.y;
    // sin und cos des negativen Balkendrehwinkels berechnen und speichern
    float sinus = sin(-bar.angle);
    float cosinus = cos(-bar.angle);
    // relativ zum Balkenursprung die y-Koordinate des Balls um den negativen Winkel drehen (-> Koordinatentransformation)
    float y1 = dx*sinus+dy*cosinus;
    // überprüfen, ob der Ball den Balken berührt
    if (y1 > -bar.barHeight/2-radius) {
      // x-Koordinate des Balls und den Geschwindigkeitsvektor drehen (-> Koordinatentransformationen)
      float x1 = dx*cosinus-dy*sinus;
      float vx1 = vx*cosinus-vy*sinus;
      float vy1 = vx*sinus+vy*cosinus;
      // Ball auf Balkenoberkante platzieren
      y1 = -bar.barHeight/2-radius;
      // Bewegungsrichtung umkehren
      vy1 *= -1;
      // sin und cos des positiven Balkendrehwinkels berechnen und speichern
      sinus = sin(bar.angle);
      cosinus = cos(bar.angle);
      // Ballkoordinaten um den Winkel zurückdrehen (-> Koordinatentransformationen)
      dx = x1*cosinus-y1*sinus;
      dy = x1*sinus+y1*cosinus;
      // Ballkoordinaten relativ zum Bühnenursprung setzten
      x = bar.x + dx;
      y = bar.y + dy;
      // Geschwindigkeitsvektor zurückdrehen (-> Koordinatentransformationen)
      vx = vx1*cosinus-vy1*sinus;
      vy = vx1*sinus+vy1*cosinus;
    }
  }

  void setPosition (float x, float y) {
    this.x = x;
    this.y = y;
  }

  void setSpeed (float vx, float vy) {
    this.vx = vx;
    this.vy = vy;
  }

  void display() {
    fill(c);
    noStroke();
    ellipseMode(RADIUS);
    ellipse(x, y, radius, radius);
  }

  boolean outOfBoundaries() {
    if (x < -radius || x > width+radius || y < -2*radius) {
      return(true);
    } else {
      return(false);
    }
  }

  boolean inHole(Hole hole) {
    if (dist(hole.x, hole.y, ball.x, ball.y) < hole.radius-this.radius) {
      return(true);
    } else {
      return(false);
    }
  }
}

Im Konstruktor werden über die Eingabeparameter Position, Radius, Geschwindigkeit und Farbe an die Eigenschaftsvariablen der Klasse übergeben.

In setPosition kann man über die Eingabeparameter von außen die Eigenschaften x und y und somit die Position des Balls ändern.
Ebenso lassen sich in setSpeed über die Eingabeparameter von außen die Eigenschaften vx und vy und somit die Geschwindigkeit des Balls ändern.
“von außen” bedeutet, dass man in einem anderen Programmteil (z.B im Hauptprogramm) über diese Funktionen die Eigenschaften einer Instanz ändern kann.
z.B. durch den Aufruf

b.setPosition(40, 120);

wobei b eine Instanz der Klasse Ball ist. Ohne Verwendung dieser set-Funktion müsste man

b.x = 40;
b.y = 120;


schreiben.

In der display-Funktion wird ein einfacher Kreis mit dem Mittelpunkt (x,y) ohne Umriß und der vorgegebenen Füllfarbe gezeichnet.

Funktion calculatePosition

Diese Funktion ist wegen der Reflexion des Balls an dem schrägen Balken der schwierigste Teil und das Herzstück des Programms.

Zuerst wird zur aktuellen Position des Balls der aktuelle Geschwindigkeitsvektor dazugezählt:

x += vx;
y += vy;


Anschließend wird geprüft,  ob der Ball in der neuen Position den Balken berührt oder bereits darunter liegt.

Wenn das der Fall ist muss der Geschwindigkeitsvektor auf Grund der Reflexion neu berechnet werden.
Die folgende Abbildung zeigt das Prinzip der Reflexion an einem horizontalen Balken (schwarze Linie).



  1. In der linken Grafik ist die neue Postion noch über der schwarzen Linie (y-Wert des Punktes ist kleiner als der y-Wert der schwarzen Linie).
  2. Somit findet noch keine Reflexion statt und der Geschwindigkeitsvektor bleibt gleich.
  3. In der mittleren Grafik berührt der Punkt die Linie (y-Wert des Punktes ist gleich dem y-Wert der schwarzen Linie).
  4. Somit muss sich wegen der Reflexion der y-Wert des neuen Geschwindigkeitsvektors umdrehen, der x-Wert bleibt gleich.
  5. In der rechten Grafik ist die neue Position bereits unterhalb der schwarzen Linie (y-Wert des Punktes ist größer als der y-Wert der schwarzen Linie).
  6. In diesem Fall muss zusätzlich zum Invertieren des y-Werts vom neuen Geschwindigkeitsvektor eine (nicht ganz exakte)
  7. Korrektur des y-Werts des Balls erfolgen, indem der y-Wert vom Ball auf den y-Wert der schwarzen Linie gesetzt wird.

Da der Balken aber nicht nur horizontal liegt, sondern auch gedreht sein kann, muss man für die Reflexion an einem schrägen Balken vorher eine Koordinatentransformation durchführen, um die Reflexion an einer schrägen Linie auf die Reflexion an einer horizontalen Linie zurückführen zu können.




Durch die Koordinatentransformation wird das Problem der Reflexion an einem schiefen Balken reduziert
auf das einfache Problem der Reflexion an einem horizontalen Balken

Das Koordinatensystem wird fiktiv um den Drehwinkel des Balkens gedreht und in den Balkenursprung verschoben.
Die Koordinaten der neuen Ballposition und des aktuellen Geschwindigkeitsvektors werden relativ zum dem gedrehten und verschobenen Koordinatensystems berechnet.
Mit diesen umgerechneten Koordinaten kann man die Reflexion an der horizontalen x-Achse des neuen Koordinatensystems berechnen.

Der y-Wert der Reflexionslinie ist in diesem Fall gleich 0.
Die Koordinatentransformation ist eine Umrechnung, die hier hergeleitet wird. Aber man braucht nur die Formel

x’ = x * cos alpha – y * sin alpha
y’ = x * sin alpha + y * cos alpha

anwenden. alpha ist der Drehwinkel aus der Sicht des neuen Koordinatensystems (daher muss der Drehwinkel vorher invertiert werden).

Zusammengefasst sind folgende Schritte zu tun:

  1. Neue Position des Balls berechnen.
  2. Koordinaten des Balls relativ zum Balkenursprung berechnen.
  3. Koordintatentransformation der y-Koordinate des Balls um den negativen Drehwinkel des Balken.
  4. Überprüfen, ob der Ball den Balken berührt oder darunter liegt.
    Wenn ja: 
    1. x-Koordinate des Balls und den Geschwindigkeitsvektor drehen.
    2. Ball auf Balkenoberkante platzieren und y-Wert des Geschwindigkeitsvektors umkehren.
    3. Ballkoordinaten um den Winkel zurückdrehen und wieder relativ zum Bühnenursprung setzten.
    4. Geschwindigkeitsvektor zurückdrehen.

Die Funktion calculatePosition schaut daher wie folgt aus:

void calculatePosition(Bar bar) {
  // neue Position des Balls berechnen
  x += vx;
  y += vy;
  // Koordinaten des Balls relativ zum Balkenursprung berechnen
  float dx = x - bar.x;
  float dy = y - bar.y;
  // sin und cos des negativen Balkendrehwinkels berechnen und speichern
  float sinus = sin(-bar.angle);
  float cosinus = cos(-bar.angle);
  // relativ zum Balkenursprung die y-Koordinate des Balls um den negativen Winkel drehen (-> Koordinatentransformation)
  float y1 = dx*sinus+dy*cosinus;
  // überprüfen, ob der Ball den Balken berührt
  if (y1 > -bar.barHeight/2-radius) {
    // x-Koordinate des Balls und den Geschwindigkeitsvektor drehen (-> Koordinatentransformationen)
    float x1 = dx*cosinus-dy*sinus;
    float vx1 = vx*cosinus-vy*sinus;
    float vy1 = vx*sinus+vy*cosinus;
    // Ball auf Balkenoberkante platzieren
    y1 = -bar.barHeight/2-radius;
    // Bewegungsrichtung umkehren
    vy1 *= -1;
    // sin und cos des positiven Balkendrehwinkels berechnen und speichern
    sinus = sin(bar.angle);
    cosinus = cos(bar.angle);
    // Ballkoordinaten um den Winkel zurückdrehen (-> Koordinatentransformationen)
    dx = x1*cosinus-y1*sinus;
    dy = x1*sinus+y1*cosinus;
    // Ballkoordinaten relativ zum Bühnenursprung setzten
    x = bar.x + dx;
    y = bar.y + dy;
    // Geschwindigkeitsvektor zurückdrehen (-> Koordinatentransformationen)
    vx = vx1*cosinus-vy1*sinus;
    vy = vx1*sinus+vy1*cosinus;
}
}


Bei der Überprüfung, ob der Ball den Balken berührt, steht die Abfrage:

if (y1 > -bar.barHeight/2-radius) {

Das hängt damit zusammen, dass der Balken eine Dicke hat und der Ball nicht nur ein Punkt, sondern ein Kreis mit einem bestimmen Radius ist.



Funktion inHole

Mit dieser Funktion wird geprüft, ob der Ball in ein Loch fällt.
Da in einer weiteren Version mehrere Löcher denkbar sind, wird an die Funktion eine Loch-Instanz als Eingabeparameter übergeben.
Ein Kreis ist vollständig innerhalb eines anderen Kreises, wenn der Abstand zwischen den Kreismittelpunkten kleiner ist als die Differenz der Radien.
boolean inHole(Hole hole) {
  if (dist(hole.x, hole.y, ball.x, ball.y) < hole.radius-this.radius) {
    return(true);
  } else {
    return(false);
  }
}

Zum Abschluss erweitern wir die Klasse Ball noch um zwei Methoden, die prüfen, ob ein Ball in ein Loch gefallen ist oder das Spielfeld verlassen hat.

Funktion out Of Boundaries
Diese Funktion prüft, ob der Ball nach der Reflexion außerhalb der Spielfeldgrenzen liegt.
Dafür muss in diesem Spiel der obere, linke und rechte Rand geprüft werden.
Der Einfachheit halber wird angenommen, dass die Spielfeldbreite mit der Breite des Anzeigefensters übereinstimmt.

boolean outOfBoundaries() {
  if (x < -radius || x > width+radius || y < -2*radius) {
    return(true);
  } else {
    return(false);
  }
}
Damit ist die Klasse Ball definiert und wir können im Hauptprogramm eine Instanz erzeugen und die entsprechenden Methoden aufrufen.

Erzeugen einer Instanz der Klasse Ball im Hauptprogramm

Im Hauptprogramm wird ganz oben eine Variable ball (der Name ist frei erfunden, Kleinschreibung ist wichtig) vom Typ Ball (das ist unsere Klasse, Großschreibung ist wichtig) deklarieren.

Ball ball;

Zusätzlich werden zwei Variablen radiusBall und speedBall vom Typ int angelegt und mit fixen Werten initialisiert.

int radiusBall = 10;
int speedBall = 5;

In der setup-Funktion wird der Konstruktur mit konkreten Eingabewerten aufgerufen.

Dadurch wird eine eindeutige Instanz der Klasse Ball erzeugt, die über die Variable ball angesprochen werden kann.

Der Ballmittelpunkt wird dabei in der Mitte außerhalb des oberen Spielfeldrandes platziert (widht/2, -radiusBall) und bekommt den Geschwindigkeitsvektor (0, speedBall).

ball = new Ball(width/2, -radiusBall, radiusBall, 0, speedBall, 255);

Wichtig! Die Reihenfolge der Parameter muss genau eingehalten werden.
In der draw-Funktion wird die Position des Balls neu berechnet und geprüft, ob er außerhalb des Spielfeldes liegt oder in das Loch “gefallen” ist.
Ist das der Fall, werden der Ball und der Geschwindigkeitsvektor zurückgesetzt.
Wenn der Ball in das Loch “gefallen” ist, wird zusätzlich eine neue Loch-Instanz erzeugt und unter der vorhandenen Variablen hole abgespeichert.
Dadurch gibt es keinen Verweis mehr auf die “alte” Loch-Instanz, die über Garbage Collection automatisch aus dem Arbeitsspeicher gelöscht wird.
Anschließend wird der Ball angezeigt.

// wenn der Ball außerhalb des Spielfeldes liegt, wird er zurückgesetzt
if (ball.outOfBoundaries()) {
  ball.setPosition(width/2, -radiusBall);
  ball.setSpeed(0, speedBall);
}
// wenn der Ball in das Loch "gefallen" ist, wird er zurückgesetzt und eine neue Loch-Instanz wird erzeugt
else if (ball.inHole(hole)) {
  ball.setPosition(width/2, -radiusBall);
  ball.setSpeed(0, speedBall);
  hole = new Hole(widthPlayground, yBaseline, 2*radiusHole, radiusHole);
}
.....
ball.display(); // Anzeige des Balls nach der Anzeige des Lochs, da dieser darüber liegt

Anmerkung: Anstatt den Ball über

ball.setPosition(width/2, -radiusBall);
ball.setSpeed(0, speedBall);

zurückzusetzen, könnte man wie beim Loch auch eine neue Ball-Instanz erzeugen und unter ball abspeichern

ball = new Ball(width/2, -radiusBall, radiusBall, 0, speedBall, 255);


weiter geht es mit dem Zähler.




*********************************************************
http://www.time.ufg.ac.at/intime/projekt012/processing-zaehler/

5. Zähler und Control Panel, Übunsaufgaben


Download Processing-Sketch

Im 5. Schritt werden dem Spiel noch ein Zähler, eine Highscore-Anzeige, ein Zeitbalken und ein Button hinzugefügt.
Im Zeitbalken rechts unten wächst während eines Spiels ein roter Balken von links nach rechts.
Wenn der rote Balken den gesamten Zeitbalken ausfüllt, ist die Spielzeit abgelaufen.
Die Treffer des aktuellen Spiels werden im Zähler links unten gezählt. In der Highscore-Anzeige links oben wird das momentan beste Ergebnis der Spielserie angezeigt.
Über den Button in der Mitte unten wird das Spiel gestartet, gestoppt und zurückgesetzt.



Komplette Definition der Klasse Controlpanel:


// Processing-Sketch: Controlpanel_1a.pde

class Controlpanel {   int score, highScore;   int gameStatus; // 0 ... bereit, 1 ... läuft, 2 ... beendet   float startTime, currentTime, maxTime;   PFont fontArial14; // Font für die Anzeige   PFont fontArial36; // Font für die Anzeige   String label;   Controlpanel (float t) {     maxTime = t*1000;     startTime = 0;     currentTime = 0;     score = 0;     highScore = 0;     gameStatus = 0;     label = "play";     fontArial14 = createFont("Arial", 14);     fontArial36 = createFont("Arial", 36);   }   void increaseScore () {     score++;   }   void pressStatusBtn () {     if (widthPlayground/2-30 <= mouseX && mouseX <= widthPlayground/2+30 && yBaseline+16 <= mouseY && mouseY <=yBaseline+36) {       gameStatus = (gameStatus+1) % 3;       switch (gameStatus) {       case 0: // reset wurde gedrückt > start-Zustand wird eingerichtet         label = "start";         ball.setPosition(widthPlayground/2, -radiusBall);         if (score > highScore) highScore = score;         score = 0;         currentTime = 0;         break;       case 1: // start wurde gedrückt > play-Zustand wird eingerichtet         label = "stop";         ball.setSpeed(0, speedBall);         startTime = millis();         break;       case 2: // stop wurde gedrückt > reset-Zustand wird eingerichtet         label = "reset";         ball.setSpeed(0, 0);         startTime = 0;         break;       }     }   }   void display () {     // Highscore-Anzeige     textFont(fontArial14);     textAlign(LEFT, TOP);     text("Highscore: "+highScore, 5, 0);     // Anzeige der aktuellen Treffer     textFont(fontArial36);     textAlign(LEFT, TOP);     text(score, 5, yBaseline+5);     // Anzeige des Buttons     displayStatusBtn();     // Anzeige der Spielzeit     displayTimeBar();   }   void displayStatusBtn () {     rectMode(CENTER);     fill(150);     stroke(0);     strokeWeight(1);     rect(widthPlayground/2, yBaseline+26, 60, 20);     fill(0);     noStroke();     textFont(fontArial14);     textAlign(CENTER, CENTER);     text(label, widthPlayground/2, yBaseline+24); // der Buttontext ist in der Variablen label gespeichert   }   void displayTimeBar () {     if (gameStatus == 1) currentTime = millis()-startTime; // wenn das Spiel läuft, aktuelle Spielzeit bestimmen     // Grundrechteck des Zeitbalkens     rectMode(CORNER);     fill(150);     stroke(0);     strokeWeight(1);     rect(widthPlayground-110, yBaseline+21, 100, 10);     // darüber gelegtes rotes Rechteck für die aktuelle Spielzeit     fill(255, 0, 0, 150);     rect(widthPlayground-110, yBaseline+21, map(currentTime, 0, maxTime, 0, 100), 10);     if (currentTime >= maxTime) { // prüfen, ob die Spielzeit abgelaufen ist       // stoppen des Spiels       gameStatus = 2; // Status ändern       label = "reset";       ball.setSpeed(0, 0); // Ballgeschwindigkeit auf 0 setzen       startTime = 0;     }   } }


In startTime  wird beim Starten eines Spiels die aktuelle Systemzeit abgespeichert.
currentTime speichert die Zeit in Millisekunden, die seit dem Spielstart vergangen ist.
An den Konstruktor wird nur die maximale Spieldauer in Sekunden übergeben, die in maxTime in Millisekunden umgerechnet wird.

Funktion increaseScore

Diese Funktion wird von außen vom Hauptprogramm aufgerufen, sobald ein Ball in dem Loch versenkt wurde und erhöht den Zähler um 1.

void increaseScore () {
  score++;
}

Gamestatus
Das Spiel kann 3 Zustände (gespeichert in gameStatus) annehmen, die mit 0, 1 oder 2 kodiert werden und durchrotieren.
Bereit = 0: Das Spiel ist für den ersten bzw. nächsten Durchgang bereit.
Der Buttontext (label) ist in diesem Zustand “play”.
Durch Drücken des Buttons, kann das Spiel gestartet werden und wechselt in den Zustand 1.
Läuft = 1: Das Spiel läuft und die Kreise fallen von oben herab.
Der Zähler zählt in diesem Zustand die Treffer. Der Buttontext ist in diesem Zustand “stop”.
Durch Drücken des Buttons in diesem Zustand oder nach Ablauf der Spielzeit wird das Spiel beendet und wechselt in den nächsten Zustand 2.
Beendet = 2: Das Spiel ist gestoppt, die erzielte Trefferanzahl wird angezeigt, der rote Balken der abgelaufenene Zeit ist sichtbar.
Der Buttontext ist in diesem Zustand “reset”.
Durch Drücken des Buttons werden Zähler und Zeitbalken zurückgesetz und es wird der Highscore-Wert aktualisiert, falls dieser verbessert wurde.
Damit sind wir wieder beim Zustand 0 angelangt.
Die Funktion pressStatusBtn bildet diesen Statuswechsel ab und wird über das Hauptprogramm aufgerufen, wenn die Maus über dem Button gedrückt wurde.


void
pressStatusBtn () {   if (widthPlayground/2-30 <= mouseX && mouseX <= widthPlayground/2+30 && yBaseline+16 <= mouseY && mouseY <=yBaseline+36) {     gameStatus = (gameStatus+1) % 3; // Zustand weiterrotieren     switch (gameStatus) {     case 0: // reset wurde gedrückt > start-Zustand wird eingerichtet       label = "start";       ball.setPosition(widthPlayground/2, -radiusBall); // Ball in Startposition bringen       if (score > highScore) highScore = score; // Highscore eventuell aktualisieren       score = 0; // Zähler zurücksetzen       currentTime = 0; // Spielzeit zurücksetzen       break;     case 1: // start wurde gedrückt > play-Zustand wird eingerichtet       label = "stop";       ball.setSpeed(0, speedBall); // Geschwindigkeitsvektor des Balls wird gesetzt       startTime = millis(); // aktuelle Systemzeit als Startzeitpunkt speichern       break;     case 2: // stop wurde gedrückt > reset-Zustand wird eingerichtet       label = "reset";       ball.setSpeed(0, 0); // Ball wird gestoppt durch Geschwindigkeitsvektor (0,0)       startTime = 0; // Startzeit zurücksetzen       break;     }   } }
In der if-Abfrage wird geprüft, ob die Maus im Buttonrechteck liegt.
Die Zahlen entsprechen den Werten in der Funktion displayStatusBtn.
Ich habe dafür keine eigenen Variablen eingeführt.
Wenn die Maus im Buttonrechteck liegt, wird der gameStatus um 1 erhöht und modulo 3 (siehe %) genommen, da die 3 Zustände durchrotieren und nach 2 wieder 0 folgt:
gameStatus = (gameStatus+1) % 3;
Über ein switch/case-Statement wird zwischen den Zuständen unterschieden und in jedem Fall wird der neue Zustand eingerichtet.

Anzeigen der Elemente
In der Funktion display werden die vier Elemente angezeigt.
Die absoluten Koordinaten der Textblöcke, des Buttons und Zeitbalkens sind auf die konkrete Fenstergröße angepasst und müssen bei einer Änderung der Fenstergröße neu bestimmt und verändert werden.
Ich habe in diesem Fall auf ein Beschreibung der Koordinaten relativ zur Fensterbreite und -höhe verzichtet.


void
display () {   // Highscore-Anzeige   textFont(fontArial14);   textAlign(LEFT, TOP);   text("Highscore: "+highScore, 5, 0);   // Anzeige der aktuellen Treffer   textFont(fontArial36);   textAlign(LEFT, TOP);   text(score, 5, yBaseline+5);   // Anzeige des Buttons   displayStatusBtn();   // Anzeige der Spielzeit   displayTimeBar(); }

Die Funktion displayStatusBtn zeichnet den Button mit dem Text, der in der Variblen label gespeichert ist.

void displayStatusBtn () {
  // Buttonumriss
  rectMode(CENTER);
  fill(150);
  stroke(0);
  strokeWeight(1);
  rect(widthPlayground/2, yBaseline+26, 60, 20);
  // Buttontext
  fill(0);
  noStroke();
  textFont(fontArial14);
  textAlign(CENTER, CENTER);
  text(label, widthPlayground/2, yBaseline+24); // der Buttontext ist in der Variablen label gespeichert
}
Am interessantesten ist die Funktion displayTimeBar in der nicht nur der Zeitbalken mit der aktuellen Spielzeit dargestellt wird, sondern auch geprüft wird, ob die Spielzeit abgelaufen ist.
Wenn das der Fall ist, wird das Spiel gestoppt.

void displayTimeBar () {
  if (gameStatus == 1) currentTime = millis()-startTime; // wenn das Spiel läuft, aktuelle Spielzeit bestimmen
  // Grundrechteck des Zeitbalkens
  rectMode(CORNER);
  fill(150);
  stroke(0);
  strokeWeight(1);
  rect(widthPlayground-110, yBaseline+21, 100, 10); // Balken mit Länge 100px
  // darüber gelegtes rotes Rechteck für die aktuelle Spielzeit
  fill(255, 0, 0, 150);
  rect(widthPlayground-110, yBaseline+21, map(currentTime, 0, maxTime, 0, 100), 10);
  if (currentTime >= maxTime) { // prüfen, ob die Spielzeit abgelaufen ist
    // stoppen des Spiels
    gameStatus = 2; // Status ändern
    label = "reset";
    ball.setSpeed(0, 0); // Ballgeschwindigkeit auf 0 setzen
    startTime = 0;
  }
}

In der Zeile

rect(widthPlayground-110, yBaseline+21, map(currentTime, 0, maxTime, 0, 100), 10);
wird die aktuelle Spielzeit currentTime, die zwischen 0 und maxTime liegen kann, auf die Länge des roten Rechtecks, die zwischen 0 und der Länge des Grundrechteckes (= 100px) sein kann, gemappt.

Erzeugen einer Instanz der Klasse Controlpanel im Hauptprogramm

Nachdem die Klasse definiert ist, können wir im Hauptprogramm  eine Variable controlPanel vom Typ Controlpanel  deklarieren.

Controlpanel controlPanel;

In der setup-Funktion wird der Konstruktur mit dem konkreten Eingabewert (30 Sekunden maximale Spielzeit) aufgerufen. Dadurch wird eine eindeutige Instanz der Klasse Controlpanel erzeugt, die über die Variable controlPanel angesprochen werden kann.

controlPanel = new Controlpanel (30);

In der draw-Funktion wird, wenn die Ball ins Loch gefallen ist (ball.inHole(hole) ist wahr), die increaseScore-Funktion aufgerufen

controlPanel.increaseScore();

und im Anzeigeteil wird das Controlpanel gezeichnet

controlPanel.display();

Button anklicken:

Ergänzt wird das Hauptprogramm durch die Funktion mousePressed, die immer aufgerufen wird, wenn man die Maustaste drückt.

void mousePressed () {
  controlPanel.pressStatusBtn();
}
Dadurch wird immer beim Drücken der Maustaste die Funktion pressStatusBtn aufgerufen, die gleich am Anfang prüft, ob die Maus über dem Buttom liegt.
Wenn das der Fall ist, werden Aktionen ausgelöst.

Das erweiterte Hauptprogramm:

Die Erweiterungen sind mit einem Kommentar versehen.

Komplette Definition des Haupt-Programmes game_controlpanel_1a:

// Processing-Sketch: game_controlpanel_1a.pde

int widthPlayground = 400;
int heightPlayground = 400;
int yBaseline = 350;
int radiusBall = 10; // Radius des Balls
int speedBall = 5;  // Fallgeschwindigkeit in px
int radiusHole = radiusBall*2;

Bar bar;
Ball ball;   // Deklaration der Variablen ball vom Typ Ball
Hole hole;
Controlpanel controlPanel; // Deklaration der Variablen controlPanel vom Typ ControlPanel

void setup() {
  size(widthPlayground, heightPlayground);
  bar = new Bar(width/2, yBaseline, 80, 10, 255);
  ball = new Ball(width/2, -radiusBall, radiusBall, 0, 0, 255); // erzeugen einer neuen Instanz der Klasse Ball mit konkreten Eingabewerten
  hole = new Hole(widthPlayground, yBaseline, 2*radiusHole, radiusHole);
  controlPanel = new Controlpanel (30); // erzeugen einer neuen Instanz der Klasse Controlpanel mit konkretem Eingabewert
}

void draw() {
  bar.calculateAngle();
  ball.calculatePosition(bar); // Neuberechung der Position des Balls relativ zum Balken
// wenn der Ball außerhalb des Spielfeldes liegt, wird er zurückgesetzt
   if (ball.outOfBoundaries()) {     ball.setPosition(width/2, -radiusBall);     ball.setSpeed(0, speedBall);
// wenn der Ball in das Loch "gefallen" ist, wird er zurückgesetzt und eine neue Loch-Instanz wird erzeugt
   } else if (ball.inHole(hole)) {     ball.setPosition(width/2, -radiusBall);     ball.setSpeed(0, speedBall);     hole = new Hole(widthPlayground, yBaseline, 2*radiusHole, radiusHole);     controlPanel.increaseScore(); // erhöhen des Counters um 1   }   // Anzeigeteil   displayPlayground();   controlPanel.display(); // Anzeige des Controlpanels   bar.display();   hole.display();     ball.display(); } void displayPlayground() {   background(128);   stroke(180);   strokeWeight(1);   line(0, yBaseline, width, yBaseline); } // Funktion für das Drücken der Maustaste void mousePressed () {   controlPanel.pressStatusBtn(); }

Damit ist die Entwicklung des Spiels abgeschlossen.

Übungsaufgaben zur Erweiterung des Spiels

  1. Für das Spielfeld, den Balken und den Ball Bitmap-Grafiken verwenden, die in einem Bildbearbeitungsprogramm erstellt wurden.
  2. Eine Soundebene einführen.
  3. Den Highscore in einer Datei auf der Festplatte speichern, damit er beim nächsten Start der Processing-Datei wieder geladen werden kann.


*********************************************************

Im 6. Schritt steuern wir das Spiel nicht über die Maus sondern über ein ARDUINO UNO Board mit Poti

Arduino   Analog-Eingang pin-A0

Zentrale Links:
arduino.cc
Firmata (Arduino-Bibliothek für Processing)
Download Processing-Sketch Arduino-Sketch
In diesem Abschnitt wird gezeigt, wie der Balken über einen Drehregler (Potentiometer) gesteuert werden kann.
Dabei wird der Drehregler mit einem Arduino Uno Board verbunden, das die Daten an Processing weiterleitet.

Hardware

Als Hardware wird verwendet:
- Arduino Uno Microcontroller
- Potentiometer
- Breadboard
- Steckbrücken
- USB A/B Kabel



Hier sehen wir einen Screenshot der Software Fritzing. Der Potentiometer wird mit dem Ground (schwarz), Analog 0 (gelb) und 5V (rot) verbunden.

Installation von Arduino und testen des Potentiometers



Für die Installation von Arduino folgt man den ausführlichen Anweisungen der Download-Seite und der Getting Started-Seite.
Nach der erfolgreichen Installation öffnen wir die Arduino IDE und laden die Datei “AnalogReadSerial” mit dem Upload-Button (Pfeil nach rechts) auf das Board.
Die Datei befindet sich im Verzeichnis: examples\01.Basics\AnalogReadSerial.
Bevor der Code auf den Arduino geladen wird, nochmals sichergehen, dass das richtige Board und der passende Serial Port ausgewählt sind.
Mit Hilfe des Blink-Beispiels (examples\01.Basics\Blink) lässt sich der richtige Port leicht feststellen.




Unter Windows ist es der COM3-Port oder höher. Auf meinem Rechner ist es COM6.
Nach erfolgreichem Upload des Programmes öffnen wir unter Tools den “Seriellen Monitor” und drehen den Potentiometer gegen den Uhrzeigersinn nach links.
Wir sollten den Wert 1023 angezeigt bekommen.
Drehen wir den Potentiometer nach rechts, verringert sich der Wert, bis er 0 anzeigt.



Arduino-Programm (links) mit Seriellem Monitor (rechts).
Somit konnten wir überprüfen, dass der Potentiometer richtig angeschlossen wurde.
Jetzt öffnen wir das Arduino-Programm arduino_analog_in_out und laden es auf das Board.
Das Beispiel stammt von http://arduino.cc/en/Reference/Firmata (Example ganz unten).
Damit sind die Arduino-seitigen Vorbereitungen abgeschlossen.

Installation der Arduino-Bibliothek für Processing

Für die Kommunikation zwischen Arduino und Processing benötigt man eine Bibliothek namens Vsync.


Im nächsten Abschnitt verändern wir das Basis-Programm aus dem Menüpunkt Processing, sodass der Balken über den Potentiometer gesteuert wird.



Anpassung des Programms

Ausgangspunkt für die Anpassung ist das Basis-Programm im Menüpunkt Processing.

Anpassungen im Hauptprogramm

Zuerst importieren wir die Standard-Bibliothek Serial für die Kommunikation über die serielle Schnittstelle (RS-232), die automatisch mit Processing mitgeleifert wird.
Anschließend wird die installierte Arduino-Bibliothek Firmata importiert.


import processing.serial.*;
import cc.arduino.*;
Nun definieren und initialisieren wir globale Variable:

int potiPin = 0; // Nummer des ANALOG-IN-Pins auf dem Arduinoboard
int potiVal = 0; // Wert des Potentiometers
Arduino arduino; // Arduino-Objekt für die Kommunikation mit dem Board
Am Ende der setup-Funktion fügen wir folgenden Code ein:
arduino = new Arduino(this, Arduino.list()[0], 57600); // neues Arduino-Objekt erzeugen
Mit new Arduino(…) wird ein Proxy (= Stellvertreter) für ein Arduino-Board, auf dem die Firmata 2 Firmware läuft, mit der Standardübertragungsrate von 57600 baud erzeugt.
Wichtiger Hinweis:
Mit println(Arduino.list()); kann man die Liste aller verfügbaren seriellen Ports auflisten.
Es kann sein, dass der Port, über den mit Arduino kommuniziert wird (siehe voriger Abschnitt), nicht der Erstgereihte ist.
Dann muss man den Index in new Arduino(this, Arduino.list()[index], 57600) entsprechend ändern oder den Port direkt hineinschreiben,
z.B. new Arduino(this, “COM7″, 57600).
Am Anfang der draw()-Funktion fügen wir folgenden Code ein:
potiVal = arduino.analogRead(potiPin); println(potiVal);
Die Funktion arduino.analogRead(potiPin) liefert den letzten bekannten Wert vom analogen Input potiPin (= 0) zwischen 0 (0 Volt) und 1023 (5 Volt) .

Anpassungen in der Klasse Bar

In der Klasse Bar wird die Funktion calculateAngle() neu definiert:

void calculateAngle() {
  if (potiVal &gt;
  = 0) {
    angle = map(potiVal, 0, 1023, -QUARTER_PI, QUARTER_PI);
  }
}

Es wird einfach der Wert des Potentiometers, der in potiVal gespeichert ist, auf den Bereich -45° bis +45° abgebildet.

Übungsaufgabe zur Erweiterung des Spiels

Zusätzlich einen Druckknopf mit dem Arduino-Board verbinden über den der Spielzustand verändert wird.
Dadurch wird das Klicken mit der Maus auf den Button im Screen ersetzt.


SENDER:             Arduino-Sketch: Proces arduino_analog_in_out_1a.pde

#include <Firmata.h>

byte analogPin;

void analogWriteCallback(byte pin, int value)
{
  pinMode(pin,OUTPUT);
  analogWrite(pin, value);
}

void setup()
{
  Firmata.setFirmwareVersion(0, 1);
  Firmata.attach(ANALOG_MESSAGE, analogWriteCallback);
  Firmata.begin();
}

void loop()
{
  while(Firmata.available()) {
    Firmata.processInput();
  }
  for(analogPin = 0; analogPin < TOTAL_ANALOG_PINS; analogPin++) {
    Firmata.sendAnalog(analogPin, analogRead(analogPin)); 
  }
}





DIN A4 ausdrucken
*********************************************************

Impressum: Fritz Prenninger, Haidestr. 11A, A-4600 Wels, Ober-Österreich, mailto:schaltungen@schaltungen.at
ENDE





Comments