Subsections
6. Verwendung des Frameworks
Um die Verwendung der im vorherigen Kapitel beschriebenen Implementierung
des Frameworks zu demonstrieren und um eine laufende Anwendung zu testen
wurden einige Beispiele und kleine Programme geschrieben.
Zunächst werden einige einfache Tools vorgestellt, welche einzelne Funktionen
erfüllen. Diese Tools sind eigenständig lauffähig und sollten auch mit jeder
Implementierung des Frameworks funktionieren.
Die nachfolgenden Beispiele sind im Package de.tud.dvs1.utils.* zu finden.
Das Tool ``PingServer'' verbindet sich kurz mit einem MMPGP2P-Server und
beendet sich dann wieder. Bei erfolgreicher Verbindung wird die auf dem
Server laufende Version ausgegeben.
Das Programm kennt folgende (optionale) Parameter:
verbose: Zusätzliche Informationen ausgeben.
server: Standardmäßig wird zum fest einkompilierten MMPGP2P-Server
verbunden. Mit diesem Parameter kann man einen beliebigen Server anpingen.
port: Falls der MMPGP2P-Server nicht auf dem Standard-Port läuft, kann
man mit diesem Parameter den richtigen Wert übergeben.
Beispiel:
Pingt den Server auf localhost:11222 an.
java de.tud.dvs1.utils.PingServer verbose server localhost port 11222
6.1.2 SendCommand
Dieses Tool kann einen MmpgP2PCommand (siehe 5.5.1) an einem RegionController senden.
Das Programm kennt folgende (optionale) Parameter:
verbose: Zusätzliche Informationen ausgeben.
rc: Standardmäßig wird der RegionController des fest einkompilierten MMPGP2P-Server
kontaktiert. Mit diesem Parameter kann man einen beliebigen RegionController anpingen.
port: Falls ein RegionController nicht auf dem Standard-Port läuft, kann
man mit diesem Parameter den richtigen Wert übergeben.
command nr: Mit diesem Parameter kann man die ID des Kommandos angeben.
args arg1 arg2 ...: Falls dieser Parameter benutzt wird, dann muss
er zuletzt angegeben werden. Die nachfolgenden Argumente werden nämlich als Array
von String-Argumenten an den Server übertragen.
Beispiel:
Sende Kommando Nr. 1500 mit Argumenten ``move 10 10'' an den RegionController auf localhost:11555.
java de.tud.dvs1.utils.SendCommand verbose rc localhost port 11555 command 1500 args move 10 10
Das Programm kann jedoch keine komplexen Kommandos erzeugen, nur solche mit Strings als Parameter.
6.1.3 StandaloneRC
Normalerweise wird ein RegionControllerThread beim Starten eines Clients automatisch
ausgeführt. Zu Testzwecken benötigt man manchmal weitere RegionControllerThreads,
die man mit diesem Programm starten kann.
Das Programm kennt folgende (optionale) Parameter:
rcdebug: Debugging im RegionControllerThread aktivieren.
rchost: Gibt einen Hostnamen an, mit welchem sich der RC beim Server meldet.
rcport: Steuert den Port, auf dem der RegionController lauschen soll.
server: Gibt den Hostnamen des Server an, zu dem sich der RC verbinden und als frei melden soll.
serverport: Gibt den Port an, auf dem der Server läuft.
Beispiel:
Starten eines RegionControllerThread auf Port 12345 und verbindet sich mit Server ``freedom'' auf dem
standart Serverport:
java de.tud.dvs1.utils.StandaloneRC rcport 12345 server freedom
Dieses Programm erzeugt eine Spielewelt mit einigen zufällig generierten Objekten
und sendet diese an einen RegionController.
Das Programm kennt folgende (optionale) Parameter:
verbose: Zusätzliche Informationen ausgeben.
rc: Standardmäßig wird der RegionController des fest einkompilierten MMPGP2P-Server
kontaktiert. Mit diesem Parameter kann man einen beliebigen RegionController kontaktieren.
rcport: Falls ein RegionController nicht auf dem Standard-Port läuft, kann
man mit diesem Parameter den richtigen Wert übergeben.
Dieses Programm könnte man erweitern, um diverse Spielwelten zu testen oder eine Art
Kartenrotation zu implementieren.
6.1.5 BotPlayer
Die Klasse BotPlayer ist eine einfache Implementierung des ClientThreads,
ähnlich der später in Abschnitt 6.6 erwähnten Implementierung.
Diese Klasse simuliert einen Client, der seinen Avatar durch die Welt bewegt und
Gelegentlich auf Objekte feuert.
Achtung: die Verwendung dieses Bots ist nur sinnvoll im Zusammenhang mit der
Beispiel-Implementierung.
Das Programm kennt folgende (optionale) Parameter:
nr: Gibt an, wieviel BotPlayer innerhalb dieses Aufrufes gestartet werden.
interval: Nur im Zusammenhang mit nr sinnvoll. Gibt die Wartezeit zwischen zwei startenden BotPlayern an.
norc: Mit diesem Parameter kann man das Starten eines RCs verhindern.
rcdebug: Zusätzliche Informationen des RegionControllers ausgeben.
rcport: Falls der zu startende RegionController nicht auf dem
rcport: Falls der zu startende RegionController nicht auf dem
server: Gibt den Hostnamen des Server an
serverport: Gibt den Port an, auf dem der Server läuft.
noaction: Der Bot loggt sich ein, beginnt das Spiel, führt dann aber
keinerlei Spielzüge durch.
6.2 Tutorial zur Verwenden des Frameworks
Um die Funktionsweise des vorgestellten Frameworks zu demonstrieren, wurde
ein einfaches Spiel entwickelt. Der Ablauf des Spiels ist sehr einfach gehalten,
ebenso die grafische Oberfläche.
Jedoch sollte es die grundsätzlichen Möglichkeiten des Systems zeigen können.
Die zugehörigen Klassen sind im Package de.tud.dvs1.example zu finden.
Im Folgendem wird genau beschrieben, wie man das Framework für die Entwicklung
eines Spiels einsetzt. Für jeden Schritt wird jeweils ein Beispiel aufgeführt.
Die Implementierung gliedert sich in folgende Schritte. Im den nachfolgenden
Abschnitten werden diese Schritte jeweils detailliert erklärt.
- Avatar implementieren (siehe Abschnitt 6.3)
- Ruleset implementieren (siehe Abschnitt 6.4)
- ServerThread implementieren (siehe Abschnitt 6.5)
- ClientThread implementieren (siehe Abschnitt 6.6)
Die Hauptarbeit beim Verwenden des Frameworks sollte das Implementieren
des Ruleset machen, denn hier wird die komplette Spielelogik modelliert.
6.3 Ruleset implementieren
Das Ruleset bestimmt die Spielregeln. Die RegionController führen ihre Berechnungen
des Weltzustandes regelmäßig über Funktionen aus, die das Ruleset zur Verfügung stellt.
Die Implementierung muß einige Methoden implementieren, in denen sie Objekt-Manipulationen
durchführt.
Für eine systemkomforme Benutzung des Rulesets sind in Kapitel 5.3.1 einige
Regeln aufgezählt, an die sich die Implementierung halten muß.
Die Beispielimplementierung kennt ein FIRETO und ein MOVETO Kommando, welche von den
Clients übermittelt werden und in der Methode applyCommand() implementiert sind.
- targetReached()
In dieser Methode wird eine Besonderheit des Spieltyps der Implementierung umgesetzt.
Ist ein Objekt vom Typ CANNONBALL an seinem Ziel angekommen, dann ``explodiert'' das
Geschoß dort und zieht nahe Objekte in Mitleidenschaft. Die ``geschädigten'' Objekte
werden manipuliert und als Array zurückgeliefert. Das CANNONBALL-Objekt wird aus der
Welt entfernt.
Falls ein sonstiges Objekt sein Ziel erreicht, wird zufällig ein neues Ziel gewählt, dass
sich innerhalb der Grenzen der aktuellen Teilwelt befinden. Bewegliche Objekte
bleiben also weiterhin in Bewegung.
- applyCommand()
An dieser Stelle werden das FIRETO und MOVETO Kommando ausgeführt.
public static final int MOVETO = 2000;
public static final int FIRETO = 3000;
public GameObject[] applyCommand(GameWorld world, MmpgP2PCommand c)
throws MmpgP2PException
{
switch (c.command) {
case MOVETO:
return moveTo(world, c.args);
case FIRETO:
return fireTo(world, c.args);
default:
return null;
}
}
Die beiden Methoden moveTo() und FireTo() führen dann die entsprechenden
Befehle detailliert aus. Um die Übersichtlichkeit zu wahren, werden diese Methoden an dieser
Stelle nicht eingebunden.
- doTick()
In Abschnitt 5.3.1 wurde zwar beschrieben, dass diese Methode in der Regel
die komplexeste ist, jedoch wird in der Beispielimplementierung an dieser Stelle keinerlei
weitere Aktion durchgeführt. Die Bewegungen werden bereits von der Oberklasse Ruleset
durchgeführt und Explosionen von Kanonenkugeln sind in der Methode targetReached()
implementiert.
Der Programmierer kann hier beliebige Manipulationen vornehmen, solange er die Regeln
einhält, die bereits in Abschnitt 5.3.1 genannt wurden.
6.4 Avatar implementieren
Das Framework bietet einen vordefinierte Rahmen für einen Avatar in der Klasse
Avatar.class. Diese Klasse ist eine Erweiterung des GameObjects (siehe
Abschnitt 5.3.3).
Falls Änderungen oder Erweiterungen zum Avatar implementiert werden sollen,
dann bietet sich eine Erweiterung der Klasse an. Die zur Verfügung gestellten
Methoden sollten dabei nur überschrieben werden, wenn deren Funktionalität
vollständig ersetzt wird.
Der ServerThread erzeugt Avatare durch die Methode getClientAvatars(),
dort müssen diese Avatar-Implementierungen instanziiert werden.
Die Klasse AvatarImplementation überschreibt die Methode hit(), um das
Leveln 6.1des Avatars zu implementieren (siehe auch Abschnitt ).
Der Parameter der Methode wird
an die Methode der Oberklasse Avatar (super.hit()) durchgereicht und
erst danach verarbeitet. Damit wird sichergestellt, dass keine ungewöhnlichen
Effekte auftreten.
public int hit(int h) {
int ret = super.hit(h);
if (life >= maxlife) {
life = 200;
maxlife+= 100;
maxspeed+=1.0;
level++;
}
return ret;
}
6.5 ServerThread implementieren
Für den ordnungsgemäßen Betrieb des Server ist die vollständige Implementierung
von einigen abstakten Methoden der Klasse ServerThread notwendig. Die wichtigsten
Methoden werden hier kurz erläutert. Für detailierte Informationen sollte die
JavaDoc zu Rate gezogen werden.
- SessionTicket loginClient(ClientRequest client)
Diese Methode wird aufgerufen, sobald sich ein Client mit dem Server
verbindet und ein Login anfordert. Als Rückgabewert wird ein
SessionTicket erwartet. Für einfache Implementierungen reicht es,
wenn einfach ein mit new SessionTicket() erzeugtes SessionTicket
zurückgegeben wird.
Die Beispiel-Implementierung führt keine wirkliche Authentifizierung
durch, sondern erzeugt einfach ein SessionTicket für den Client. Es
wird lediglich dafür gesorgt, dass ein Client nicht doppelt einloggt.
- int registerNewClient(ClientRequest client)
Falls der Client einen Befehl zum Registrieren eines neuen Accounts
übermittelt, dann wird diese Methode aufgerufen und ausgeführt.
Die Implementierung sollte den Account dann dauerhaft anlegen, so
dass er in Zukunft bei jedem Login verfügbar ist.
Die Beispiel-Implementierung reagiert nicht auf diesen Aufruf, d.h.
sie ist leer und gibt immer 0 (SUCCESS) zurück. Die Spieler werden
nicht permanent gespeichert.
- Ruleset getRuleset()
Jedem RegionController, der sich beim Server als frei meldet,
sowie jedem Client, der sich beim Server einloggt, wird ein Ruleset
übermittelt. Diese wissen dann, wie Befehle zu verarbeiten sind.
Die Beispiel-Implementierung des Rulesets (RulesetImplementation, siehe 6.3)
ist auf ein Minimum beschränkt und kennt daher nur wenige Befehle.
- Avatar[] getClientAvatars(ClientInfo c)
Loggt sich ein Client in das System ein, dann müssen seine Avatare
an den zuständigen RC-Pool übertragen werden. Die Implementierung kann
hier die Avatare aus einer Datenbank oder von sonstigen Speichermedien laden.
(siehe Abb. 5.1)
Die Beispiel-Implementierung erzeugt zufällig einen neuen Avatar vom
Typ AvatarImplementaion (siehe 6.4) an einer beliebigen Position und gibt diesen zurück.
- saveClientAvatars(ClientInfo c, Avatar[] avatars)
Die Implementierung soll die Avatare des Clients speichern. Die
Art der Speicherung ist natürlich abhängig davon, wie sie später
wieder durch die Methode getClientAvatars() abgerufen werden.
(siehe Abb. 5.3)
Das die Beispiel-Implementierung immer zufällige Avatare erzeugt,
werden die Avatare nicht gespeichert sondern einfach verworfen.
Dementsprechend ist die Methode leer.
- GameWorld getInitialGameWorld()
Diese Methode ruft das System auf, sobald ServerThread startet. Die
Implementierung kann an dieser Stelle dafür sorgen, dass eine persistente
Spielewelt aus einer Datenbank geladen wird.
Die Beispiel-Implementierung erzeugt eine zufällige Welt und gibt sie zurück.
Das bedeutet, dass nach dem Beenden des Servers die Welt verloren geht.
6.5.1 Beispiel: ServerImplementation.class
Diese Klasse implementiert die abstrakte Klasse ServerThread und lässt sich
als Anwendung starten. Ausgaben und Fehler werden über System.out ausgegeben.
Die Implementierung unterstützt einige Kommandozeilen-Parameter und das Einlesen
einer Properties-Datei (siehe 5.2). Diese kann über den Parameter
prop dateiname angegeben werden. Als Standard lädt der Server die
unter $HOME/.mmpgp2p abgespeicherte Datei server.rc.
Zum Starten des Servers wird folgender Befehl ausgeführt:
java de.tud.dvs1.example.ServerImplementation
Zuvor muss dafür gesorgt werden, dass der CLASSPATH richtig gesetzt ist.
Ein Großteil des Programmcodes innerhalb der Methode main() dient lediglich
dem Auslesen und Setzen von Parametern und den erwähnten Java-Properties.
Das eigentliche Starten des ServerThread umfasst wenige Zeilen:
0 public static void main (String[] args) {
1 ServerThread server = new ServerImplementation(prop);
2
3 if (daemon) {
4 server.setDaemon(true);
5 System.out.println("SI : Daemon-Modus");
6 }
7
8 ...
9
10 try{
11 server.start();
12 ...
13 server.initGameWorld(getTestWorld());
14 }
15 } // main Ende
Zeile 1: diese Zeile muß in jeder Implementierung ausgeführt werden, damit
die Parameter des ServerThread gesetzt werden und später richtig gestartet
werden kann.
Zeile 3-6: ServerThread kann als Daemon gestartet werden. Dies wird hier
gesetzt.
Zeile 11: nun wird der Thread gestartet.
Zeile 13: eine TestWelt wird dem Server als initiale Welt übergeben.
Zeile 15: Ende der Methode main(). Der Thread jedoch läuft weiter bis er
getötet oder über die Methode stopThread() beendet wird.
6.6 ClientThread implementieren
Zur Implementierung des Clients bedarf es weitaus weniger Schritte als
zur Implementierung des Servers. Lediglich ein paar Logging-Funktionen und
die Methode stopClientImplementation() müssen implementiert werden.
Detailierte Beschreibungen dieser Funktionen sind in der Javadoc
zu finden.
6.6.1 Beispiel: ClientImplementation.class
Diese Klasse implementiert die abstrakte Klasse ClientThread und lässt sich
als Anwendung starten. Ausgaben und Fehler werden in einem grafischen Fenster
ausgegeben. Über die Menüleiste kann das Verhalten gesteuert werden und
Informationen abgerufen werden.
Wie bei der Implementierung des Servers umfasst der Programmcode vor allem
das Auslesen und Setzen von Parametern und Java-Properties. Das eigentliche
Starten des ClientThread umfasst wenige Zeilen:
0 public static void main (String[] args) {
1 ClientThread client = new ClientImplementation(p);
2
3 try {
4 // GUI für Programm-Ausgaben starten
5 javax.swing.SwingUtilities.invokeLater(new Runnable() {
6 public void run() {
7 createAndShowGUI();
8 }
9 });
10
11 // Start Client Thread
12 client.start();
13 }
15 } // main Ende
Zeile 1: diese Zeile muß in jeder Implementierung ausgeführt werden, damit
die Parameter des ClientThread gesetzt werden und später richtig gestartet
werden kann.
Zeile 5-9: die grafische Oberfläche des Beispiel-Clients wird gestartet.
Zeile 12: nun wird der Thread gestartet.
Zeile 15: Ende der Methode main(). Der Thread jedoch läuft weiter bis er
getötet, über die Methode stopThread() oder über den Close-Button der
grafischen Oberfläche beendet wird.
Die Implementierung bietet eine grafische Oberfläche zum Steuern des Avatars
und zur Ausgabe verschiedener Informationen. Die beiden Reiter ``Client-Log''
und ``RC-Log'' protokollieren Systemausgaben, welche über die Log()-Funktionen
vom System erzeugt werden. Der Reiter ``Spielfeld'' ist die Benutzungs-Oberfläche
des Spiels, in ihr können Bewegungs- und Feuer-Kommandos befehligt werden. Sie
stellt die Objektzustände in einer einfachen Weise grafisch dar.
Figure 6.1:
Screenshot der Benutzungsoberfläche der Beispiel-Implementierung
 |
Über das Menü ``Info'' ist die Ausgabe von Informationen möglich, z.B. die Ausgabe
des SessionTickets und der Avatar-Daten. Im Menü ``Spiel'' können Kommandos zum Starten,
Pausieren und Wiederaufnehmen des Spiels übermittelt werden. Unter ``Ansicht'' können
einige grafische Optionen angepasst werden. Der Menüpunkt ``Client'' bietet zwei
wichtige Funktionen, nämlich Login und Logout, sowie die Möglichkeit zum Ein-
oder Ausschalten der Komprimierung einiger Kommandos mit GZIP. Und schließlich kann
man im Menü ``Datei'' das Programm beenden oder einen BotPlayer starten, der sich
dann direkt beim System anmeldet und spielt.
Die Spielregeln sind einfach, die Steuerung funktioniert mit der Maus:
- Jeder Avatar, dargestellt als Panzer, hat Energie. Diese wird unten links als
Balken dargestellt. Beim Feuern verliert der Panzer einen Energiepunkt. Wenn man
einen Treffer landet, wird diese Energie wieder aufgefüllt.
Übersteigt die Energie das Limit, dann steigt der Avatar ein Level auf. Sein
Energielimit wird bei jedem Aufstieg ebenfalls erhöht.
- Mit einem Doppelklick sendet man ein MOVETO-Kommando. Der Avatar bewegt sich
dann zum angegebenen Punkt
- Durch einen Rechtsklick wird ein FIRETO-Kommando ausgeführt. Die Kanonenkugel
``fliegt'' dann zu der Stelle, an der geklickt wurde. Dort explodiert sie dann und
zieht Objekte in ihrer Umgebung in Mitleidenschaft.
- Mit dem Mausrad lässt sich die Umgebung zoomen. Ein Klick auf die mittlere Maustaste
zentriert auf dem Avatar, schaltet auf Verfolgungsmodus und stellt den Ursprungszoom wieder her.
- Mit einem STRG+Doppelklick kann man die Oberfläche auf den geklickten Punkt
zentrieren. Der Verfolgungsmodus wird deaktiviert.
http://www.psitronic.de/ti/mmpg/mmpg-peer_to_peer/
|
|