Mein Weg zum OOP in Spielen
Übersicht

![]() |
DivineDominionBetreff: Mein Weg zum OOP in Spielen |
![]() Antworten mit Zitat ![]() |
---|---|---|
Ich habe im Chat MasterK viel belästigt (daher geht vorab schon ein mal ein großes Dankeschön für die Zeit und die netten Erklärungen an ihn!) und von ihm Links aufgesammelt, auf deren Basis (meist C++ Codes) ich dann was praktisches zusammengestellt habe, was bei meinen Blitzmax Spielansätzen bisher gut funktioniert hat, wobei ich da auch noch einige Problemchen habe und oft nicht weiterweiß.
Was ich aber schonmal vermitteln kann, ist das Grundgerüst, mit dem jedes Spiel von mir bisher Arbeitet. Es besteht aus einer einzigen Datei, das Spiel, und hat Beispielsweise 3 Sinnabschnitte: 1) Das Menü (Neues Spiel, Credits, Ende) 2) Das Spiel 3) Die Credits Jeder Sinnabschnitt wird als ein GameState bezeichnet. Man könnte so auch einen kleinen Ingame-Editor basteln oder andere Abteilungen einführen, zwischen denen man wechseln muss. In BlitzClassic wurde das beispielsweise so gelöst: https://www.blitzforum.de/viewtopic.php?t=6780 Das Prinzip sollte zumindest verstanden werden und ich setze voraus, dass jeder, der das hier liest, diese wechseleien in Suco-X Tutorial nachvollziehen kann, denn ich werde anfangs nichts anderes behandeln. Die Gamestates Wie bereits erwähnt, werden die Spielstadien hier "GameState" genannt. Sie werden vom Programm ausgewählt und geben bescheid, wenn sie sich beenden, bzw. wenn ein anderer Gamestate an die Reihe kommen soll. In BlitzMax haben wir endlich die Möglichkeit, Methoden zu nutzen, daher werden die Gamestates in Klassen ausgelagert. Im Idealfall benötigt man: 1) eine Methode die aufgerufen wird, wenn der Gamestate gestartet wird 2) eine Methode für die "Mainloop" (Berechnungen etc.) 3) eine Methode zum zeichnen, die von der "Mainloop"-Methode aufgerufen wird 4) eine Methode, die aufgerufen wird, wenn man den Gamestate verlässt Da wir keinen Universal-Gamestate benötigen, leiten wir unsere einzelnen Spielstadien von einer abstrakten Klasse ab, damit man jeden Gamestate von außen gleich behandeln kann. Mir fällt der Fahcbegriff nicht ein, der sowas beschreibt, aber dieses System ist sehr wichtig, auch für spätere Dinge, wie z.B. das Ableiten von Raumschiffen, Gegenständen etc. von einer Objekt-Klasse (wenn man sowas mal brauchen sollte). Unsere Klasse Gamestate sieht dann folgendermaßen aus: Code: [AUSKLAPPEN] Type TGameState Abstract
Method enter() Abstract 'Einstieg in den Gamestate Method process:Int() Abstract '"Mainloop" Method leave() Abstract 'Beenden des Gamestates Method draw() Abstract 'Zeichenfunktion EndType Von dort aus leiten wir unsere drei oben genannten Spielstadien ab. Diese definieren wir zur Erkennung im späteren Programm als konstante Integer: Code: [AUSKLAPPEN] Const GAMESTATE_MENU = 1
Const GAMESTATE_GAME = 2 Const GAMESTATE_CREDITS = 3 Der erste Gamestate muss den Wert 1 haben, nicht 0! Das ist wichtig, denn nun folgt auch gleichzeitig die Erklärung, warum TGameState.process() einen Integerwert zurückliefert. Wie weiter oben erwähnt, sollen Gamestates Bescheid geben, wenn sie beenden werden, und ihren "Nachfolger" bestimmen. Das Menü muss sagen, ob nun zum Spiel gewechselt werden soll, oder ob die Credits angezeigt werden. Dafür liefert die Methode process() dann den Wert der entsprechenden Konstante zurück. Wird nichts geliefert, also soll alles so bleiben wie es ist, empfängt das Hauptprogramm logischerweise eine 0 als Rückgabewert. Wenn nun aber eine Gamestatekonstante den Wert 0 hat, würde dieser andauernd aufgerufen werden. Ja, was uns nun fehlt, ist eben jeder einzelne Gamestate. Diese leiten wir wie gesagt ab. Weil ich jetzt nur die Struktur erklären will, lasse ich die Ein- und Ausstiegsmethoden erst einmal leer und liefere auch nicht gleich ein Spiel ![]() Hier sind die Dummys! Das Menü Code: [AUSKLAPPEN] Type TGSMenu Extends TGameState
Method enter() EndMethod Method process:int() Rem Je nach Auswahl wird ein neuer Gamestate zurückgeliefert, den das Hauptprogramm dann abfängt und behandelt. -1 ist Beenden. EndRem If KeyHit(KEY_N) Return GAMESTATE_GAME ElseIf KeyHit(KEY_C) Return GAMESTATE_CREDITS ElseIf KeyHit(KEY_B) Return -1 EndIf Self.draw() EndMethod Method leave() EndMethod Method draw() DrawText "Menü", 0, 0 DrawText "Neues Spiel [N]", 10, 20 DrawText "Credits [C]", 10, 40 DrawText "Beenden [B]", 10, 60 EndMethod EndType Das Spiel Code: [AUSKLAPPEN] Type TGSGame Extends TGameState
Method enter() EndMethod Method process:int() 'Hier gibts nicht viel zu wechseln - ab ins Menü, wenn nötig If KeyHit(KEY_ESCAPE) Return GAMESTATE_MENU EndIf Self.draw() EndMethod Method leave() EndMethod Method draw() DrawText "Hier muss mal ein Spiel hin... Mit [ESC] geht's in's Menü zurück!", 0, 0 EndMethod EndType Die Credits Code: [AUSKLAPPEN] Type TGSCredits Extends TGameState
Field hTextLines:TList Method enter() 'Konnte es mir nicht verkneifen... Hier mal bisschen spannenderes Zeug Self.hTextLines = New TList Self.hTextLines.addLast("Credits:") Self.hTextLines.addLast("Code: DivineDominion") Self.hTextLines.addLast("Idee: DivineDominion") Self.hTextLines.addLast("") Self.hTextLines.addLast("Tschüssi!") Self.hTextLines.addLast("Mit [ESC] kommt man in's Menü!") EndMethod Method process:int() 'Mit ESC wieder ins Menü If KeyHit(KEY_ESCAPE) Return GAMESTATE_MENU EndIf Self.draw() EndMethod Method leave() EndMethod Method draw() 'Liste der Credits anzeigen Local i:int = 0 For Local sLine:String = EachIn Self.hTextLines DrawText sLine, 50, i * 20 i :+ 1 Next EndMethod EndType So. Das war relativ viel Code für ansich sehr wenig an Action. Viel machen kann man mit dem Zeug aber noch nicht. Es wird noch nichts aufgerufen, es Fehlt das Programm drumherum - es fehlt eigentlich alles, außer der groben Struktur im Spielgeschehen. Als nächstes kommt daher der Manager, der sich darum kümmert, dass die Gamestates aufgerufen werden, und den Wechsel organisiert. Der GameManager! Der Gamemanager Der Gamemanager ist eine Klasse. Während die Gamestates das eigentliche Spiel beinhalten, muss man einmal diesen Gamemanager schreiben und anständig nutzen und danach nie wieder die eigentliche Mainloop anrühren, sondern nurncoh Gamestates hinzufügen und bearbeiten. Wenn das nicht am Ende cool aussieht und was zum Beeindrucken der Mädels ist, weiß ich auch nicht. Der Gamemanager kümmert sich also um alles. Das heißt, er wird einmal vor der Mainloop erstellt und dadrin aufgerufen, um wiederum den aktuellen Gamestate aufzurufen. Vorher muss man ihm sagen, dass er einen Gamestate laden soll (das Menü) und nach der Mainloop soll er den Gamestate auch anständig beenden. Ich schreibe daher erst einmal die Mainloop nieder, wie wir sie in etwa brauchen werden: Die Mainloop Code: [AUSKLAPPEN] Global GAMEMANAGER:TGameManager = New TGameManager
GAMEMANAGER.init() 'Hochfahren While Not GAMEMANAGER.quitProgram() Cls GAMEMANAGER.idle() 'Aufruf des Gamestates Flip FlushMem Wend GAMEMANAGER.terminate() 'Beenden End So sieht sie später aus. Mehr passiert nicht. Der Rest wird wie gesagt in den Gamestates erledigt (Zeichnen etc) und der Gamemanager kümmert sich darum, dem eigentlichen Programm, der Mainloop eigentlich nur, mitzuteilen, was passieren soll. Hier sieht man auch schon alle Methoden des Gamemanagers, bis auf eine: Die, die einen anderen Gamestate auswählt. Die ist nämlich nur intern von Belang. Gamemanager-Gerüst Code: [AUSKLAPPEN] Type TGameManager
'Aktueller Gamestate Field hGameState:TGameState 'Boolean zum Beenden der Mainloop Field bQuitProgram:Int = False 'Einstieg ins Programm Method init() EndMethod 'Aufruf aus Mainloop -> Gametate aufrufen Method idle() EndMethod 'Runterfahren Method terminate() EndMethod 'Ggf. beednen der Mainloop Method quitProgram:Int() EndMethod 'Ändern des Gamestates Method setGameState(iGameState:Int) EndMethod EndType Am Anfang sind nun noch zwei Variablen dazugekommen, die aber selbsterklärend sein sollten. Jetzt entwickeln wir erstmal die Rümpfe der Methoden! Am einfachsten ist TGameManager.quitProgram(). Diese liefert nur den Status der internen und beinahe gleichnamigen Variable zurück: Code: [AUSKLAPPEN] Method quitProgram:Int() Return Self.bQuitProgram EndMethod Als nächstes brauchen wir die längste aller Methoden: TGameManager.setGameState(), da darauf gleich die anderen kurzen aufbauen. Wenn zwischen Gamestates gewechselt wird, muss der eine beendet werden, bevor der andere gestartet wird. Das wird übder die Methoden enter() und leave() gelöst, die weiter oben für die Gamestates beschrieben wurden. Diese Abfragen sind eigentlich alles, was die folgende Methode so lang machen. Kompliziert ist daran nichts weiter ![]() Code: [AUSKLAPPEN] Method setGameState(iGameState:Int) 'Neuen Gamestate buffern Local hNewGameState:TGameState 'Schauen, was übergeben wurde Select iGameState Case GAMESTATE_MENU hNewGameState = New TGSMenu Case GAMESTATE_GAME hNewGameState = New TGSGame Case GAMESTATE_CREDITS hNewGameState = New TGSCredits Default 'Wird was Nichtexistentes angegeben, wird der Wechsel abgebrochen Return EndSelect 'Alten Status ggf. beenden und löschen If Self.hGameState Self.hGameState.leave() Self.hGameState = Null EndIf 'Neuen Gamestate übernehmen und "einsteigen" Self.hGameState = hNewGameState Self.hGameState.enter() EndMethod Ich bezweifle, dass die Methode außerordentlich schwierig war ![]() ![]() Ja, was jetzt fehlt sind die drei Stadien für den Gamemanager: Starten, Aufrufen aus der Mainloop und Beenden. Aufrufen und Beenden gehen am schnellsten, daher direkt hintereinander: Code: [AUSKLAPPEN] Method init() 'Das Menü beim Start "laden" Self.setGameState(GAMESTATE_MENU) EndMethod Method terminate() 'Wenn ein Gamestate geladen ist, diesen beednen; und ab dafür. If Self.hGameState Self.hGameState.leave() Self.hGameState = Null EndIf EndMethod Was jetzt fehlt, ist die TGameManager.idle(). Diese Methode ruft den aktuellen Gamestate auf und, wenn er eine Zahl <>0 zurückliefert, reagiert auf einen Wechsel im Programm. Ansich auch trivial, daher der kommentierte Code ebenfalls mal schnell: Code: [AUSKLAPPEN] Method idle() If Self.hGameState 'Aufrufen der Hauptmethode des Gamestate und Speichern der Rückgabe Local iNewGameState:Int = Self.hGameState.process() If iNewGameState = -1 'Es soll beendet werden Self.bQuitProgram = True ElseIf iNewGameState > 0 'Ein neuer Gamestate muss her Self.setGameState(iNewGameState) EndIf EndIf EndMethod Vielleicht fällt es niemandem auf, aber ich möchte es kurz anmerken, da es bisher mitunter das einzige Beispiel für den OOP-Gedanken darstellt: ch prüfe die Gültigkeit der zurückgelieferten Variable iNewGameState nicht in der idle()-Methode, sondern in TGameManager.setGameState(). Vielleicht würde es jeder von euch auch so machen, und das fände ich durchaus eines Kompliments würdig, denn der Gedanke liegt ja nahe, dass man doch mal fix prüfen kann, wenn man den WErt eh schon hat; schließlich kann ein Gamestate versehentlich 5 liefern oder was anderes wirres, was dann nicht behandelt werden kann. Aber - warum sollte man das da schon tun? Was geht es diese Methode an, was der Gamestate liefert? Genau: nix. Sie bekommt was und leitet es an den Zuständigen weiter - an setGameState()! Der soll sich dann darum kümmern, das alles gültig ist. Das geht außer ihm niemanden an, denn er ist der einzige Zuständige, der irgendwas mit Gamestatewechsel am Hut hat. Aus dem Grund der Kommunikation, die in OOP-Sprachen sehr wichtig und löblich ist, wurde auch die Methode quitProgram() erstellt und nicht einfach die Variable abgefragt. Niemand hat in den Sachen des Gamemanagers rumzukramen. In anderen Hochsprachen wie C++ oder Java ist es üblich, dass alle Variablen "private" deklariert werden, und so vor der Außenwelt versteckt werden können. Dadurch ist der Benutzer gezwungen, mit den Klassen über Methoden zu kommunizieren. Zugegeben, es ist für Koordinaten sehr unsinnig, Methoden wie getX() in Gegnerklassen einzubauen, aber soll sich der Gegner doch selber malen, soll er sich selber um die Bewegung kümmern. Dafür gibt es Methoden, sie erleichtern die Struktur im Programm, da man Dinge abkapseln, sie auslagern kann und an logischen Stellen wiederfindet. Daher ist es auch wichtig, seinen Variablen und Methoden sinnvolle Namen zu geben. Methoden sollten immer mit einem Verb beginnen oder aus einem solchen bestehen. Kommunikation setzt voraus, dass man weiß, womit man überhaupt wie kommunizieren soll. Auf koreanisch kann ich das nämlich auch nicht ![]() Das war es jetzt auch schopn von mir. Fragen könnt ihr hier stellen, auch wenn mir ein diskussionsthread außerhalb lieber wäre, weil hier ja nicht unbedingt dikustiert werden soll... Aber wenn sich irgendwelche diskussionen entwickeln, kann man die ja noch immer absplitten, also nur zu! Anbei noch ein kompletter Code zum kopieren und testen. Viel Spaß! Code: [AUSKLAPPEN] Graphics 640, 480, 0
Type TGameState Abstract Method enter() Abstract 'Einstieg in den Gamestate Method process:Int() Abstract '"Mainloop" Method leave() Abstract 'Beenden des Gamestates Method draw() Abstract 'Zeichenfunktion EndType Const GAMESTATE_MENU = 1 Const GAMESTATE_GAME = 2 Const GAMESTATE_CREDITS = 3 Type TGSMenu Extends TGameState Method enter() EndMethod Method process:Int() Rem Je nach Auswahl wird ein neuer Gamestate zurückgeliefert, den das Hauptprogramm dann abfängt und behandelt. -1 ist Beenden. EndRem If KeyHit(KEY_N) Return GAMESTATE_GAME ElseIf KeyHit(KEY_C) Return GAMESTATE_CREDITS ElseIf KeyHit(KEY_B) Return -1 EndIf Self.draw() EndMethod Method leave() EndMethod Method draw() DrawText "Menü", 0, 0 DrawText "Neues Spiel [N]", 10, 20 DrawText "Credits [C]", 10, 40 DrawText "Beenden [B]", 10, 60 EndMethod EndType Type TGSGame Extends TGameState Method enter() EndMethod Method process:Int() 'Hier gibts nicht viel zu wechseln - ab ins Menü, wenn nötig If KeyHit(KEY_ESCAPE) Return GAMESTATE_MENU EndIf Self.draw() EndMethod Method leave() EndMethod Method draw() DrawText "Hier muss mal ein Spiel hin... Mit [ESC] geht's in's Menü zurück!", 0, 0 EndMethod EndType Type TGSCredits Extends TGameState Field hTextLines:TList Method enter() 'Konnte es mir nicht verkneifen... Hier mal bisschen spannenderes Zeug Self.hTextLines = New TList Self.hTextLines.addLast("Credits:") Self.hTextLines.addLast("Code: DivineDominion") Self.hTextLines.addLast("Idee: DivineDominion") Self.hTextLines.addLast("") Self.hTextLines.addLast("Tschüssi!") Self.hTextLines.addLast("Mit [ESC] kommt man in's Menü!") EndMethod Method process:Int() 'Mit ESC wieder ins Menü If KeyHit(KEY_ESCAPE) Return GAMESTATE_MENU EndIf Self.draw() EndMethod Method leave() EndMethod Method draw() 'Liste der Credits anzeigen Local i:Int = 0 For Local sLine:String = EachIn Self.hTextLines DrawText sLine, 50, i * 20 i :+ 1 Next EndMethod EndType Type TGameManager 'Aktueller Gamestate Field hGameState:TGameState 'Boolean zum Beenden der Mainloop Field bQuitProgram:Int = False 'Einstieg ins Programm Method init() 'Das Menü beim Start "laden" Self.setGameState(GAMESTATE_MENU) EndMethod 'Aufruf aus Mainloop -> Gametate aufrufen Method idle() If Self.hGameState 'Aufrufen der Hauptmethode des Gamestate und Speichern der Rückgabe Local iNewGameState:Int = Self.hGameState.process() If iNewGameState = -1 'Es soll beendet werden Self.bQuitProgram = True ElseIf iNewGameState > 0 'Ein neuer Gamestate muss her Self.setGameState(iNewGameState) EndIf EndIf EndMethod 'Runterfahren Method terminate() 'Wenn ein Gamestate geladen ist, diesen beednen; und ab dafür. If Self.hGameState Self.hGameState.leave() Self.hGameState = Null EndIf EndMethod 'Ggf. beednen der Mainloop Method quitProgram:Int() Return Self.bQuitProgram EndMethod 'Ändern des Gamestates Method setGameState(iGameState:Int) 'Neuen Gamestate buffern Local hNewGameState:TGameState 'Schauen, was übergeben wurde Select iGameState Case GAMESTATE_MENU hNewGameState = New TGSMenu Case GAMESTATE_GAME hNewGameState = New TGSGame Case GAMESTATE_CREDITS hNewGameState = New TGSCredits Default 'Wird was Nichtexistentes angegeben, wird der Wechsel abgebrochen Return EndSelect 'Alten Status ggf. beenden und löschen If Self.hGameState Self.hGameState.leave() Self.hGameState = Null EndIf 'Neuen Gamestate übernehmen und "einsteigen" Self.hGameState = hNewGameState Self.hGameState.enter() EndMethod EndType Global GAMEMANAGER:TGameManager = New TGameManager GAMEMANAGER.init() 'Hochfahren While Not GAMEMANAGER.quitProgram() Cls GAMEMANAGER.idle() 'Aufruf des Gamestates Flip FlushMem Wend GAMEMANAGER.terminate() 'Beenden End[/code] |
||
christian.tietze@gmail.com - https://christiantietze.de
macOS |
![]() |
Jolinah |
![]() Antworten mit Zitat ![]() |
---|---|---|
Finde das kann man recht gut gebrauchen, daher hab ich mir mal erlaubt es in ein Modul umzuschreiben ![]() Da es doof wäre wenn man ein Modul dauernd ändern müsste hab ich den Vorgang mit SetGameState geändert so dass man das Modul immer unverändert lassen kann. Edit: Gehört jetzt vielleicht eher in Module.. Hab absichtlich bei ModuleInfo nix reingeschrieben weil ich den Hauptcode ja von Divi habe ![]() Code: [AUSKLAPPEN] Strict Module pub.GameManager ModuleInfo "Name:" ModuleInfo "Version:" ModuleInfo "Author:" Import brl.system Import brl.glmax2d 'Basisklasse für Gamestates Type TGameState Abstract 'Starten des Gamestates (laden etc.) Method Enter() End Method 'Hauptschleife des Gamestates Method Process() End Method 'Beenden des Gamestates (resourcen freigeben etc.) Method Leave() End Method 'Zeichnen des Gamestates (kann in Process() aufgerufen werden) Method Draw() End Method End Type 'Standard-Gamestate, wird beim Starten des Gamemanagers als Hinweis verwendet dass noch keine eigenen Gamestates erstellt wurden. Type TMainGameState Extends TGameState Final Field _lines:TList Method Enter() _lines = New TList _lines.AddLast("Noch keine States erstellt / No states created") _lines.AddLast("Druecke ESC zum Beenden / Press ESC Key to quit") _lines.AddLast("") _lines.AddLast("Beispiel eines Gamestates / Example of a gamestate") _lines.AddLast("") _lines.AddLast("") _lines.AddLast("Type TMyGameState extends TGameState") _lines.AddLast(" Method Enter()") _lines.AddLast(" 'Entering Gamestate") _lines.AddLast(" End Method") _lines.AddLast("") _lines.AddLast(" Method Process()") _lines.AddLast(" 'Mainloop of this Gamestate") _lines.AddLast(" If KeyHit(KEY_ESCAPE) then GameManager.SetGameState(Null)") _lines.AddLast(" Draw()") _lines.AddLast(" End Method") _lines.AddLast("") _lines.AddLast(" Method Leave()") _lines.AddLast(" 'End of Gamestate") _lines.AddLast(" End Method") _lines.AddLast("") _lines.AddLast(" Method Draw()") _lines.AddLast(" 'Draw") _lines.AddLast(" End Method") _lines.AddLast("End Type") _lines.AddLast("") _lines.AddLast("") _lines.AddLast("Local MyState:TMyGameState = new TMyGameState") _lines.AddLast("") _lines.AddLast("GameManager.Run(MyState)") End Method Method Process() If KeyHit(KEY_ESCAPE) Then GameManager.SetGameState(Null) Return EndIf Draw() End Method Method Leave() _lines.Clear() _lines = Null End Method Method Draw() Local count = 0 For Local line:String = EachIn _lines DrawText line, 10, count * 20 + 10 count :+ 1 Next End Method End Type 'FPS Manager - für frameunabhängiges Programmieren Type TFPSManager Field _fps Field _fps_counter Field _fps_timer Field _fps_value:Float Field _fps_begin Method New() _fps_begin = MilliSecs() End Method 'Aktualisiert die FPS Daten Method Update() Local ms = MilliSecs() _fps_value = (ms - _fps_begin) / 1000.0 _fps_begin = ms If ms >= _fps_timer _fps = _fps_counter * 5 _fps_counter = 0 _fps_timer = ms + 200 '1/5 einer Sekunde Else _fps_counter :+ 1 EndIf End Method 'Gibt die aktuellen FPS zurück Method FPS() Return _fps End Method 'Multipliziert den FPS Faktor mit einer Geschwindigkeit in pixel/sek. und gibt das Ergebnis zurück. Method FPS_Val:Float(value:Float) Return _fps_value * value End Method End Type 'Gamemanager Type TGameManager Field _state:TGameState 'aktueller Gamestate der ausgeführt wird. Field _quit:Byte 'gibt an ob das Spiel beendet ist. Field _fps:TFPSManager 'frameunabhängiges Programmieren und FPS 'Start des Spiels mit übergabe einer GameState Instanz, wenn keine vorhanden wird TMainGameState als Hinweis benutzt. Method Run(State:TGameState = Null) If State = Null _state = New TMainGameState Else _state = State EndIf _state.Enter() _quit = False _fps = New TFPSManager 'Hauptschleife des Spiels While Not _quit _fps.Update() Cls Update() Flip FlushMem Wend Terminate() End Method 'Führt den aktuellen GameState aus, wenn keiner vorhanden wird _quit auf True gesetzt. Method Update() If _state _state.Process() Else _quit = True EndIf End Method 'Gibt vom aktuellen Gamestate verwendete Resourcen frei und setzt den Gamestate auf Null. Method Terminate() If _state _state.Leave() _state = Null EndIf End Method 'Ändert den Gamestate. Auch Null möglich (wodurch das Spiel beendet wird). Method SetGameState(NewState:TGameState) If _state _state.Leave() EndIf _state = NewState If _state _state.Enter() EndIf End Method 'Gibt die aktuellen FPS zurück Method FPS() Return _fps.FPS() End Method 'Multipliziert den FPS Faktor mit einem Wert, wird für die frameunabhängige Programmierung benötigt. Method FPS_Val:Float(value:Float) Return _fps.FPS_Val(value) End Method End Type 'Globale Instanz vom GameManager Global GameManager:TGameManager = New TGameManager Dann noch eine Beispielanwendung: Code: [AUSKLAPPEN] Strict Type TMyState Extends TGameState Field x:Float, y:Float Method Enter() x = 10.0 y = 200.0 End Method Method Process() If KeyHit(KEY_ESCAPE) Then GameManager.SetGameState(Null) Return EndIf If KeyHit(KEY_SPACE) Then GameManager.SetGameState(New TMyOtherState) Return EndIf x :+ GameManager.FPS_Val(50.0) Draw() End Method Method Leave() End Method Method Draw() DrawText GameManager.FPS(), 10, 300 DrawRect x, y, 10, 10 End Method End Type Type TMyOtherState Extends TGameState Method Enter() End Method Method Process() If KeyHit(KEY_ESCAPE) Then GameManager.SetGameState(Null) Return EndIf If KeyHit(KEY_SPACE) Then GameManager.SetGameState(New TMyState) Return EndIf Draw() End Method Method Leave() End Method Method Draw() DrawText "Der andere Gamestate", 10, 10 End Method End Type '**** HAUPTTEIL **** Graphics 1024,768,32,-1 GameManager.Run(New TMyState) EndGraphics FlushMem End Was haltet ihr davon? Edit2: Mit dieser Variante kann man sogar GameStates beibehalten. Also man wechselt auf einen anderen GameState und speichert den alten in einer Variable. Dann kann man wieder auf diesen zurückschalten und alles läuft von da an weiter wo man aufgehört hat. |
||
![]() |
diGGaGruppenKaspar |
![]() Antworten mit Zitat ![]() |
---|---|---|
erstmal danke an euch beide für die mühe ![]() wie kann man das modul da einbinden? find nix grad |
||
Gestern Nacht Schlug der Regen an mein Fenster
Ich ging durch das dunkle Zimmer und glaubte im Licht der Straßenlampe Den Geist unseres jahrhunderts auf der Straße zu sehen Der uns sagte, daß wir alle am Rande des Abgrunds stehen. - Al Steward Athlon 64 3000+ / Radeon 9600 / 1024mb ddram |
![]() |
Freeman |
![]() Antworten mit Zitat ![]() |
---|---|---|
find ich toll das du dir die zeit nimmst OOP richtig zu erklären!Ich gehöre leider auch noch zu den leuten die trotz BlitzMax immer noch kein richtiges OOP praktizieren,aber ich versuche mich jetzt schon etwas darin einzuarbeiten.Danke
FrEeMaN |
||
MacBook | MacOSX 10.5 | 80GB HDD | 1GB | BlitzMax 1.28 |
![]() |
DivineDominion |
![]() Antworten mit Zitat ![]() |
---|---|---|
mr:split
Habe den Thread mal aufgespalten. Die übrigen Modulposts beziehen sich auf https://www.blitzforum.de/viewtopic.php?t=11482 ![]() |
||
christian.tietze@gmail.com - https://christiantietze.de
macOS |
Dogg0 |
![]() Antworten mit Zitat ![]() |
|
---|---|---|
Falls jemand Interesse hat: ich habe einen Pausemodus direkt eingebaut, dass einfach zwischen den States gewechselt werden kann, zB für ein Ingame Menü.
Der pausierte Spielstand wird einfach angehalten und alle Werte erhalten, so dass einfach zurück gewechselt werden kann. Hab dazu ein paar Sachen geändert, einfach mal reinschauen: Code: [AUSKLAPPEN] 'Strict rem Module pub.GameManager ModuleInfo "Name:" ModuleInfo "Version:" ModuleInfo "Author:" Import brl.system Import brl.glmax2d end rem 'Basisklasse für Gamestates Type TGameState Abstract 'Starten des Gamestates (daten laden etc.) Method Start() End Method 'Gamestate (wieder)aufrufen Method Enter() End Method 'Hauptschleife des Gamestates Method Process() End Method 'Beenden des Gamestates (resourcen freigeben etc.) Method Leave() End Method 'Zeichnen des Gamestates (kann in Process() aufgerufen werden) Method Draw() End Method End Type 'FPS Manager - für frameunabhängiges Programmieren Type TFPSManager Field _fps Field _fps_counter Field _fps_timer Field _fps_value:Float Field _fps_begin Method New() _fps_begin = MilliSecs() End Method 'Aktualisiert die FPS Daten Method Update() Local ms = MilliSecs() _fps_value = (ms - _fps_begin) / 1000.0 _fps_begin = ms If ms >= _fps_timer _fps = _fps_counter * 5 _fps_counter = 0 _fps_timer = ms + 200 '1/5 einer Sekunde Else _fps_counter :+ 1 EndIf End Method 'Gibt die aktuellen FPS zurück Method FPS() Return _fps End Method 'Multipliziert den FPS Faktor mit einer Geschwindigkeit in pixel/sek. und gibt das Ergebnis zurück. Method FPS_Val:Float(value:Float) Return _fps_value * value End Method End Type 'Gamemanager Type TGameManager Field _state:TGameState 'aktueller Gamestate der ausgeführt wird. Field _quit:Byte 'gibt an ob das Spiel beendet ist. Field _fps:TFPSManager 'frameunabhängiges Programmieren und FPS field pauseState:TGameState 'Start des Spiels mit übergabe einer GameState Instanz method InitGameManager(State:TGameState = Null) If State = Null ' _state = New TMainGameState Else _state = State EndIf _state.Start() _state.Enter() _quit = False _fps = New TFPSManager Run() End Method Method Run() 'Hauptschleife des Spiels While Not _quit _fps.Update() Cls Update() Flip FlushMem Wend Terminate() End Method 'Führt den aktuellen GameState aus, wenn keiner vorhanden wird _quit auf True gesetzt. Method Update() If _state _state.Process() Else _quit = True EndIf End Method 'Gibt vom aktuellen Gamestate verwendete Resourcen frei und setzt den Gamestate auf Null. Method Terminate() If _state _state.Leave() _state = Null EndIf End Method 'Ändert den Gamestate. Auch Null möglich (wodurch das Spiel beendet wird). 'Aktueller Gamestate geht verloren, Pausierter bleibt erhalten Method NewGameState(NewState:TGameState) If _state _state.Leave() EndIf _state = NewState if _state then _state.start() _state.Enter() end if End Method 'Aktuellen Gamestate pausieren und neuen starten Method PauseAndNew(NewState:TGameState) pauseState = _state _state = NewState If _state _state.start() _state.Enter() EndIf End Method 'setzt aktuellen Gamestate in den Pausemodus und führt den State in der Pause aus method GoToPausedAndSave() local tempstate:TGamestate = _state _state = pausestate pausestate = tempstate tempstate = null pausestate.Enter() End Method 'Wechselt zum pausierten Gamestate und lässt den aktuellen verschwinden method GoToPausedAndKill() _state.Leave() _state = pauseState _state.Enter() end method 'Gibt die aktuellen FPS zurück Method FPS() Return _fps.FPS() End Method 'Multipliziert den FPS Faktor mit einem Wert, wird für die frameunabhängige Programmierung benötigt. Method FPS_Val:Float(value:Float) Return _fps.FPS_Val(value) End Method End Type 'Globale Instanz vom GameManager Global GameManager:TGameManager = New TGameManager Geändertes Beispiel: Code: [AUSKLAPPEN] include "TGameManager.bmx" Type TMyState Extends TGameState Field x:Float, y:Float Method Start() x = 10.0 y = 200.0 End Method Method Process() If KeyHit(KEY_ESCAPE) Then GameManager.NewGameState(Null) Return EndIf If KeyHit(KEY_SPACE) Then 'pausestate = self GameManager.PauseAndNew(New TMyOtherState) 'GameManager.SetGameState(New TMyOtherState) Return EndIf x :+ GameManager.FPS_Val(200.0) if x > 1000 then x = 0 Draw() End Method Method Leave() End Method Method Draw() DrawText GameManager.FPS(), 10, 300 DrawRect x, y, 10, 10 End Method End Type Type TMyOtherState Extends TGameState Method Enter() End Method Method Process() If KeyHit(KEY_ESCAPE) Then GameManager.NewGameState(Null) Return EndIf If KeyHit(KEY_SPACE) Then 'GameManager.SetGameState(New TMyState) 'GameManager.SetGameState(pauseState) GameManager.GoToPausedAndKill() Return EndIf Draw() End Method Method Leave() End Method Method Draw() DrawText "Der andere Gamestate", 10, 10 End Method End Type '**** HAUPTTEIL **** 'Graphics 1024,768,32,-1 Graphics 1024,768,32,nosync GameManager.InitGameManager(New TMyState) EndGraphics FlushMem End |
||
![]() |
Klip |
![]() Antworten mit Zitat ![]() |
---|---|---|
Vielen Dank für die ausführliche Erklärung. Hat mir sehr geholfen. Die Gamestats in BB zu benutzen war ja immer etwas anders gekoppelt. Aber so ist es viel schöner.
Noch eine Anmerkung: Die Method enter() sollte in den meisten Fällen so aussehen: BlitzMax: [AUSKLAPPEN]
Warum? Drückt in mal auf C für die Credits, innerhalb der Credits auf B und dann auf Esc. Sofort beendet das Programm. Beim verlassen eines States müssen die alten Tasten immer noch einmal "geleert" werden. |
||
Übersicht


Powered by phpBB © 2001 - 2006, phpBB Group