(Die spielsteuernde Klasse)
(Das Grafiksystem)
Zeile 127: Zeile 127:
 
[[Datei:zeichenebene_mitpunkt.jpg|right|thumb|200px|Zeichenebene mit Punkt (2|4)]]
 
[[Datei:zeichenebene_mitpunkt.jpg|right|thumb|200px|Zeichenebene mit Punkt (2|4)]]
  
Die einfachste solche Klasse ist die Klasse [[Klasse:Punkt|<code>Punkt</code>]]. Diese beschreibt einen Punkt auf der Zeichenebene und wird vor Allem bei Positionsangaben verwendet.
+
Die einfachste solche Klasse ist die Klasse [[Dokumentation/ea/Punkt|<code>Punkt</code>]]. Diese beschreibt einen Punkt auf der Zeichenebene und wird vor Allem bei Positionsangaben verwendet.
  
 
Sie hat 2 Attribute, je ein <code>int</code>-Attribut für die X- und Y-Koordinate.
 
Sie hat 2 Attribute, je ein <code>int</code>-Attribut für die X- und Y-Koordinate.
Zeile 143: Zeile 143:
 
[[Datei:zeichenebene_vektorverschiebung.jpg|right|thumb|200px|Vektor/Verschiebung als praktische Angabe]]
 
[[Datei:zeichenebene_vektorverschiebung.jpg|right|thumb|200px|Vektor/Verschiebung als praktische Angabe]]
  
Neben einem bestimmten Punkt auf der Zeichenebene kann man auch eine Bewegung auf der Zeichenebene um ein Δx und ein Δy beschreiben. Dies macht die Klasse [[Klasse:Vektor|<code>Vektor</code>]].
+
Neben einem bestimmten Punkt auf der Zeichenebene kann man auch eine Bewegung auf der Zeichenebene um ein Δx und ein Δy beschreiben. Dies macht die Klasse [[Dokumentation/ea/Vektor|<code>Vektor</code>]].
Diese hat, genau wie die Klasse [[Klasse:Punkt|<code>Punkt</code>]], zwei <code>int</code>-Attribute für ihr Δx und Δy.
+
Diese hat, genau wie die Klasse [[Dokumentation/ea/Punkt|<code>Punkt</code>]], zwei <code>int</code>-Attribute für ihr Δx und Δy.
  
 
Erstellt wird der Vektor (-3|-2) so:
 
Erstellt wird der Vektor (-3|-2) so:
Zeile 156: Zeile 156:
  
 
Neben Punkten und einfachen Bewegungen kann auf der Zeichenebene auch eine rechteckige Fläche beschrieben werden, deren Seiten immer parallel zu den Achsen sind.
 
Neben Punkten und einfachen Bewegungen kann auf der Zeichenebene auch eine rechteckige Fläche beschrieben werden, deren Seiten immer parallel zu den Achsen sind.
So eine Fläche wird durch ein Objekt der Klasse [[Klasse:BoundingRechteck|<code>BoundingRechteck</code>]] beschrieben und ist definiert durch die X- und Y-Koordinate der linken oberen Ecke sowie  seiner Breite und Höhe.
+
So eine Fläche wird durch ein Objekt der Klasse [[Dokumentation/ea/BoundingRechteck|<code>BoundingRechteck</code>]] beschrieben und ist definiert durch die X- und Y-Koordinate der linken oberen Ecke sowie  seiner Breite und Höhe.
  
 
<div class="hinweisInfo">
 
<div class="hinweisInfo">
Zeile 167: Zeile 167:
 
BoundingRechteck br = new BoundingRechteck(2, 1, 3, 5);
 
BoundingRechteck br = new BoundingRechteck(2, 1, 3, 5);
 
</source>
 
</source>
 
  
 
== Grafik und die Klasse <code>Raum</code> ==
 
== Grafik und die Klasse <code>Raum</code> ==

Version vom 13. April 2014, 01:02 Uhr


Herzlich Willkommen! Du bist an die Engine Alpha gekommen und ich freue mich, dass du versuchen möchtest, hiermit deine eigenen Spiele zu realisieren. Dieser Wiki soll dich bei deiner Arbeit mit der Engine begeleiten durch die Bereitstellung der Dokumentation und Tutorials. Das erste Tutorial liegt vor dir!


Ziel des Tutorials

Hier ist der Beginn deines Abenteuers; dieses Tutorial hilft dir, die Grundlagen der Engine zu verstehen und anzuwenden. Wenn du mehr lernen willst, kannst du auf Basis des Wissens, was du hier erwerben wirst, eines der anderen Tutorials zu einem bestimmten Thema durcharbeiten.

Am Ende dieses Tutorials weißt du bereits, wie du ein Spiel mit der Engine grundlegend erstellst, wie die Grafik der Engine grundlegend funktionert und wie du einfache Grafiken erstellst.

Du benötigst ab hier keine Vorkenntnisse bezüglich der Engine. Jedoch solltest du folgendes beachten:


Die spielsteuernde Klasse

Es ist sinnvoll, dass bei einem Spiel immer eine Klasse "die Fäden in der Hand hat". Dies ist die spielsteuernde Klasse.

Mit dieser Klasse fängt jedes Spiel an, dies ist also der wichtigste Schritt!


Diese Klasse muss sich aus der Klasse Game in der Engine Alpha ableiten (Stichwort Vererbung). Hierdurch wird die Superklasse Game bereits in ihrem Konstruktor alles tun, was notwendig ist, um die Spielgrundlagen fertig einzurichten. Hierin wird das Spielfenster erstellt, und die wichtigen internen Vorgänge der Engine gestartet.


Weiterhin hat die Klasse Game eine abstrakte Methode:

/**
 * Diese Methode wird immer dann aufgerufen, wenn eine taste gedrueckt wurde.
 * @param tastenCode Der Zahlencode der Taste, die gedrueckt wurde, dadurch ist 
 * jeder Tastendruck eindeutig zuzuordnen
 */
public abstract void tasteReagieren(int tastenCode)


Der Rumpf dieser Methode wird nun in Deiner eigenen Spielklasse geschrieben. Diese Methode wird immer dann automatisch aufgerufen, wenn eine Taste auf der Tastatur gedrückt wird, die auch in der Engine Alpha verarbeitet wird. Jede Taste hat einen Zahlencode, der die herunter gedrückte Taste eindeutig identifiziert und dadurch kann in dieser Methode jeder Tastendruck gesondert behandelt werden, obwohl nur eine Methode benötigt wird. Welcher Code für welche Taste steht kannst du in dieser Tabelle einsehen.

Hier gibt es noch was zu tun: Tabelle für Tasten (Handbuch letzte Seite) plus Erläuterung

Die ganze Spielklasse sieht vom Quelltext her ungefähr so aus:

import ea.*; //Importieren der gesamten Engine Alpha

/**
 * Die Spielsteuerungsklasse fuer mein Spiel
 */
public class MeinSpiel

extends Game
{

    /**
     * Konstruktor, erstellt das Spielfenster und alle Hintergrundressourcen
     * in der Klasse <code>Game</code>
     */
    public MeinSpiel() {
        super(400, 300); //Aufruf des Konstruktors der Klasse Game; 
        //erstellt ein Fenster der Breite 400 und Hoehe 300
    }

    /**
     * Diese Methode wird immer dann aufgerufen, wenn eine der Tasten der
     * Tastatur gedrueckt wurde.
     * @param   tastenCode  Der Code der gedrueckten Taste als Zahl. Dadurch 
     *  kann man die Tasten unterscheiden un entsprechend auf darauf reagieren.
     *  Zum Beispiel mit einer <code>switch</code>-Anweisung
     */
    public void tasteReagieren(int tastenCode) {
        //Verarbeitung der Tastenbefehle, z.B. mit der switch-Anweisung
    }
}


Die Klasse, die sich aus der Klasse Game ableitet, wird im folgenden sehr oft als die "spielsteuernde Klasse" bezeichnet.

Damit ist die eigene Spielklasse bereits grundlegend fertig. Alles weitere wird in den folgenden Kapiteln behandelt.

Solltest du bis hier Schwierigkeiten haben, kannst du dir ein Beispielprojekt herunterladen und damit ein Gefühl für die spielsteuernde Klasse gewinnen.

Das Grafiksystem

Im folgenden erkläre ich das Grafik-System der Engine. Denn die Grafik ist letzten Endes einer der wichtigsten Bereiche bei der Spieleprogrammierung.


Die Zeichenebene

Hierfür erkläre ich Dir kurz das grundlegende Konzept hinter der Grafik.

Alle Grafikelemente liegen auf der Zeichenebene. Die kannst Du Dir vorstellen wie ein Blatt Papier, auf das die verschiedenen Grafiken gemalt werden. Nur ist dieses Papier in alle Richtungen unendlich groß, so schnell sollte also kein Platzmangel herrschen.

Die Position eines Punktes auf der Zeichenebene lässt sich, wie auch in der Mathematik, durch ein Koordinatensystem definieren, wobei hier allerdings die Y-Achse nach unten geht und der Ursprung links oben ist.

Weiterhin sollte Dir klar sein, dass die Einheiten in diesem System minimal winzig sind! Sämtliche Längeneinheiten werden in Pixel gerechnet, also einer winzig kleinen Einheit. Angegeben werden Punkte und Maße natürlich immer wie im Mathematikunterricht: Als erstes die X-, dann die Y-Koordinate.


Die Klassen der Zeichenebene

Für diese Zeichenebene gibt es besondere, nicht grafische Klassen, mit denen Sachverhalte auf der Zeichenebene einfach beschrieben werden können. Vieles davon könnte wieder dir bekannt aus der Mathematik kommen.


Die Klasse Punkt

Datei:zeichenebene mitpunkt.jpg
Zeichenebene mit Punkt (2|4)

Die einfachste solche Klasse ist die Klasse Punkt. Diese beschreibt einen Punkt auf der Zeichenebene und wird vor Allem bei Positionsangaben verwendet.

Sie hat 2 Attribute, je ein int-Attribut für die X- und Y-Koordinate.

Erstellt wird der Punkt (2|4) so:

Punkt punktAn2_4 = new Punkt(2, 4);


Die Klasse Vektor

Datei:zeichenebene mitvektor.jpg
Zeichenebene mit Vektor (-3|-2)
Datei:zeichenebene vektorverschiebung.jpg
Vektor/Verschiebung als praktische Angabe

Neben einem bestimmten Punkt auf der Zeichenebene kann man auch eine Bewegung auf der Zeichenebene um ein Δx und ein Δy beschreiben. Dies macht die Klasse Vektor. Diese hat, genau wie die Klasse Punkt, zwei int-Attribute für ihr Δx und Δy.

Erstellt wird der Vektor (-3|-2) so:

Vektor verschiebung = new Vektor(-3, -2);


Die Klasse BoundingRechteck

Neben Punkten und einfachen Bewegungen kann auf der Zeichenebene auch eine rechteckige Fläche beschrieben werden, deren Seiten immer parallel zu den Achsen sind. So eine Fläche wird durch ein Objekt der Klasse BoundingRechteck beschrieben und ist definiert durch die X- und Y-Koordinate der linken oberen Ecke sowie seiner Breite und Höhe.

Diese Klasse wird jedoch vor allem intern benutzt und ist daher nicht unbedingt nötig für Dich. Du kannst die Erklärung dieser Klasse ruhig überspringen.

Erstellt wird dieses BoundingRechteck so:

BoundingRechteck br = new BoundingRechteck(2, 1, 3, 5);

Grafik und die Klasse Raum

Nun ist alles abgehandelt, was man zur Zeichenebene, dem „Blatt Papier“ wissen muss, und jetzt soll endlich auch etwas auf das Papier kommen! Nun brauchen wir also Klassen, die gezeichnet werden können. Gezeichnet werden können vor Allem:

  • Texte
  • Geometrische Figuren
  • Bilder

Das sind die elementaren grafischen Elemente der meisten Computerspiele. Und sie haben einiges gemeinsam. Und spätestens hier klingelt das Glöckchen im Informatikerhinterkopf: "Hier kann man Vererbung nutzen!"

Genau das geschieht hier: Alle grafischen Klassen leiten sich aus einer Superklasse ab, die bereits alle wichtigen Eigenschaften für die grafischen Spielelemente definiert.

Diese Klasse ist die Klasse Raum. Sie ist die Vaterklasse aller grafischer Objekte.

Raum definiert neben den Zeichenoperationen einige sehr praktische Methoden, die an jedem Grafik-Objekt ausgeführt werden können:

Hier gibt es noch was zu tun: Tabelle zur Klasse Raum abschreiben

Diese und einige andere Methoden (die du zum Beispiel im Wiki der Klasse Raum nachlesen kannst) lassen sich beliebig auf jede grafische Klasse anwenden, sei es ein Bild, ein Text oder anderes. Denn alle Klassen hierfür leiten sich ja aus Raum ab. Dieser enorme Vorteil bietet auch gleichzeitig z.B. Kollisionstests zweier beliebiger grafischer Objekte.

Diese Klassen werden jetzt kurz mit ihren eigenen, nicht geerbten Eigenschaften behandelt:

Achtung, vorab stelle Ich klar: wenn Du ein Objekt einer solchen Klasse zum Ausprobieren erstellst, wird es nicht im Fenster sichtbar sein! Sei bitte daher nicht enttäuscht oder entmutigt, im nächsten Kapitel lernst Du, Deine Raum-Objekte sichtbar werden zu lassen. Möchtest Du dennoch Deine Raum-Objekte vorzeitig sichtbar machen, binde diese Methode in Deine spielsteuernde Klasse ein und rufe sie nach Bedarf auf:

/**
 * Macht ein beliebiges Raum-Objekt sichtbar.<br />
 * Dank der Vererbungshierarchie koennen ueber 
 * diese Methode Texte wie Bilder und andere 
 * grafische Elemente mit einer Methode behandelt werden.<br />
 * Diese Methode muss noch nicht verstanden werden. Sie wird 
 * im Kapitel 'Knoten' behandelt und erklaert.
 * @param   m   Das sichtbar zu machende Raum-Objekt
 */
public void sichtbarMachen(Raum m) {
    wurzel.add(m);
}

Diese Methode wird im folgenden Kapitel, nach der Einführung der wichtigsten grafischen Klassen, erläutert.

Ich stelle bei den folgenden Klassen nur kurz die wichtigsten Methoden und längsten Methoden vor, doch besonders bei den Konstruktoren muss es nicht immer so kompliziert sein. Nimm die Dokumentation der entsprechenden Klasse zur Hand und lies mit. Du wirst feststellen, dass das Arbeiten mit diesen Klassen so viel einfacher sein kann, wenn man nur die einfachereren Methoden braucht.


Bilder

Bilder sind weit einfacher einzubinden, als du vielleicht denkst. Nur eine einzige wichtige Eigenschaft muss angegeben werden, nämlich der Dateiname der dazugehörigen Bilddatei. Diese Bilddatei legst Du einfach in Deinen Projektordner, und dann ist sie für die Engine Alpha erreichbar.

Hier der grundlegende Konstruktor:

public Bild(	int x,
		int y,
		String verzeichnis)

Eine kurze Erläuterung der Parameter:

x Die X-Koordinate der linken oberen Ecke des Bildes auf der Zeichenebene.
y Die Y-Koordinate der linken oberen Ecke des Bildes auf der Zeichenebene.
verzeichnis Das Verzeichnis des zu ladenden Bildes. Lege das Bild in Deinen Projektordner; Du musst nur den Dateinamen angeben, zum Beispiel "meinBild.jpg".

Das reicht schon, um Deine Bilder einzubinden. Es gibt noch andere interessante Funktionen, wie Bilder vor dem Darstellen auf eine gewünschte Größe skalieren, oder ein Bild in einer Größe auf einer definierten Fläche immer wieder wiederholen. Diese Spielereien lassen sich nach Bedarf in der Dokumentation bestens einsehen.

Du kannst ein einfaches Beispielprojekt herunterladen, das nur ein Bild lädt und anzeigt.


Texte

Hierfür gibt es die Klasse Text. Kurz und knapp: Du kannst ein String-Objekt, also eine Zeichenkette hierdurch grafisch in Dein Spiel bringen. Ein Text hat folgende Eigenschaften:

  • Einen Inhalt
  • Eine bestimmte Schriftgröße
  • Einen bestimmten Font, in dem er dargestellt wird
  • Eine Schriftart (Normal, Fett, Kursiv, oder beides)
  • Eine Farbe

Diese Eigenschaften werden im Konstruktor mitgegeben:

public Text(	String inhalt, 
		int x, 
		int y,
		String fontName,
		int schriftGroesse,
		int schriftart,
		String farbe)

Du musst nicht alle Parameter benutzen, es gibt alternative Konstruktoren, bei denen Du von unten her Parameter weglassen kannst, bis Du nur noch den Inhalt, sowie die X- und Y-Koordinate angeben musst. Möchtest du es ganz genau wissen, kannst du in die Dokumentation der Klasse Text schauen.

Für maßgeschneiderte Texte in Deinem Spiel, erkläre Ich hier alle Parameter des größten Konstruktors:

inhalt Der Inhalt des Textes als Zeichenkette.
x Die X-Koordinate der linken oberen Ecke des Textes auf der Zeichenebene.
y Die Y-Koordinate der linken oberen Ecke des Textes auf der Zeichenebene.
fontName Der Name des Fonts, in dem der Text dargestellt werden soll (zum Beispiel "Times New Roman" oder "Arial").

Du kannst auch eigene Fontdateien (.ttf-Dateien) in Dein Spiel einbinden: Du musst die Datei nur in den Projektordner legen, die Engine wird sie automatisch laden und Du kannst sie dann verwenden, als ob sie auf dem Computer wäre. Hast Du alle gewünschten Schriftarten in deinem Projektordner, solltest Du folgende Quelltextzeile einmal aufrufen:

Text.geladeneSchriftartenAusgeben();

Dann werden Dir an der Konsole alle aus Deinem Projektordner geladenen Schriftarten genannt, inklusive der Namen, unter denen Du sie verwenden kannst.

schriftGroesse Die Schriftgröße des Textes. Wie bei einem Textverarbeitungsprogramm.
schriftart Die Schriftart des Textes, nur Werte zwischen einschließlich 0 und 3 sind möglich:
  • 0: Normaler Text
  • 1: Fetter Text
  • 2: Kursiver Text
  • 3: Fetter & Kursiver Text

Andere Werte hierfür sind nicht möglich!

farbe Die Farbe, in der der Text dargestellt werden soll, als Zeichenkette.

Die Engine Alpha kann eine Reihe von Zeichenketten als Farben interpretieren (z. B. „Gruen“). Welche das sind, wird im Tutorial Farben aufgelistet..


Alle diese Eigenschaften können mit entsprechenden „setzen“-Methoden geändert werden. Du kannst dies und weitere Möglichkeiten in der Dokumentation der Klasse Text einsehen.

Du kannst ein Beispielprojekt herunterladen, das nur einen Text erstellt und ihn etwas modifzieren kann.


Geometrische Figuren

Für geometrische Figuren gibt es in der Engine Alpha jeweils eigene, einfache Klassen. Jede dieser Klassen leitet sich aus der Klasse Geometrie ab, die sich selber aus Raum ableitet.

Diese Klassen sind sehr einfach zu benutzen, die zwei wichtigsten sind wohl die Klassen Kreis und Rechteck.

Klasse Rechteck Kreis
Konstruktor
Rechteck(	int x, 
		int y, 
		int breite,
		int hoehe)
Kreis(	int x, 
	int y,
	int durchmesser)
Methode zum Ändern der Maße
masseSetzen(	int hoehe,
		int breite)
durchmesserSetzen(
	int durchmesser)

Bei sämtlichen Geometrie-Figuren kannst du folgende Methode aufrufen:

public void farbeSetzen(String farbe)

Hiermit kannst du die Füllfarbe einer Geometrie-Figur ändern, zum Beispiel ("Rot", "Cyan" oder "Gelb"). Du kannst auch kompliziertere Farben mit dieser Methode setzen. Dazu erfährst du in einem späteren Tutorial mehr.


Ordnung mit Knoten

Nun kennst Du die wichtigsten grafischen Klassen. Doch wenn Du versucht hast, sie in Dein Spielfenster zu bringen, konntest Du das nur durch die Spezialmethode, die Ich Dir im letzten Kapitel gezeigt habe. Jetzt erfährst du, was dahintersteckt!


Definition der Klasse Knoten

Datei:zeichenebene gruppeohneknoten.jpg
Drei einzelne Spielelemente...
Datei:zeichenebene gruppeohneknoten versch.jpg
...Sie müssen einzelnen verschoben werden.
Datei:zeichenebene gruppemitknoten.jpg
Drei Elemente an einem Knoten...
Datei:zeichenebene gruppemitknoten versch.jpg
...Nur der Knoten muss verschoben werden, und die Bewegung überträgt sich auf alle Elemente.

Dahinter steckt ein spezielles Konzept, auf dem das ganze Grafiksystem basiert, und das, wenn man es einmal durchblickt hat, ein unglaublich zeitsparendes und vorteilhaftes ist.

Dieses System kann man Knoten-System nennen.

Durchhalten, das ist das letzte Kapitel, dann kannst Du bereits Dein eigenes Spiel programmieren!

Knoten ist eine Klasse, die sich aus Raum ableitet. Das besondere an ihr ist jedoch, dass sie kein richtiges sichtbares Grafikelement ist. Sie sammelt andere Raum-Objekte, und überträgt Befehle, die sie ausführen soll, auf alle gesammelten Elemente.

Hier gibt es noch was zu tun: UML-Diagramm: Knoten & Raum

Haben wir also eine Spielfigur, die aus einem Kreis und zwei Rechtecken besteht, können wir diese an einem Knoten sammeln und so zum Beispiel mit einem Verschiebebefehl in einer Quelltextzeile alles erreichen, wofür sonst 3 Programmzeilen nötig werden. Das spart zum einen Programmieraufwand und verhindert zum anderen Flüchtigkeitsfehler, und macht vor allem die gesamte Arbeit wesentlich strukturierter.

Das Prinzip das dahintersteht, heißt auch Composite Pattern.

Die Klasse Knoten verfügt - neben den aus Raum geerbten Methoden und einem parameterlosen Konstruktor – ausschließlich über Methoden zum Organisieren von anderen Raum-Objekten:

Methodenname Parameter Funktion
add raum : Raum Fügt diesem Knoten ein Raum-Objekt hinzu.

Das hinzugefügte Raum-Objekt wird ab sofort mit verschoben, wenn der Knoten verschoben wird.

entfernen raum : Raum Entfernt ein Raum-Objekt von diesem Knoten.

Das entfernte Raum-Objekt wird ab sofort nicht mehr bei Methoden mit aufgerufen.

leeren [keine Parameter] Entfernt alle Raum-Objekte von diesem Knoten. Dann ist der Knoten wieder leer.

Nun weißt Du, wie ein Knoten funktioniert. Dieses System nennt der Informatiker auch hierarchisches Prinzip der Baumstruktur. Es entspricht dem System, nach dem Dateiordner funktionieren. Ein Dateiordner kann jede Art von Datei enthalten, auch einen weiteren Ordner.


Objekte werden sichtbar

Wie Du nun sicher bereits gemerkt hast, musst Du immer noch diese kryptische Befehlszeile benutzen, die ich Dir gegeben habe, wenn Du ein Objekt sichtbar in Dein Fenster bringen willst. Nun will ich Dir erklären, welches System dahintersteckt.

Jedes Raum-Objekt hat einen "Zeichnen-Befehl", eine abstrakte Methode, mit der es sich zeichnen kann. Ein Knoten gibt natürlich diesen „Zeichnen-Befehl“ an alle gesammelten Objekte weiter. In der Engine Alpha gibt es einen Superknoten, den obersten aller Knoten, die gezeichnet werden. Dieser Knoten ist die Wurzel. Die Klasse Game hat eine Referenz auf ihn, unter dem Namen wurzel. Jedes Objekt, das sichtbar sein soll, muss an der Wurzel, oder natürlich an einem anderen Knoten, der wiederum an der Wurzel oder einem Unterknoten der Wurzel angemeldet ist, angemeldet werden. Das können beliebig viele sein, jeder Knoten kann ja unendlich viele Objekte erfassen.

Jetzt ist auch die Programmzeile verständlich, die ein Raum-Objekt sichtbar werden lässt. Das entsprechende Objekt wird einfach an der Wurzel angemeldet, und damit kommt die Engine-Alpha auch an das Objekt heran und kann den Zeichnen-Befehl ausführen.