Teil 13: Resource Preloading

So, es geht endlich mal wieder weiter mit unserem kleinen Tutorial. Zu dumm, wenn man nebenbei noch eine kleine Diplomarbeit schreiben muß 😉

Also, wir hatten beim letzten Mal festgestellt, daß es sich mitunter negativ auf die Performance auswirken könnte, wenn Grafiken ständig neu geladen und wieder freigegeben werden. Es ist ja auch irgendwie Blödsinn. Wir werden im Spiel nicht solche Unmengen an Grafiken haben, als daß wir nicht gleich alle zu Beginn laden könnten. Ebenso wird es sich mit den Sounds verhalten. Wir werden also ein möglichst allgemeines System schreiben, das uns solche Ressourcen zu Beginn lädt. Allgemein bedeutet, daß wir zunächst einmal eine Resource-Klasse entwerfen müssen, die später unsere Grafiken/Sounds/… aufnehmen wird. Im Moment ist da nur der Dateiname interessant, der zu der Resource gehört, damit diese sich hinterher im Notfall auch mal selbst neuladen kann. So sieht unsere Interface-Klasse für die Ressourcen also aus:

Datei /core/Resource.h:
[source:cpp]#ifndef RESOURCE_H
#define RESOURCE_H

// ——————————————————————————–
#include
#include

// ——————————————————————————–
class IResource
{
protected:
std::string m_strFileName;

public:
void SetFileName( const std::string& rstr )
{
m_strFileName = rstr;
}

std::string& GetFileName()
{
return m_strFileName;
}

virtual bool Load( const std::string& rstrFileName ) = 0;
virtual void Unload() = 0;
};

// ——————————————————————————–
typedef std::map< std::string, IResource* > mapStringToResource;

#endif[/source]

Der ein oder andere kennt vielleicht den STL Container “map” noch nicht, den ich hier verwende, deshalb möchte ich ganz kurz darauf eingehen, worum es sich dabei handelt. Die map ist ein sogenannter assoziativer Container, das bedeutet, daß sie zwei Werte miteinander verknüpft, eine Zuordnung herstellt. Ein Array funktioniert ähnlich: Hier kann ich mit einem Index auf ein bestimmtes Element zugreifen. Die Einschränkung ist allerdings, daß der Index eine Ziffer sein muß. Wir möchten über einen Alias, z.B. “glider”, eine Resource anfordern, und “glider” lässt sich schlecht als Array-Index verwenden. Deshalb hilft uns die map, eine Zuordnung von string zu IResource* herzustellen.

Wie sollen solche Resourcen nun definiert werden? Ich mache es hier mal ganz einfach: Wir legen eine Textdatei an, in der eine beliebige Anzahl von Alias und Dateiname untereinander stehen. Ein Resourcemanager, den wir gleich noch angehen werden, lädt solche Dateien und die entsprechenden Resourcen. Für unsere bisherigen Grafiken des Gleiters und des Weltraumschrotts wird die Resourcedatei also zunächst so aussehen:

Datei /resource/preload-gfx.txt:
[source:cpp]glider
resource/gleiter/gleiter.bmp
scrap1
resource/schrott/schrottteil1-1.bmp
scrap2
resource/schrott/schrottteil1.bmp
scrap3
resource/schrott/schrottteil2-1.bmp
scrap4
resource/schrott/schrottteil2-2.bmp[/source]

Der ResourceManager muß nun noch wissen, um was für einen Typ Ressource es sich handelt. Das sollten wir beim Laden als Parameter übergeben können. Aufgrund der Tatsache, daß der ResourceManager von überall aus ansprechbar sein sollte (z.B. wird ja der Gleiter auch wieder sein Bitmap irgendwoher holen müssen), wählen wir für die Implementierung wieder das Singleton-Prinzip.

Datei /core/ResourceManager.h:
[source:cpp]#ifndef RESOURCEMANAGER_H
#define RESOURCEMANAGER_H

// ——————————————————————————–
#include “Resource.h”
#include “ResourceFactory.h”

// ——————————————————————————–
class CResourceManager
{
public:
static CResourceManager* GetInstance();
static void DestroyInstance();

bool Preload( const std::string& rstrListFileName, CResourceFactory::eResourceType eResType );
IResource* GetResource( const std::string& rstrIdentifier );

private:
CResourceManager();
~CResourceManager();

mapStringToResource m_mapResources;

static CResourceManager* m_pInstance;
};

#define RESOURCEMGR CResourceManager::GetInstance()

#endif[/source]

Die Funktion Preload wird uns also dazu dienen, eine Datei wie oben zu sehen zu laden, und die Resourcen in die map m_mapResources einzupflegen. Die Funktion GetResource gibt eine solche Ressource dann wieder zurück, so daß sie im Programm verwertet werden kann. Interessant ist oben noch die Include-Datei ResourceFactory.h.

Wie schon gesagt, wird es verschiedene Resource-Typen geben, z.B. Grafiken im BMP Format oder Sounds im WAV Format. Also muß entsprechend dem in Preload angegebenen Parameter eResType der richtige Ressourcetyp dynamisch angelegt werden. Darum kümmert sich dann diese Factory-Methode. Das Prinzip ist kein anderes, als wie wir es schon beim GameManager oder EntityManager kennengelernt haben. Zunächst aber mal die Implementierung des ResourceManagers:

Datei /core/ResourceManager.cpp:
[source:cpp]#include “ResourceManager.h”
#include
#include

// ——————————————————————————–
using namespace std;

// ——————————————————————————–
CResourceManager* CResourceManager::m_pInstance = 0;

// ——————————————————————————–
CResourceManager* CResourceManager::GetInstance()
{
if( !m_pInstance )
{
m_pInstance = new CResourceManager;
}

return m_pInstance;
}

// ——————————————————————————–
void CResourceManager::DestroyInstance()
{
if( m_pInstance )
{
delete m_pInstance;
m_pInstance = 0;
}
}

// ——————————————————————————–
CResourceManager::CResourceManager()
{
}

// ——————————————————————————–
CResourceManager::~CResourceManager()
{
for( mapStringToResource::iterator it = m_mapResources.begin(); it != m_mapResources.end(); ++it )
{
IResource* pResource = it->second;
pResource- >Unload();

delete pResource;
}
}

// ——————————————————————————–
bool CResourceManager::Preload( const std::string& rstrListFileName, CResourceFactory::eResourceType eResType )
{
LOG << "INFO: Preloading file " << rstrListFileName << ENDL; ifstream clFile( rstrListFileName.c_str() ); if( clFile.fail() ) { return false; } std::string strAlias; std::string strFileName; while( !clFile.eof() ) { clFile >> strAlias;
clFile >> strFileName;

if( strAlias != “” && strFileName != “” )
{
LOG << "INFO: Loading resource " << strAlias << ": " << strFileName << ENDL; IResource* pResource = CResourceFactory::Create( eResType ); if( !pResource ) { return false; } m_mapResources.insert( make_pair( strAlias, pResource ) ); pResource- >SetIdentifier( strAlias );
pResource- >SetFileName( strFileName );

if( !pResource->Load( strFileName ) )
{
return false;
}
}
}

LOG << "INFO: Successfully finished preloading file " < second;
}

return 0;
}[/source]

Die eben angesprochene Factory sieht vorerst folgendermaßen aus:

Datei /core/ResourceFactory.h:
[source:cpp]#ifndef RESOURCEFACTORY_H
#define RESOURCEFACTORY_H

// ——————————————————————————–
#include “Resource.h”

// ——————————————————————————–
class CResourceFactory
{
public:
enum eResourceType
{
RESOURCE_BMP
};

static IResource* Create( eResourceType eType );
};

#endif[/source]

Datei /core/ResourceFactory.cpp:
[source:cpp]#include “ResourceFactory.h”
#include “ResourceBMP.h”

// ——————————————————————————–
IResource* CResourceFactory::Create( eResourceType eType )
{
switch( eType )
{
case RESOURCE_BMP: return new CResourceBMP;
}

return 0;
}[/source]

Fehlt nun nur noch der erste tatsächliche Ressourcetyp, die Bitmap-Ressource. Die unterscheidet sich zunächst einmal nur durch einen SDL_Surface* als Member von der Basisklasse:

Datei /core/ResourceBMP.h:
[source:cpp]#ifndef RESOURCEBMP_H
#define RESOURCEBMP_H

// ——————————————————————————–
#include “Resource.h”
#include
#include

// ——————————————————————————–
class CResourceBMP : public IResource
{
public:
bool Load( const std::string& rstrFileName );
void Unload();

SDL_Surface* GetSurface() const
{
return m_pSurface;
}

private:
SDL_Surface* m_pSurface;
};

#endif[/source]

Datei /core/ResourceBMP.cpp:
[source:cpp]#include “ResourceBMP.h”

// ——————————————————————————–
bool CResourceBMP::Load( const std::string& rstrFileName )
{
m_pSurface = SDL_LoadBMP( rstrFileName.c_str() );
if( 0 == m_pSurface )
{
return false;
}

m_strFileName = rstrFileName;

return true;
}

// ——————————————————————————–
void CResourceBMP::Unload()
{
if( m_pSurface )
{
SDL_FreeSurface( m_pSurface );
m_pSurface = 0;
}

m_strFileName = “”;
}[/source]

Wie Du Dir sicher denken kannst, werden unsere Klassen also in Zukunft nicht mehr direkt den SDL_Surface* halten, sondern einen CResourceBMP*, den sie vom ResourceManager anfordern.

Die große Frage ist nun noch: Wo werden die Grafiken vorgeladen? Dafür werden wir, oder besser: Du, einen neuen GameState einfügen, GS_Preload. Hier soll zunächst nur die Preload-Methode für die Grafikliste aufgerufen werden, und dann direkt zum nächsten GameState GS_Game gewechselt werden. Das ganze soll Deine Aufgabe bis zum nächsten Tutorial-Teil sein.

Aufgaben

  • Erstelle eine neue GameState-Klasse CGSPreload, in welcher in der Idle-Methode die Liste von Grafiken über den ResourceManager vorgeladen wird und dann zum Spiel-GameState gewechselt wird.
  • Ändere den Start-GameState von GS_Game zu GS_Preload.
  • Sorge dafür, daß Gleiter- und Schrott-Klasse nicht mehr direkt die Grafiken laden, sondern die vom Resource-Manager angeforderten CResourceBMP* verwenden.

Übrigens: Das ganze Ressource-Management-Zeugs ist ein Kapitel für sich, und man könnte das beliebig kompliziert gestalten. Normalerweise gehört da auf jeden Fall noch sowas wie Reference Counting etc. rein. Wir halten es aber erstmal unseren Zwecken entsprechend einfach.

No Comments

Cancel