Im letzten Teil habe ich ja bereits auf eine Stelle aufmerksam gemacht, die Probleme bereiten kann.
Wird ein mit SDL_LoadBMP geladenes Surface nicht gefunden, ist der Surfacezeiger auf 0, und das darauf folgende SDL_SetColorKey führt zu einem Absturz. Wir müssen also derartige Probleme abfangen und das Programm dann möglichst sanft beenden.
Die paar SDL Initialisierungen beim Programmstart sind noch nicht weiter tragisch. Unangenehm wird die Fehlerbehandlung erst, nachdem der GameManager läuft und ein GameState aktiviert wurde. Du kannst Dir vorstellen, daß das laufende Spiel später eine Menge Grafiken laden, und dementsprechend Speicher anfordern wird. Soundeffekte werden ebenfalls noch dazu kommen, und wohl auch noch ein paar andere dynamisch angelegte Daten. Wir möchten bei einem einfachen Problem wie einer nicht gefundenen Grafik nicht einfach das Programm beenden und unseren Müll nicht wegräumen. Es wäre also nett, wenn der GameManager noch die Gelegenheit bekommt, sich und seinen aktiven GameState zu deinitialisieren.
Wie machen wir das? Wir sagen dem GameManager einfach, daß das Programm beendet wird. Und unsere Hauptschleife in Main.cpp wird auch nicht mehr so lange laufen, wie nicht Escape gedrückt wurde, sondern solange, wie der GameManager das gerne möchte. Wir fügen dazu erstmal drei kleine Funktionen und eine neue Membervariable in den GameManager ein:
Datei /game/GameManager.cpp
[source:cpp]// —————————————————————————
void CGameManager::Quit()
{
m_bRunning = false;
}
// —————————————————————————
void CGameManager::QuitWithError( const char* pszErrorMsg )
{
LOG << ENDL << "*** Das Spiel wird aufgrund des folgenden Fehlers beendet: ***" << ENDL;
LOG << "*** " << pszErrorMsg << " ***" << ENDL << ENDL;
m_bRunning = false;
}
// ---------------------------------------------------------------------------
bool CGameManager::IsRunning() const
{
return m_bRunning;
}[/source]
m_bRunning ist also unsere neue Variable, die festlegt, ob der GameManager aktiv ist. Sie wird beim Start natürlich mit true initialisiert:
[source:cpp]// ---------------------------------------------------------------------------
CGameManager::CGameManager()
: m_dwFrameTime( 0 ),
m_pCurrentGameState( 0 ),
m_eCurrentGameState( CGameStateFactory::GS_UNDEFINED ),
m_bRunning( true )
{
m_dwFrameStart = SDL_GetTicks();
}[/source]
Die Funktion IsRunning() liefert also einfach den Stand von m_bRunning zurück. Damit ist klar, wie unsere Hauptschleife in Main.cpp fortan aussehen wird:
[source:cpp]while( GAMEMANAGER- >IsRunning() )[/source]
Selbstverständlich sollten wir irgendwo dann trotzdem noch die Escape-Abfrage unterbringen, damit das Programm irgendwie beendet werden kann. Z.B. in die GSGame::Idle Funktion. Zum Beenden rufen wir dann auch direkt die nächste neue Funktion auf, CGameManager::Quit:
Datei /game/GSGame.cpp
[source:cpp] if( KEYBOARD->KeyDown( SDLK_ESCAPE ) )
{
GAMEMANAGER- >Quit();
}[/source]
Und was ist nun mit der Fehlerbehandlung? Richtig, dafür verwenden wir die Funktion CGameManager::QuitWithError. Problemzone war ja das Laden der Gleitergrafik, also passen wir diese Funktion direkt mal an:
Datei /objects/Glider.cpp/
[source:cpp]// —————————————————————————
void CGlider::Initialize()
{
m_pSurface = SDL_LoadBMP( “resource/gleiter/gleiter.bmp” );
if( 0 == m_pSurface )
{
GAMEMANAGER- >QuitWithError( “Datei resource/gleiter/gleiter.bmp wurde nicht gefunden!” );
}
if( SDL_SetColorKey( m_pSurface, SDL_SRCCOLORKEY, SDL_MapRGB( g_pBackSurface- >format, 0, 0, 0 ) ) < 0 )
{
GAMEMANAGER- >QuitWithError( “Colorkey für resource/gleiter/gleiter.bmp konnte nicht gesetzt werden!” );
}
}[/source]
Wenn nun also ein Fehler auftritt, wird das Programm nicht direkt beendet. Stattdessen wird der GameManager mitsamt aktivem GameState noch ordnungsgemäß heruntergefahren, und auch die SDL am Ende deinitialisiert. Ein Problem ist dann allerdings noch die CGSGame::Leave Funktion, die dann ja sicher auch noch aufgerufen wird. Die wiederum deinitialisiert nämlich den Gleiter, der ja u.U. die Grafik garnicht richtig geladen hat. SDL_FreeSurface könnte hier Probleme bereiten, wenn ein Nullpointer übergeben wird. Wir passen die Funktion CGlider::Uninitialize also auch noch etwas an:
Datei /objects/Glider.cpp
[source:cpp]// —————————————————————————
void CGlider::Uninitialize()
{
if( m_pSurface )
{
SDL_FreeSurface( m_pSurface );
}
}[/source]
Wenn wir diesen Stil fortsetzen, sollten wir ganz gut gegen derartige Fehler gewappnet sein. Im Log wird dann ein schöner Fehlereintrag erzeugt. Zusätzlich könnte man am Ende bei einem Fehler auch noch eine Messagebox o.ä. anzeigen, wir verzichten hier aber mal zwecks Plattformunabhängigkeit darauf.
Der Vollständigkeit halber muß wohl an dieser Stelle erwähnt werden, daß C++ auch Methoden zur Ausnahmebehandlung zur Verfügung stellt. Ich habe mich allerdings dazu entschlossen, diese nicht zu verwenden, um das Tutorial nicht total zu überladen.
Im nächsten Teil erstellen wir eine weitere Manager-Klasse, die sich um Leben und Ableben unserer Spielobjekte kümmern wird.
Weiterführende Links
- An exceptional model
http://www.flipcode.com/articles/article_exceptionalmodel.shtml
Weiterführender Artikel zum Thema Exception Handling - Charles Blooms Coding Tipps
http://www.cbloom.com/3d/techdocs/coding.txt