[Monkey] Tutorial: Ein GameStateManager mit Interfaces
Übersicht Andere Programmiersprachen FAQs und Tutorials
FarbfinsternisBetreff: Tutorial: Ein GameStateManager mit Interfaces |
Do, Nov 28, 2013 22:47 Antworten mit Zitat |
|
---|---|---|
Interfaces sind eine extrem nützliche Sache und ich möchte versuchen diese hier halbwegs verständlich zu erklären. Als Beispiel soll ein GameStateManager dienen der es gestattet zwischen den verschiedenen States eines Spiels zu wechseln.
Stelle Dir vor Dein Spiel besitzt die States Intro, MainMenu, InGame und GameOver. Diese lassen sich in der Regel wechseln, das heißt man kommt vom Intro in das MainMenu, von dort ins Game und zurück in MainMenu und irgendwann erscheint der State GameOver. Um das zu realisieren könnte man das Probem mit dem Wechsel der States (oder Ansichten) mit ganz viel Code bewerfen, oder aber einfach ein Interface realisieren welches alle States implementieren. Jetzt gehts los. Interfaces werden in Monkey, wer hätte das gedacht, mit dem Schlüsselwort Interface eingeleitet und wie andere Konstrukte auch mit End abgeschlossen: Code: [AUSKLAPPEN] Interface IMyInterface ... End In einem Interface schreibt man aber keinen ausufernden Code, sondern nur Deklarationen der Methoden die die Klassen implementieren müssen wenn sie dieses Interface nutzen wollen. Fangen wir gleich mit dem kalten Wasser an: Das GameState Interface: Code: [AUSKLAPPEN] Interface IGameState Method update:IGameState() Method render:Void() End Dieses Interface sieht vor dass ein GameState eine Methode update besitzt welche einen Wert vom Typ IGameState zurück gibt (wozu das so sein soll kommt noch!) und eine Methode render in der das Eyecandy eingebaut werden soll. Das Interface alleine macht aber noch keinen GameStateManager, denn eine Methode die nur deklariert ist kann man nicht aufrufen. Tipp: Etwas zu deklarieren bedeutet: Ich sage schon mal an dass ich es irgendwann benutzen will, gehe aber noch nicht ins Detail. Etwas zu definieren heißt: Jetzt wird es ernst, ich sage Dir wie das Ding aussieht und wie man es benutzt! Um dieses Interface also nutzen zu können benötigen wir eine Klasse welche dieses Interface implementiert. Um auch gleich noch den Rundumschlag zur Veerbung zu schaffen basteln wir uns eine Klasse welche IGameState implementiert und von welcher unsere eigentlichen States später erben. Das ist super bequem und ihr werdet bald erkennen warum. Code: [AUSKLAPPEN] Class GameState Implements IGameState Method update:GameState() Return Self End Method render:Void() End End Sieht erstmal recht komisch aus aber passt auf, das ist ganz einfach: Unsere Klasse GameState implementiert das Interface IGameState, somit haben alle Methoden in GameState zwei Rückgabetypen: GameState und IGameState. Die Klasse GameState soll aber nur als Vorlage für unsere eigentlichen GameStates dienen, also basteln wir uns mal eine MainMenu-Klasse welche vom GameState erbt: Code: [AUSKLAPPEN] Class MainMenu Extends GameState End Unsere Klasse MainMenu kann nun nicht nur alles was GameState kann, sondern implementiert gleich noch IGameState mit. Wir überschreiben natürlich die Methoden der Elternklasse, denn wir wollen ja selbst bestimmen was in der Klasse passiert: Code: [AUSKLAPPEN] Class MainMenu Extends GameState Method update:GameState() Local result:GameState = Self Return result End Method render:Void() Cls(255, 0, 0) End End Die Klasse MainMenu beschreibt die Methoden update und render nun schon etwas genauer. Wir wollen dass die Methode update solange sich selbst zurück gibt bis sie nichts mehr zu tun hat und in den nächsten State wechseln möchte. Den State-Wechsel machen wir uns beobachtbar indem wir in render den Screen/das Canvas rot färben. Um aber einen nächsten State zu haben müssen wir unsere Klasse GameState noch etwas erweitern: Code: [AUSKLAPPEN] Class GameState Implements IGameState Method update:GameState() Return Self End Method render:Void() End Method setNext:Void(state:GameState) Self._next = state End Method setPrevious:Void(state:GameState) Self._prev = state End Private Field _next:GameState, _prev:GameState End Mit den neuen Methoden setNext und setPrevious ist es uns nun möglich zu definieren was vor dem aktuellen State für ein GameState war und welcher GameState angefahren werden soll wenn der aktuelle State beendet wird. Bedenkt dass wir das in MainMenu direkt verwenden können da wir ja von der Klasse GameState erben. Brauchen wir noch unsere "InGame" Klasse, welche wir natürlich auch von GameState erben: Code: [AUSKLAPPEN] Class InGame Extends GameState Method update:GameState() Local result:GameState = Self Return result End Method render:Void() Cls(0, 255, 0) End End In dieser Klasse lassen wir den Screen grün füllen um ein visuelles Feedback über den Wechsel zu haben. Um das alles nutzen zu können benötigen wir natürlich eine Klasse die als Hauptklasse fungiert und all das verwaltet was hier bisher beschrieben wurde. Leiten wir diese Klasse ganz normal von mojo.app ab und definieren unsere States: Code: [AUSKLAPPEN] Strict Import mojo Class Game Extends App Public Method OnCreate:Int() SetUpdateRate(30) Self._mainMenu = New MainMenu() Self._inGame = New InGame() Self._mainMenu.setNext(Self._inGame) Self._inGame.setPrevious(Self._mainMenu) Self._currentState = Self._mainMenu Return 1 End Method OnUpdate:Int() Return 1 End Method OnRender:Int() Return 1 End Private Field _mainMenu:GameState Field _inGame:GameState Field _currentState:GameState End Function Main:Int() New Game() Return 1 End In OnCreate erzeugen wir zuerst das MainMenu und dann den InGame-State und weisen diesen dann den jeweiligen Vorgänger und den jeweiligen Nachfolger zu. Zusätzlich sichern wir in "_currentState" die Instanz die gerade aktuell ist, in diesem Fall das MainMenu. Um nun zwischen den States wechseln zu können müssen wir in den Klassen MainMenu und InGame auf ein Event reagieren und dann jeweils den letzten oder den nächsten State zurückgeben. Da dieses Event nicht immer gleich sein muss (im Menu klicken wir auf "Los", im Game stirbt der Character etc.) müssen wir diesen Wechsel direkt in den betroffen Klassen implementieren und können das nicht der Elternklasse überlassen: Code: [AUSKLAPPEN] Class MainMenu Extends GameState Method update:GameState() Local result:GameState = Self If MouseHit() result = Self._next Return result End Method render:Void() Cls(0, 255, 0) End End Class InGame Extends GameState Method update:GameState() Local result:GameState = Self If MouseHit() result = Self._prev Return result End Method render:Void() Cls(0, 255, 0) End End Der Einfachheit halber habe ich in diesem Beispiel nur die tatsächlich definierten States implementiert. Das MainMenu kennt halt nur einen nächsten State (InGame) und das Game kennt halt nur den vorherigen State (MainMenu). Nun müssen wir nur noch die Klasse Game anpassen um zwischen den States per Mausklick wechseln zu können: Code: [AUSKLAPPEN] Strict Import mojo Class Game Extends App Public Method OnCreate:Int() SetUpdateRate(30) Self._mainMenu = New MainMenu() Self._inGame = New InGame() Self._mainMenu.setNext(Self._inGame) Self._inGame.setPrevious(Self._mainMenu) Self._currentState = Self._mainMenu Return 1 End Method OnUpdate:Int() Self._currentState = Self._currentState.update() Return 1 End Method OnRender:Int() Self._currentState.render() Return 1 End Private Field _mainMenu:GameState Field _inGame:GameState Field _currentState:GameState End Function Main:Int() New Game() Return 1 End Der aktuelle State gibt solange sich selbst zurück und wird in "_currentState" geschrieben bis der Anwender die linke Maustaste drückt, dann wird der nächste oder vorherige State (je nach Aufgabe) zurück gegeben und in "_currentState" geschrieben. Die Methode render ruft die Render-Methode des aktuellen States auf und vóila, es läuft... Fragen (ich könnte etwas undeutlich formuliert haben) und Kommentare (ich bin nicht omnipotent ) gerne in diesen Thread. |
||
Thorsten |
Do, Dez 19, 2013 13:27 Antworten mit Zitat |
|
---|---|---|
Wo genau ist das Interface jetzt nötig? | ||
DAK |
Do, Dez 19, 2013 16:12 Antworten mit Zitat |
|
---|---|---|
Ein Interface ist im Grunde eine Überklasse, in der keine Methoden / Funktionen implementiert sind, und die keine Felder hat.
Es definiert aber Funktions-/Methodenheader. Alle Klassen, die ein Interface implementieren, können überall verwendet werden, wo man Objekte des Interfaces verwenden kann. Z.B.: BlitzMax: [AUSKLAPPEN] Interface ITest Trotzdem dass TTestA und TTestB was anderes sind, kann man beide behandeln als wären sie ITest. |
||
Gewinner der 6. und der 68. BlitzCodeCompo |
das wurgel |
Fr, Dez 20, 2013 18:01 Antworten mit Zitat |
|
---|---|---|
Das gute an Interfaces ist, dass unabhängig von der Oberklasse gleich mehrere Inferfaces implemtiert werden können. Das gute an Vererbung ist, dass Felder und bereits ausprogrammierte Methoden mitgerebt werden. Beides ist gut, um eine gemeinsame Schnittstelle bereitzustellen. | ||
1 ist ungefähr 3 |
Übersicht Andere Programmiersprachen FAQs und Tutorials
Powered by phpBB © 2001 - 2006, phpBB Group