kramann.info
© Guido Kramann

Login: Passwort:










kramann.info
© Guido Kramann

Login: Passwort:




Informatik -- Lehrveranstaltung vom 19.06.2024

(EN google-translate)

(PL google-translate)

Entwicklung eines einfachen "Arcade Games"

In den 1980er Jahren kamen so genannte Arcade Games auf, einfache Action-Spiele gegen den Computer, wie Space-Invaders, Pacman, Racing u.ä.

Mit der Processing-Entwicklungsumgebung lässt sich so etwas gut realisieren.

Als Motivation sei angeführt:

  • Technisch muss hier der Übergang zwischen verschiedenen Systemzuständen umgesetzt werden, eine s.g. state-machine wird benötigt. Ablaufsteuerungen tauchen auch in sehr vielen anderen technischen Anwendungen auf, weshalb deren Behandlung sinnvoll ist.
  • Didaktisch sollte die Erinnerung an bekannte Spiele dazu motivieren, immer noch etwas Neues, wie Sound, Highscore usw., hinzu zu fügen, nachdem die Grundversion fertig ist.

Grundkonzept

Das Grundkonzept wird hier vorgegeben und kann dann beliebig verfeinert und erweitert werden:

  • Eine Spielfigur (blauer Kreis) kann über die Pfeiltasten auf einem Spielfeld mit quadratischem 11x11-Raster in vier Richtungen, mit jedem Tastendruck ein Spielfeld weit bewegt werden. Start ist in der Mitte.
  • "Nahrung" (20 kleinere grüne Kreise) ist zufällig über das Spielfeld verteilt und wird durch Begehen des entsprechenden Feldes durch den Spieler "gegessen", was einen inkrementellen Punktezuwachs ergibt.
  • 5..10 Gegner befinden sich zu Beginn am linken Rand und bewegen sich dann zufällig immer um ein Feld in eine beliebige Richtung weiter. Treffen Sie auf den Spieler, verliert dieser (wird gefressen).
  • Die Ränder sind miteinander verbunden, d.h. es geht da immer am gegenüberliegenden Rand weiter, wenn eine Grenze über- oder unterschritten wird.
Entwurfsskizze.

Bild 0-1: Entwurfsskizze.

Spezifizierungen:

  • Das Fenster hat eine Größe von 750 mal 550 Pixeln, wobei der linke Bereich von dem 550x550 Pixel großen Spielraster eingenommen wird und rechts der Spielzustand (erreichte Punkte, gewonnen/verloren) dargestellt werden soll.
  • Rechts soll es auch einen Taster für das Auslösen eines Neustarts geben.
Transiente Ereignisse

Es gibt Spielereignisse, die dazu führen, dass das Spiel von einem MODE in einen anderen übergeht. Beispiele:

  • Drücken des Startknopfes => Übergang vom Startbildschirm zum aktiven Spiel.
  • Spieler wird gefressen => Übergang vom aktiven Spiel zur Anzeige des "Verloren-Bildschirms"
  • Spieler frisst letztes Nahrungselement => Übergang vom aktiven Spiel zum "Gewonnen-Bildschirm"
  • ...

Legen Sie alle möglichen Übergänge und Zustände zu Beginn der Entwicklung fest.



Die Spielfiguren können als Objekte von entsprechenden Klassen umgesetzt werden, um später auch beispielsweise "Gesichter" ergänzen zu können.


Snippets (Kleine Code-Abschnitte, die bestimmte auftretende Programmierprobleme lösen)

#import java.util.Random;

Random zufall = new Random(System.currentTimeMillis());

public void setup()
{
}

public void draw()
{
   int z = zufall.nextInt(10); //Liefert eine gleichverteilte Zufallszahl zwischen 0..9.
}

Code 0-1: Zufallszahlen erzeugen.

int MODE=0;

public void zeichneStartbildschirm()
{
    ...
}

public void draw()
{
    switch(MODE)
    {
        case 0:
            zeichneStartbildschirm();
        break;
        case 1:
            ...
        break;
        ...
        default:
        break;
    }
}

Code 0-2: Die switch-Anweisung, um zwischen Modi zu unterscheiden.

Auf Pfeiltasten reagieren -- https://processing.org/reference/keyCode.html
Sound-Ereignisse (Kür) -- https://processing.org/reference/libraries/sound/SoundFile_play_.html

public void mouseClicked()
{
    if(mouseX>550 && mouseY<100 && MODE==0) //Beispiel
    {
        MODE=1;
    }
}

Code 0-3: Bei Tastendruck starten

ÜBUNG
  • Bilden Sie ein mehrköpfiges Entwicklerteam.
  • Setzen Sie ein Grundkonzept des Spiels mit Processing um.
  • Überlegen Sie sich Verbesserungen und Verfeinerungen des Spiels und ergänzen diese, Beispiele:
  • Highscore -- Laden und Speichern des bislang höchsten Punktestandes in einer Datei.
  • Spielfiguren mit Gesicht.
  • Sound (Lauf- und Fressgeräusche)
  • Feste mögliche Wege (Labyrinth)
  • ...
ENTWICKLUNGSSCHRITTE
Spiel001, nur das Raster und die Matrix
int[][] mat = new int[11][11];

public void setup()
{
    size(750,550);
    frameRate(30);
    mat[5][5] = 1;
    mat[0][0] = 3;
    mat[0][1] = 2;
}

public void draw()
{
    background(255);
    float kante = 50;
    for(int i=0;i<mat.length;i++)
    {
         for(int k=0;k<mat[i].length;k++)
         {
              noFill();
              stroke(0);
              rect(k*kante,i*kante,kante,kante);
         }
    }
}

Code 0-4: Spiel001, nur das Raster und die Matrix.

Spiel002, Spielfiguren sichtbar
int[][] mat = new int[11][11];

public void setup()
{
    size(750,550);
    frameRate(30);
    mat[5][5] = 1;
    mat[0][0] = 3;
    mat[0][1] = 2;
}

public void draw()
{
    background(255);
    float kante = 50;
    for(int i=0;i<mat.length;i++)
    {
         for(int k=0;k<mat[i].length;k++)
         {
              noFill();
              stroke(0);
              rect(k*kante,i*kante,kante,kante);
              if(mat[i][k]%10==1)
              {
                  fill(0,0,255);
                  noStroke();
                  ellipse(k*kante+kante/2,i*kante+kante/2,kante,kante);
              }
              else if(mat[i][k]%10==2)
              {
                  fill(0,255,0);
                  noStroke();
                  ellipse(k*kante+kante/2,i*kante+kante/2,kante/2,kante/2);
              }
              else if(mat[i][k]%10==3)
              {
                  fill(255,0,0);
                  noStroke();
                  triangle(k*kante+kante/2,i*kante,k*kante,(i+1)*kante,(k+1)*kante,(i+1)*kante);
              }
         }
    }
}

Code 0-5: Spiel002, Spielfiguren sichtbar.

Spiel003, Spieler bewegen
int[][] mat = new int[11][11];
int x_spieler, y_spieler;
public void setup()
{
    size(750,550);
    frameRate(30);
    x_spieler = 5;
    y_spieler = 5;
    mat[y_spieler][x_spieler] = 1;
    
    mat[0][0] = 3;
    mat[0][1] = 2;
}

public void draw()
{
    background(255);
    float kante = 50;
    for(int i=0;i<mat.length;i++)
    {
         for(int k=0;k<mat[i].length;k++)
         {
              noFill();
              stroke(0);
              rect(k*kante,i*kante,kante,kante);
              if(mat[i][k]%10==1)
              {
                  fill(0,0,255);
                  noStroke();
                  ellipse(k*kante+kante/2,i*kante+kante/2,kante,kante);
              }
              else if(mat[i][k]%10==2)
              {
                  fill(0,255,0);
                  noStroke();
                  ellipse(k*kante+kante/2,i*kante+kante/2,kante/2,kante/2);
              }
              else if(mat[i][k]%10==3)
              {
                  fill(255,0,0);
                  noStroke();
                  triangle(k*kante+kante/2,i*kante,k*kante,(i+1)*kante,(k+1)*kante,(i+1)*kante);
              }
         }
    }
}

void keyPressed() 
{
  if (key == CODED) 
  {
    int x_alt = x_spieler;
    int y_alt = y_spieler;
    if (keyCode == UP) 
    {
        y_spieler++;
        mat[y_spieler][x_spieler] = mat[y_alt][x_alt];
        mat[y_alt][x_alt] = 0;
    } 
    else if (keyCode == DOWN) 
    {
        y_spieler--;
        mat[y_spieler][x_spieler] = mat[y_alt][x_alt];
        mat[y_alt][x_alt] = 0;
    } 
    else if (keyCode == LEFT) 
    {
        x_spieler--;
        mat[y_spieler][x_spieler] = mat[y_alt][x_alt];
        mat[y_alt][x_alt] = 0;
    } 
    else if (keyCode == RIGHT) 
    {
        x_spieler++;
        mat[y_spieler][x_spieler] = mat[y_alt][x_alt];
        mat[y_alt][x_alt] = 0;
    } 
  } 
  else 
  {
  }
}

Code 0-6: Spiel003, Spieler bewegen.

Informatik -- Lehrveranstaltung vom 24.06.2024

Themen

  1. Objektorientierte Entwicklung eines Arcade-Games
  2. Beantwortung von Fragen zur anstehenden Klausur

1. Objektorientierte Entwicklung eines Arcade-Games

Nachdem in der vornagegangenen Lehrveanstaltung einige Erfahrung in der Entwicklung eines Spiels auf prozeduraler Ebene gewonnen wurde, soll nun das gleiche Spiel mit einem objektorientierten Ansatz entwickelt werden.

Dazu wird vorgeschlagen, eine Klasse "Spiellogik" ins Zentrum zu stellen, die das Spiel in seinem Ablauf und seinen Daten repräsentiert, jedoch keinerlei Visualisierung besitzt.

Um um ein Objekt vom Typ Spiellogik dann Benutzerinteraktion und Visualisierung drum herum aufzubauen, ergeben sich gewisse Anforderungen an die Klasse Spiellogik:

Es muss eine Reihe von Prüfmethoden geben, die zurück geben, ...

  • ...ob der Spieler verloren hat,
  • ...ob der Spieler gewonnen hat,
  • ...ob das Spiel läuft,
  • ...ob ein bestimmter Zug des Spielers möglich ist. Diese könnte aber direkt in "bewegeSpieler()" integriert sein, vergl. weiter unten.

Das Spiel muss in einen Anfangszustand versetzt werden können und das Spiel sollte dann gestartet werden. Entsprechende Methoden könnten heißen:

  • initialisieren()
  • starten()

Die beweglichen Gegner sollten über eine Methode von ihrer aktuellen Position aus in eine nachfolgende übergehen. Eine entsprechende Methode könnte heißen:

  • bewegeGegner()

Schließlich sollte es eine Methode geben, die einen Zug des Spielers ermöglicht. Diese könnte so beschaffen sein, dass sie nur vollzogen wird, wenn der gewünschte Schritt möglich ist. Dann sollte sie true zurück geben, sonst false. Die möglichen Richtungen sollten durch Zahlen kodiert sein: nach rechts 1, nach links 2, nach oben 3, nach unten 4:

  • boolean bewegeSpieler(int richtung)

Insgesamt ergibt sich für die Klasse folgender vorläufiger Entwurf:

public class Spiellogik
{
    int[][] mat;//Spielbrett
    int punkte;
    boolean verloren,gewonnen,laeuft;
    public Spiellogik(int kante, int anzahl_gegner, int anzahl_essen)
    {
        mat = new int[kante][kante];
    }

    public void initialisieren()
    {
        punkte = 0;
        gewonnen = false;
        verloren = false;
        laeuft = false;
    }

    public void starten()
    {
        laeuft = true;
    }

    public void bewegeGegner()
    {
    }

    public boolean bewegeSpieler(int richtung)
    {
    }

    public boolean spielerHatGewonnen()
    {
       //alle Essen-Objekt sind weg, aber der Spieler ist noch da.
    }

    public boolean spielerHatVerloren()
    {
      //Der Spieler ist weg (gefressen von einem Gegner)
    }

    public boolean spielLaeuft()
    {
    }
}

Code 0-7: (Unvollständiger) Entwurf der Klasse Spiellogik.


Neben den aufgeführten public-Methoden, kann es eine Reihe an private-Hilfsmethoden in Spiellogik geben, wie beispielsweise: private boolean koordinatenSindImSpielfeld(int zeile, int spalte).


Interaktion

  • Die Interaktion läuft über einen Start-Knopf, um das Spiel beginnen zu können und mit den Pfeil-Tasten, um den Spieler zu bewegen.
  • Wird der Start-Knopf gedrückt, so wird die Objektmethode starten() im Siellogik-Objekt aufgerufen.
  • Die Methode initialisieren() könnte dagegen beim Aufruf des Programms aufgerufen werden und danach schon am Ende jedes Spiels.

Visualisierung

  • Indem für jeden Spielfigurentyp eine eigene Klasse geschrieben wird, kann jedes Objekt ein wenig anders aussehen und es werden auch komplexere Darstellungen handhabbar.
  • Die Klassen könnten folgendermaßen heißen:
  • VisSpieler
  • VisEssen
  • VisGegner

Eine fortgeschrittene Umsetzung könnte ein Interface iVis vorsehen, in dem festgelegt ist, dass alle Vis... Objekte eine Methode draw(...) besitzen. Alle diese Visualisierungsobjekte könnten dann ein einem Array gemerkt werden.

Schließlich sollte es eine Klasse Spielfeld geben, die in der Lage ist, die Spielsituation auf der Grundlage des Siellogik-Objektes darzustellen. Das Spielfeld-Objekt sollte als innere Objekte die Vis... Objekte besitzen.

Kodierung der Spielfiguren und -elemente in int[][] mat

  • Ein leeres Feld erhält die Nummer 0.
  • Der Spieler erhält die Nummer 1.
  • Die Essenssachen erhalten 2,12,22,32,42,...
  • Die Gegner erhalten die Nummern 3,13,23,33,43,...

Es sollen im Unterricht zunächst gemeinsam die folgenden Entwicklungsschritte angeschaut werden:


ArcadeOOP001.zip
ArcadeOOP002.zip
ÜBUNG
AUFGABE 13.1 -- Implementierung der Methode bewegeGegner()

Sorgen Sie zuerst als Vorübung dafür, dass immer die aktuelle Punktzahl angezeigt wird.

Die nachfolgende einfache objektorientierte Version des Arcade-Games ist fast vollständig:

ArcadeOOP003uebung.zip

Lediglich die Objektmethode bewegeGegner() in der Klasse Spiellogik muss noch implementiert werden.

  • Analysieren Sie die Programmteile dieses Projektes.
  • Schauen Sie sich die Hinweise zu bewegeGegner() im Projekt an und versuchen Sie Methode zu implementieren.
  • Hinweis: Schauen Sie sich auch den Aufruf der Methode in der draw()-Methode des Hauptprogramms an.
AUFGABE 13.2 -- Spiel "aufpeppen"

Im Sinne der letzte Woche und heute gemachten Angaben soll die bereit gestellte Version des Spiels nun in freier Regie verbessert und verfeinert werden:

  1. Verbessern Sie die grafische Darstellung der Spielfiguren- und elemente. Versuchen Sie hier auch Gebrauch von objektorientierten Ansätzen zu machen.
  2. Verbessern Sie die Darstellung der Spielzustände (Startbildschirm / Darstellung, wenn der Spieler gewonnen oder verloren hat).
  3. Speichern und Laden Sie den Highscore (bisher maximal erreichte Punktezahl). Nutzen Sie dazu die Methoden String[] zeilen = loadStrings(String name) und saveStrings(String name, String[] zeilen), sowie int x = Integer.parseInt(String zahl_als_string).
  4. Neben der Punktezahl kann auch das Messen der Dauer bis zum Gewinnen die Spieler motivieren. Ergänzen Sie auch diesen Aspekt. Nutzen Sie long systemzeit_in_millisekunden = System.currentTimeMillis();
  5. Modulieren Sie das Tempo der Gegner in Abhängigkeit davon, wie gut der Spieler ist.
  6. Die Momente des Fressens und Gefressen-werdens könnten eindrücklicher dargestellt werden.
  7. Schauen Sie sich die Möglichkeiten von Sound auf den Processing-Seiten an und versuchen Sie etwas Sound zu ergänzen.

Mailen Sie mir gerne Ihre Varianten, um sie hier als studentische Lösungen bereit stellen zu können.


AUFGABE 13.3 -- eigenes Spiel

Überlegen Sie sich auf der Grundlage der bisher gemachten Erfahrungen ein eigenes Spiel, vielleicht eines, das auch für zwei Personen funktioniert.

ArcadeOOP006.zip -- Beispiellösung mit Sound, "Gesichtern", Erhöhung der Gegnerzahl und der Bewegungsgeschwindigkeit nach einem Gewinn.
Screenshot zu ArcadeOOP006.zip.

Bild 0-2: Screenshot zu ArcadeOOP006.zip.

2. Beantwortung von Fragen zur anstehenden Klausur

    public class Zahl
    {
        protected int x;
        public void setX(int x)
        {
            this.x = x;
        }
    }
    //Klasse ZahlB
    public class ZahlB extends Zahl
    {
        public int getX()
        {
            return this.x;
        }
    }
                                            
    public void setup()
    {
        Zahl z1 = new Zahl();
        z1.setX(7);
        ZahlB z2 = new ZahlB();
        z2.setX(5);
        System.out.println("z2="+z2.getX());
    }

    public void draw()
    {
    }

Code 0-8: Minimales Beispiel für Vererbung.

Zugehöriges UML-Klassendiagramm.

Bild 0-3: Zugehöriges UML-Klassendiagramm.