Teil 4: Managing A Game I

Heute wird’s mal etwas härter…
Betrachten wir ein klassisches Spiel, so ist dieses meist in mehrere Phasen aufgeteilt. Angefangen beim Herstellerlogo geht es weiter zu einem Introvideo, von dort zum Hauptmenü, von dort ins Spiel (was sich wiederum in Lade-Bildschirm, tatsächliches Spiel, und Ende-Bildschirm gliedern könnte), oder in die Optionen, oder in die Credits. Die Möglichkeiten sind vielfältig. Derartige Zustände bezeichne ich als Gamestates (GS), also einen Zustand, in dem sich das Spiel gerade befindet. Das Menü könnte also ein GS sein, ebenso wie das Spiel. Zwischen derartigen GS muß gewechselt werden, z.B. vom Menü zum Spiel und umgekehrt, wir brauchen also noch etwas, was diese Zustände verwaltet: den GameManager (GM).
Wir haben hiermit gerade eine kleine Hierarchie erarbeitet. Während die main-Funktion quasi das Hauptprogramm kapselt, wird der GM das Spiel kapseln. Die gesamte Spiellogik wird in einem gerade aktiven GS ausgeführt. So können wir Spiellogik auch leicht vom restlichen systemnahen Kram abschirmen.
Zurück zum klassischen Spiel: Wir zeigen also das Herstellerlogo. Wie kommen wir jetzt weiter zum gerenderten Intro? Es gibt verschiedene Ansätze. Der GM könnte höflich beim GS nachfragen, ob ein Wechsel stattfinden soll, der GS könnte aber auch dem GM den Wechsel direkt befehlen. Wir wählen den letztgenannten Ansatz, womit direkt die nächste Frage aufkommt: Wie erreicht ein GS den GM? Da nun der GM ja in einer Art Vater-Kind Hierarchie für den GS verantwortlich ist, ist es leicht, dem GS einen Zeiger oder eine Referenz auf den GM mitzugeben. Da der GM aber eh nur einmal existieren muß, und vielleicht auch noch von anderer Stelle aus erreicht werden soll, machen wir es uns noch leichter, und erstellen den GM als Singleton. Wer nicht weiß, was ein Singleton ist, sollte mal schnell unten in den weiterführenden Links nachlesen.
Es folgt also der Header für den GM. Um weiterhin die Ordnung im Quelltext beizubehalten, lege ich für die folgenden Dateien einen Ordner game an. Hier rein kommt alles, was irgendwie mit der tatsächlichen Spiellogik zu tun hat, also der GM und die GSs.
Datei /game/GameManager.h
[source:cpp]#ifndef GAMEMANAGER_H
#define GAMEMANAGER_H
// —————————————————————————
#include “Types.h”
#include “GameState.h”
#include “GameStateFactory.h”
// —————————————————————————
class CGameManager
{
public:
static CGameManager* GetInstance();
static void DestroyInstance();
void Initialize();
void Uninitialize();
void BeginFrame();
void Idle();
void EndFrame();
void SetGameState( CGameStateFactory::eGameState eGS );
private:
CGameManager();
~CGameManager();
static CGameManager* m_pInstance;
IGameState* m_pCurrentGameState;
CGameStateFactory::eGameState m_eCurrentGameState;
DWORD m_dwFrameStart;
DWORD m_dwFrameTime;
};
#define GAMEMANAGER CGameManager::GetInstance()
#endif[/source]
Huch? Da werden ja Dateien eingebunden, die wir noch gar nicht kennen? Keine Sorge, die kommen später. Wir beschränken uns erstmal auf das, was wir kennen: GetInstance und DestroyInstance sind die Zugriffsfunktionen für die Singleton-Instanz. Gemäß der Natur des Singletons sind Constructor (c’tor) und Destructor (d’tor) als private Methoden vor Zugriff von aussen versteckt. Initialize macht den GM startklar, Uninitialize räumt wieder auf. BeginFrame, Idle, und EndFrame sind eigentlich eine Einheit – normalerweise werden die Befehle in der Hauptschleife in der main-Funktion direkt hintereinander aufgerufen. BeginFrame macht hier erstmal gar nichts, Idle beschäftigt den aktuellen GS, und EndFrame misst die gebrauchte Zeit, um die FPS messen zu können, etc. SetGameState wechselt den aktuellen GS. GAMEMANAGER ist ein define, das die Schreibweise beim Zugriff auf den GS verkürzen soll.
Die Implementierung ist dann, wenn man das System verstanden hat, eigentlich trivial:
Datei /game/GameManager.cpp
[source:cpp]#include
#include “GameManager.h”
// —————————————————————————
CGameManager* CGameManager::m_pInstance = 0;
// —————————————————————————
CGameManager* CGameManager::GetInstance()
{
if( !m_pInstance )
{
m_pInstance = new CGameManager;
}
return m_pInstance;
}
// —————————————————————————
void CGameManager::DestroyInstance()
{
if ( m_pInstance )
{
delete m_pInstance;
m_pInstance = 0;
}
}
// —————————————————————————
CGameManager::CGameManager()
: m_dwFrameTime( 0 ),
m_pCurrentGameState( 0 ),
m_eCurrentGameState( CGameStateFactory::GS_UNDEFINED )
{
m_dwFrameStart = SDL_GetTicks();
}
// —————————————————————————
CGameManager::~CGameManager()
{
if( m_pCurrentGameState )
{
m_pCurrentGameState->Leave();
delete m_pCurrentGameState;
}
}
// —————————————————————————
void CGameManager::Initialize()
{
SetGameState( CGameStateFactory::GS_GAME );
}
// —————————————————————————
void CGameManager::Uninitialize()
{
if( m_pCurrentGameState )
{
m_pCurrentGameState->Leave();
delete m_pCurrentGameState;
m_pCurrentGameState = 0;
}
}
// —————————————————————————
void CGameManager::SetGameState( CGameStateFactory::eGameState eGS )
{
if (eGS == m_eCurrentGameState)
{
return;
}
IGameState* pNewGameState = CGameStateFactory::Create( eGS, this );
if (!pNewGameState)
{
return;
}
if (m_pCurrentGameState)
{
m_pCurrentGameState->Leave();
delete m_pCurrentGameState;
}
m_pCurrentGameState = pNewGameState;
m_pCurrentGameState-> Enter();
m_eCurrentGameState = eGS;
}
// —————————————————————————
void CGameManager::BeginFrame()
{
}
// —————————————————————————
void CGameManager::Idle()
{
if (m_pCurrentGameState)
{
m_pCurrentGameState->Idle( m_dwFrameTime );
}
}
// —————————————————————————
void CGameManager::EndFrame()
{
DWORD dwCurrentTime = SDL_GetTicks();
m_dwFrameTime = dwCurrentTime – m_dwFrameStart;
m_dwFrameStart = dwCurrentTime;
}[/source]
Was passiert da bei Initialize? Der GM wird mit einem Start-GS initialisiert. Die SetGameState-Funktion ist für derartige Wechsel verantwortlich. Sie beendet den aktuellen GS und startet den neuen.
Damit wäre der GM schon komplett, aber was ist mit dem GS, und was zur Hölle soll CGameStateFactory sein? Das erfährst du in Teil II.

Weiterführende Links

Leave a Reply

Your email address will not be published. Required fields are marked *