Mein Weg zum OOP in Spielen

Übersicht BlitzMax, BlitzMax NG FAQs und Tutorials

Neue Antwort erstellen

DivineDominion

Betreff: Mein Weg zum OOP in Spielen

BeitragDi, Mai 17, 2005 23:53
Antworten mit Zitat
Benutzer-Profile anzeigen
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 Smile

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 Smile

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 Smile Wer Probleme hat, die Syntax zu verstehen, sollte vielleicht erst die Language Reference lesen Smile Ich verwende hier ja keine höhere Mathematik...


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 Wink

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

BeitragMi, Mai 18, 2005 11:03
Antworten mit Zitat
Benutzer-Profile anzeigen
Finde das kann man recht gut gebrauchen, daher hab ich mir mal erlaubt es in ein Modul umzuschreiben Wink

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 Wink

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.

diGGa

GruppenKaspar

BeitragMi, Mai 18, 2005 13:47
Antworten mit Zitat
Benutzer-Profile anzeigen
erstmal danke an euch beide für die mühe Wink
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

BeitragMi, Mai 18, 2005 14:13
Antworten mit Zitat
Benutzer-Profile anzeigen
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

BeitragFr, Mai 20, 2005 14:01
Antworten mit Zitat
Benutzer-Profile anzeigen
mr:split
Habe den Thread mal aufgespalten.
Die übrigen Modulposts beziehen sich auf https://www.blitzforum.de/viewtopic.php?t=11482

Smile
christian.tietze@gmail.com - https://christiantietze.de
macOS
 

Dogg0

BeitragDo, Sep 22, 2005 0:18
Antworten mit Zitat
Benutzer-Profile anzeigen
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

BeitragDi, Nov 15, 2005 16:57
Antworten mit Zitat
Benutzer-Profile anzeigen
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]
    
Method enter()
FlushKeys()
EndMethod


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.

Neue Antwort erstellen


Übersicht BlitzMax, BlitzMax NG FAQs und Tutorials

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group