Making a 3D game with Ogre – State Management
DOWNLOAD THE DEMO AND SOURCE CODE FOR WINDOWS
DOWNLOAD THE DEMO AND SOURCE CODE FOR LINUX
In order to complete a level we need to implement some sort of state management. This state management will be responsible for cleaning up all the objects in a level, as well as the level itself, and loading the next level. The Startup and Shotdown pair of functions already allow objects to be easily added and removed from the game, but currently the Shutdown function for the manager classes (EnemyDatabase, WeaponDatabase, ParticleSystemEffectManager and IrrKlangEngineManager) will remove and delete the objects they manage. When switching levels we don’t want to delete the pooled objects, just shut them down ready for reuse in the new level.
In order to shutdown, but not delete, the pooled objects a new function will be added to the manager classes called Cleanup. Below is the Cleanup function for the ParticleSystemEffectManager class. Notice that it loops through the collection of the ParticleSystemEffects and calls their Shutdown function, but leaves the objects in the pool.
void ParticleSystemEffectManager::Cleanup()
{
for (ParticleSystemEffectList::iterator iter =
particleSystemEffectList.begin();
iter != particleSystemEffectList.end(); ++iter)
{
ParticleSystemEffect* effect = *iter;
if (effect->IsStarted())
effect->Shutdown();
}
}
The Shutdown function goes one step further, calling the Cleanup function to Shutdown the ParticleSystemEffect objects, and then doing a second loop through the pool to delete the objects, reclaiming the memory they take up.
void ParticleSystemEffectManager::Shutdown()
{
Cleanup();
for (ParticleSystemEffectList::iterator iter = particleSystemEffectList.begin();
iter != particleSystemEffectList.end(); ++iter)
{
delete *iter;
}
particleSystemEffectList.clear();
}
The Cleanup function is called in between levels, whereas the Shutdown function is called just before the game is exited. To manage the transition between levels (and eventually menu screens, high score screens etc) is the StateManager class.
Similar changes are made to the other manager classes. Check out the source code to see the exact logic in the new Cleanup functions.
StateManager.h
#ifndef STATEMANAGER_H_
#define STATEMANAGER_H_
#include “map”
#include “string”
#define STATEMANAGER StateManager::Instance()
typedef std::map LevelMap;
class StateManager
{
public:
~StateManager();
static StateManager& Instance()
{
static StateManager instance;
return instance;
}
void Startup(const std::string& levelMapXML);
void Shutdown();
void SetIntroState();
void SetGameState();
void FinishGameState();
void MapSceneXMLToLevel(const int level, const std::string& XML);
protected:
StateManager();
void InitialiseVariables();
void CleanupComponents();
LevelMap levelMap;
int currentLevel;
};
#endif
StateManager.cpp
#include “StateManager.h”
#include “WeaponDatabase.h”
#include “EnemyDatabase.h”
#include “GameLevel.h”
#include “CollisionManager.h”
#include “IrrKlangEngineManager.h”
#include “ParticleSystemEffectManager.h”
#include “DotSceneLoader.h”
#include “GameConstants.h”
StateManager::StateManager()
{
InitialiseVariables();
}
StateManager::~StateManager()
{
}
void StateManager::InitialiseVariables()
{
currentLevel = 1;
}
The StateManager maintains a mapping between level numbers and the XML files that will be used to load those levels. This mapping is itself loaded from an XML file, thanks to the DotSceneLoader class. Here in the Startup function the StateManager initialises itself with the supplied XML file.
void StateManager::Startup(const std::string& levelMapXML)
{
DOTSCENELOADER.parseDotScene(levelMapXML, LEVEL_GROUP_NAME);
}
void StateManager::Shutdown()
{
levelMap.clear();
InitialiseVariables();
}
The SetGameState and FinishGameState functions are designed to be called in sequence. The SetGameState function will start up the GameLevel function, supplying the XML file that defines the level. The FinishGameState increments the currentLevel, which indicates the level that should be loaded next (or more correctly it is an index to the XML file that should be loaded for the next level), cleans up all of the manager classes, shuts down the current level and then calls the SetGameState function to load the next level.
void StateManager::SetGameState()
{
if (levelMap.find(currentLevel) != levelMap.end())
GAMELEVEL.Startup(levelMap[currentLevel]);
}
void StateManager::FinishGameState()
{
if (levelMap.find(currentLevel + 1) != levelMap.end())
++currentLevel;
else
currentLevel = 1;
CleanupComponents();
GAMELEVEL.Shutdown();
SetGameState();
}
The CleanupComponents function calls the new Cleanup functions that have been added to the manager classes. In doing so all of the persistent game objects like enemies, weapons and explosions are removed from the level. This stops these objects from crashing because they are still trying to be moved or played after the GameLevel SceneManager has been cleaned up, destroying the particle systems and models that were created by it.
void StateManager::CleanupComponents()
{
WEAPONDATABASE.Cleanup();
ENEMYDATABASE.Cleanup();
IRRKLANGENGINEMANAGER.Cleanup();
PARTICLESYSTEMEFFECTMANAGER.Cleanup();
}
The MapSceneXMLToLevel function is used by the DotSceneManager when loading the level number to scene XML file mapping.
void StateManager::MapSceneXMLToLevel(const int level, const std::string& XML)
{
levelMap[level] = XML;
}
The GameLevel is then modified to trigger the change between levels. It calls a new function on the EnemyDatabase class called AllEnemiesFinished to work out if all the enemies have been placed in the level and then subsequently destroyed or moved off the screen. If this is the case there is nothing left to do in the level, and the level needs to be finished. A short delay, indicated by the timeToEndOfLevel variable, counts down, and then the StateManager FinishGameState function is called to trigger the change to the next level.
bool GameLevel::FrameStarted(const FrameEvent& evt)
{
if (playerSceneNode->getPosition().z > this->levelLength – this->cameraStartZ)
{
float translateDist = SCROLL_SPEED * evt.timeSinceLastFrame;
float distToEnd = playerSceneNode->getPosition().z – (this->levelLength – this->cameraStartZ);
playerSceneNode->translate(-1 * Vector3(0, 0, translateDist>distToEnd?distToEnd:translateDist));
}
camera->setPosition(playerSceneNode->getPosition() + Vector3(0, PLAYER_Y, 0));
if (timeToEndOfLevel != -1)
{
timeToEndOfLevel -= evt.timeSinceLastFrame;
if (timeToEndOfLevel
return true;
}
To show off the level transitions a new scene XML file has been added to the game, along with the LevelMap.XML file that tells the StateManager which scene XML files relate to which levels in the game. Now when all the enemies are destroyed the game will move to the next level.
Written by mcasperson
Find More Ad Management Articles

