Sitzung: Jeden Freitag in der Vorlesungszeit ab 16 Uhr c. t. im MAR 0.005. In der vorlesungsfreien Zeit unregelmäßig (Jemensch da?). Macht mit!

Javakurs/Übungsaufgaben/Doom

ASCII-DOOM

In Ascii-Doom geht es darum, ein kleines Spiel zu implementieren, mit ASCII Grafiken. Es wird möglich sein, mit einer Spielfigur auf dem Spielfeld zu laufen, später könnt ihr auch Items, Waffen und Liv Tyler einbauen! Wir werden Klasse für Klasse das Spiel implementieren.



class GameMain Teil 1

  • Die Klasse GameMain ist die Klasse, die die main Methode enthalten wird und somit ausführbar ist.
  • Sie wird die Schnittstelle zur Außenwelt sein und die Ausgabe auf die Konsole tätigen.
  • Erstellt zunächst nur diese Klasse. Sobald ihr Klassen aus den späteren Aufgaben implementiert habt, könnt ihr in der main-Methode ihre Funktionalität testen.



class Position Teil 1

  • Position repräsentiert eine Position auf einem 2D-Spielfeld.
  • Enthält private Attribute x und y, gekapselt mit gettern/settern.
  • Implementiert eine equals-Methode, die 2 Positions vergleicht.
  • TESTET euer equals!




class GameTile Teil 1

  • GameTile repräsentiert ein Tile (ein Feld) im späteren Spiel.
  • Enthält eine private Variable, die für den Typ des Feldes steht (am besten ein int).
    • 1 entspricht wand
    • 2 entspricht leer
  • Also auch hier braucht ihr Getter und Setter.
  • Enthält außerdem eine private Variable vom Typ Position, welche die Position des GameTiles auf dem Feld speichert.
  • Implementiert eine toString-Methode, die jedem GameTile einen String mit Länge 1 zuordnet.
    • wand = "x"
    • leer = " "
  • TESTET euer toString.


class GameField Teil 1

  • GameField ist eine zentrale Klasse in diesem Spiel, sie repräsentiert das Spielfeld als Ganzes und besteht aus GameTiles.
  • Enthält eine private Variable für das Level (2D-Array aus GameTiles).
  • Über den Konstruktor wird ein String dieser Form übergeben(ebenso seine Breite und Höhe):
       String s = "########" +
		  "#      #" +
		  "#      #" +
		  "###    #" +
		  "#      #" +
		  "########";
    • Es soll mit einer Schleife der String Zeichen für Zeichen ausgelesen werden und aus jedem Zeichen ein neues GameTile (fest oder leer) erzeugt werden. Diese GameTiles werden dann in dem Array gespeichert!
    • Wie man Strings Zeichen für Zeichen ausliest, findet ihr in der API (java.lang.String).
    • Implementiert ein toString, dieses soll folgenden String liefern:
		  12345678
		1 ######## 
		2 #      #
		3 #      #
		4 ###    #
		5 #      #
 		6 ########
    • Tipp: "\n" steht in einem String für Zeilenumbruch.
    • Es soll also aus dem GameField-Array wieder ein String generiert werden.
    • Die toString-Methode von GameField soll die toString Methode von GameTile verwenden, um diese Ausgabe zu erzeugen.
  • TESTET euer Programm mit verschiedenen Levels



class World Teil 1

  • World ist die Klasse, die alles verbinden wird, Spieler, Items, Spielfeld etc.
  • Zunächst einmal gibt es nur das GameField, was wir einbinden können (später der Spieler).
  • So muss es auch ein Variable vom Typ GameField geben.
  • Die Main Klasse erzeugt ab jetzt nur noch eine Instanz von World nicht mehr von GameField. Denn World selbst soll sich das GameField erzeugen (im Konstruktor).
  • Bei Java funktioniert es bei grafischen Anwendungen etwa so: Man hat ein Objekt, das eine Leinwand repräsentiert (Canvas), man darf auf diesem Objekt Sachen malen. In einem Spiel wird das Objekt in jedem Frame leer gemacht, danach werden alle beteiligten grafischen Objekte (Spieler, Spielfeld, Gegner) nacheinander auf diese Leinwand gemalt. Letzendlich wird die Leinwand auf dem Bildschirm ausgegeben. Das hat den Vorteil, dass jede Klasse die darauf Zugriff bekommt, irgendetwas malen kann ohne zu wissen, was bereits darauf ist oder noch darauf kommt. (Jedes Objekt kümmert sich selbst um seine grafische Entsprechung.)

Das werden wir hier auch machen, allerdings mit Strings, da uns die Grundlagen für Grafisches noch fehlen.

  • Definiert eine solche Canvas Variable, die für die grafische Repräsentierung des Spielfelds stehen wird.
    • Das soll ein 2D-Array aus Strings der Länge 1 sein. Ein String pro Feld, dieses enthält dann "#" oder " ".



class GameField Teil 2

  • Wir brauchen nun eine draw-Methode, die eine solche Leinwand (String[][]) aus World übergeben bekommt und sich selbst darauf malt. Diese Methode funktioniert nun ähnlich wie unsere toString-Methode, sie gibt nur nichts zurück, sondern modifiziert direkt unser Leinwand-Array und malt dort Zeichen für Zeichen die Felder rein. (Arrays sind keine primitiven Datentypen, daher wird hier nicht mit einer Kopie gearbeitet)
public void draw( String[][] canvas )
 
  • Was passiert eigentlich, wenn das GameField größer ist als unser String[][]?



class World Teil 2

  • Nun kommt unsere wichtigste Methode public void draw(). Draw soll alle Inhalte (bisher nur GameField) auf unser Canvas-Objekt malen, indem es die draw-Methode von GameField aufruft und das Canvas-Array übergibt. Der Code beschränkt sich hier also auf eine Zeile.
    • Später fügen wir hier mehr ein.
  • Implementiert auch eine toString-Methode, diese sollte das aktuelle Canvas-Array in der richtigen Form für die Konsolenausgabe liefern.
  • TESTET nun eure Grafik Ausgabe und euer toString().



class GameMain Teil 2

  • Die Klasse GameMain enthält als einzige eine Instanz der Klasse World und kann somit das Spielfeld zeichnen.
  • Sofern ihr beim Testen versucht habt, das Spielfeld mehrmals auf die Konsole auszugeben, seht ihr, dass das alte Spielfeld noch immer sichtbar ist über dem neuen. Das sieht irgendwie blöd aus.
  • Zählt nach, wieviele Zeilen euere Konsole hat.
  • Bevor man das Level ausgibt, kann man soviele leere Zeilen ausgeben lassen, dass das alte Level nicht mehr sichtbar ist. System.out.println(""); gibt eine leere Zeile aus.
  • TESTEN ob das klappt und gut aussieht




class Player Teil 1

  • Der Spieler wird die Möglichkeit haben, auf dem Spielfeld rumzulaufen und je nachdem, wie weit ihr kommt, noch viel mehr.
  • Der Spieler besitzt eine Position (Variable vom Typ Position), wie immer private mit Getter/Setter
  • Der Spieler wird durch ein "*" auf dem Spielfeld repräsentiert. Implementiert eine draw-Methode, die genau so wie die draw-Methode aus GameField den Spieler auf unsere Leinwand malt, an die Position die in der position-Variable gepseichert ist.
  • Player soll auch vier Methoden haben
    • public void moveLeft()
    • public void moveRight()
    • public void moveUp()
    • public void moveDown()
  • Welche einfach nur die X- oder Y-Position um 1 erhöhen oder dekrementieren.




class World Teil 3

  • Im Konstruktor soll nun auch eine Spielerinstanz erzeugt werden und zunächst an einer bel. Position gesetzt werden.
  • Erweitert die draw-Methode so, dass nun auch der Spieler angezeigt wird.
  • TESTET ob das funktioniert. Was passiert eigentlich, wenn man den Spieler an der Position -1 | -1 setzt ?




class World Teil 4

  • Implementiert eine Methode public boolean onKeyPressed( String key ){ ... }. Sie wird dann aufgerufen werden, wenn eine Taste gedrückt wurde und soll true zurückgeben, wenn der Spieler sich bewegt hat, ansonsten false.
  • Zunächst müssen wir in onKeyPressed prüfen, ob key einer der Strings "a" (links) , "s"(unten) , "d"(rechts) , "w"(oben) ist (equals benutzen!). Falls ja, wurde schonmal sicherlich eine gültige Taste gedrückt. Falls nein, können wir false zurückliefern.
  • Wir müssen als nächstes prüfen, ob der Spieler durch die Bewegung nicht in eine Mauer reinläuft, dazu müssen wir die Spielerposition vergleichen und schauen, in welchem Tile er stehen würde, wenn wir rechts/links/hoch/runter liefen. Sofern dieses Tile leer ist, dürfen wir in diese Richtung laufen.
  • Nun wo wir das alles geprüft haben, bewegen wir den Spieler mit einer der vier Bewegungsfunktionen und geben true zurück.
  • TESTET unbedingt manuell diese Methode, sie muss korrekt funktionieren!




class GameMain Teil 2

  • Nun kommt alles zusammen.
  • Fuer die Eingabe von der Konsole benutzt wieder die Terminal Klasse.
  • Es muss geprueft werden ob es sich um ein gueltiges Zeichen handelt und ob es eines der Zeichen a,s,w,d ist.
  • Das eingegebene Zeichen soll dann an die World Klasse uebergeben werden und diese soll den Spieler bewegen, falls dies moeglich ist!




  • Nun kann der Spieler im Level rumlaufen und es gibt eine Art Interaktion. Das ist das Basisgerüst für ein Spiel, ihr könnt nun weitere Sachen einfügen wie z.B. Items, die man einsammeln kann. Wie könnte man das machen? Was hat Liv Tyler in diesem Spiel für eine Aufgabe?

Kommentare

Wenn du Anmerkungen zur Aufgabe hast oder Lob und Kritik loswerden möchtest, ist hier die richtige Stelle dafür. Klicke einfach ganz rechts auf "bearbeiten" und schreibe deinen Kommentar direkt ins Wiki. Keine Scheu, es geht nichts kaputt ;)

AB: ich mahce gerade diese Aufgabe als Übung und kann nur sagen, dass sie wirklich vorbildlich ist, wie auch der rest der Aufgaben...die Tutoren und Professoren für MPGI2 sollten sich mal eine scheibe abschneiden :P


Vielleicht sollte man noch angeben, wie groß die Dimension des Spielfeld-Arrays sein soll. Zum Beispiel:
private GameTile[][] level = new GameTile[10][10];
- Michael

Hallo, ich hab ein paar Fragen zu den Erklärungen:

World Teil 1 "Die Main Klasse erzeugt ab jetzt nur noch eine Instanz von World nicht mehr von GameField. Denn World selbst soll sich das GameField erzeugen (im Konstruktor)."

Erstelle ich jetzt in meinem World-Konstruktor ein neues GameField? Wenn ja, wie kann ich das denn weiter Benutzen? Also, um eine Draw-Methode aufrufen.

World Teil 2 "Draw soll alle Inhalte (bisher nur GameField) auf unser Canvas-Objekt malen, indem es die draw-Methode von GameField aufruft und das Canvas-Array übergibt. Der Code beschränkt sich hier also auf eine Zeile."

Wie kann ich hier die Draw-Methode von GAmeField aufrufen. Dazu bräuchte ich doch erste mal ein GameField. z.B.: XYGameField.draw(MyCanvasArray) ??

würde mich über Antworten freuen. nico



Ich arbeite auch an der Aufgabe und denke mir dazu folgendes:

1) ja du erstellst im Konstructor von World ein Gamefield. Um das weiter zu benutzen muss deine Klasse auch eine Variable vom Typ gamefield haben. zB. "GameField newField;"

2) mit 1. dann recht leicht "newField.draw(canvas);"

So habe ich die Aufgabe zumindest verstanden. Hoffe (und denke) das ist so richtig.

Eric