#5 Framework 1 – Timers

The first thing I have to say is, my programming style is going to change a little bit from the previous tutorial. The reason for this is quite simply, we needed to get a base project up and running, so the code was just the quickest possible way to get there. As the tutorial controller is still a pretty temporary class, I’m not going to spend any time in tidying that up, however, what we’re going to be working on now will be the building blocks for a game framework built on top of Maratis. The first thing on our list of things to do is a timer. A stopwatch, if you will. For that we’re going to need a bit of infrastructure. But first, a design. I’ve tried to find some online UML tools I can use to describe this, but I’ve not got anything as of now, so I’m going to do it by hand. (note 1)

I hope this diagram's correct
Design for Timer

Note: I usually don’t do UML diagrams, I have a lot of vaguely UML-ish scribbles on various bits of paper and notebooks. As such I don’t have a clue whether what I’ve done is strictly UML. Please don’t shout at me if it’s not.

Ss you can see, we have 4 classes we’re going to write. That’s actually incorrect, but I’ll explain that in a bit. Let’s have a look at the classes we’re wanting.

  • TutorialGame – this will be inherited from MGame and will be added to MEngine. It will be the central point of contact for all things game related. (note 2)
  • GameClock – this will keep track of the update time of the game. I’m not sure whether Maratis has a fixed timestep, but I couldn’t find anywhere to get the time delta between frames, and it’s always a good thing to have, especially if you end up slowing down. You want to be time dependant, not framerate dependant.
  • Timer – this is the bit which you will add to anything that needs timing. As not every class will necessarily have an update function, and it might be problematic to route updates from somewhere, we’ll get the game clock to update this automatically, which is why the game clock has a list of timers.
  • ITimerAction – this interface is an example of the command design pattern. There will be no implementation at all, but the Timer, when it expires, will call the OnTimerExpired function.

When looking at this design, you might ask why the game clock isn’t a singleton. The reason for this is, I’m not particularly fond of having a lot of singletons floating around. They can come in very useful when you need only one of an object, and it’s needed in a lot of places. I don’t think that’s necessarily the case here though. For one thing, we already have one singleton, MEngine, and the game clock is only a few levels below that, so it’s not difficult to get to. Also, it’s possible that we may want to replace the game clock at a later date, for example, if we ever make a network game, then we might want a clock that sync’s over the network… or something. It’s always nice to have that option anyway.

Next, as I said, it’s not actually 4 classes. ITimerAction is actually an interface, so I’m not sure if that counts as 1 or 0.5. The idea is that we’re going to make the TutorialController as an ITimerAction. This brings up another point. What happens if we need several timers associated with a class, one option would be to implement small timer actions externally to the class for each timer. This can get a bit awkward as we have to create and keep multiple classes. The solution is to put an ID into the timer, so we can distinguish which one has completed. We’ll try this out in a bit, for now, let’s start with the code.

To begin with, we need to make 6 files:

  • TutorialGame.h
  • TutorialGame.cpp
  • GameClock.h
  • GameClock.cpp
  • Timer.h
  • Timer.cpp

Then you’ll need to re-run premake4 to recreate the project. When you head over to Visual Studio, you should be able to see your new files in there.

TutorialGame.h
#ifndef __TUTORIAL_GAME_H__
#define __TUTORIAL_GAME_H__

#include "MEngine.h"

//--------------------------------------------
// TutorialGame
//
// Our friendly neighbourhood game class
// This class should wrap up any game specific
// functionality
//--------------------------------------------
class TutorialGame : public MGame
{
public:
	TutorialGame();
	~TutorialGame();

	//----------------------------------------
	// MGameVirtuals
	//----------------------------------------
	virtual void update();
};

#endif /*__TUTORIAL_GAME_H__*/
TutorialGame.cpp
#include "TutorialGame.h"

//----------------------------------------
// TutorialGame
//----------------------------------------
TutorialGame::TutorialGame()
{
	// for now, we have nothing to construct
}
//----------------------------------------
TutorialGame::~TutorialGame()
{
	// for now, we have nothing to destruct
}
//----------------------------------------
void TutorialGame::update()
{
	// for now, all we want to do is make
	// sure the Maratis functionality
	// continues
	MGame::update();
}
TutorialPlugin.cpp
// Standard include
#include "TutorialPlugin.h"

// Project includes
#include "TutorialBehaviour.h"
#include "TutorialGame.h"

// System includes

// static games.
MGame* s_prevGame = NULL;
TutorialGame* s_tutGame = NULL;

void StartPlugin()
{
	MEngine* engine = MEngine::getInstance();
	MBehaviorManager * behaviorManager = engine->getBehaviorManager();
	behaviorManager->addBehavior(TutorialBehaviour::getStaticName(),
						M_OBJECT3D,
						TutorialBehaviour::getNew);

	// just cache the previous game, shouldn't need to, but just in case
	s_prevGame = engine->getGame();
	s_tutGame = new TutorialGame;
	engine->setGame(s_tutGame);
}

void EndPlugin()
{
	// check that the game we're removing is ourself
	MEngine* engine = MEngine::getInstance();
	if(engine->getGame() == s_tutGame)
		engine->setGame(s_prevGame);
	// delete me
	SAFE_DELETE(s_tutGame);
}

So, we’ve now got a custom game class linking into Maratis. At the moment, it does absolutely nothing. Now, there are 2 ways I can do that, either by explaining a bit at a time, which will compile each time, or I can just put a huge load of code and explain it all in one big go. As it would probably mean that I would be repeatedly showing the same files, with a few changes each time, I think I’ll just show all the files now. Hope noone minds. So, let’s start off with with the game clock.

GameClock.h
#ifndef __GAME_CLOCK_H__
#define __GAME_CLOCK_H__

#include 

class Timer;

//--------------------------------------------
// typedef's
//--------------------------------------------
typedef long int clocktime;

//--------------------------------------------
// GameClock
//
// Takes care of any active timers, also
// provides an interface for getting the delta
// between ticks
//--------------------------------------------
class GameClock
{
public:
	GameClock();

	//----------------------------------------
	// Public interface
	//----------------------------------------
	void	Update();

	Timer*		CreateTimer(int id);
	void		DestroyTimer(Timer* timer);

	clocktime	GetDeltaMs();

private:
	//----------------------------------------
	// Internal types
	//----------------------------------------
	typedef std::vector timerVec;
	typedef timerVec::iterator	timerVecIter;

	//----------------------------------------
	// Members
	//----------------------------------------
	timerVec	m_Timers;

	clocktime	m_StartTime;
	clocktime	m_CurTime;
	clocktime	m_Delta;
};

#endif /*__GAME_CLOCK_H__*/

So, pretty much everything is as was layed out in the diagram at the beginning. I just decided to expose the delta, as it’s a generally useful thing to know about. Also, much as the design said that we would be using a uint64 for the time, we’re wrapping it in a type definition so that if we change it at any point, we don’t have to change code everywhere.

GameClock.cpp
#include "GameClock.h"

#include "Timer.h"

#include
#include 

//----------------------------------------
// getCurTime
// quick helper function to lookup tick
// time
//----------------------------------------
clocktime getCurTime()
{
	MEngine* engine = MEngine::getInstance();
	MSystemContext* system = engine->getSystemContext();

	return system->getSystemTick();
}

//----------------------------------------
GameClock::GameClock()
{
	m_StartTime = getCurTime();
	m_CurTime = m_StartTime;
	m_Delta = 0;
}
//----------------------------------------
void GameClock::Update()
{
	// get the new time and then work out the delta
	clocktime prevTime = m_CurTime;
	m_CurTime = getCurTime();
	m_Delta = m_CurTime - prevTime;

	// update any attached timers
	for(timerVecIter iTimer = m_Timers.begin();
		iTimer != m_Timers.end();
		iTimer++)
	{
		(*iTimer)->Update(m_Delta);
	}
}
//----------------------------------------
Timer* GameClock::CreateTimer(int id)
{
	// create a new timer and push it
	// back onto the update list
	Timer* timer = new Timer(id);
	m_Timers.push_back(timer);
	return timer;
}
//----------------------------------------
void GameClock::DestroyTimer(Timer* timer)
{
	// try to find the timer and remove from
	// the update list
	timerVecIter iTimer = std::find(m_Timers.begin(),
						m_Timers.end(),
						timer);
	if(iTimer != m_Timers.end())
	{
		m_Timers.erase(iTimer);
	}

	// regardless of whether it was in the
	// list, we need to delete the timer
	delete timer;
}
//----------------------------------------
clocktime GameClock::GetDeltaMs()
{
	return m_Delta;
}

We’ve wrapped the current time in a little helper function, just because it’s easier than writing the whole lookup every time. The functionality of the game clock is pretty simple, when update is called, it calculates the difference between the last frame and this one, then updates all the attached timers. CreateTimer/DestroyTimer are little more than factory wrappers for the creation of timer objects, the only difference is that they also add/remove to the list of automatically updated timers. Next up, the timer.

Timer.h
#ifndef __TIMER_H__
#define __TIMER_H__

#include "GameClock.h"

//--------------------------------------------
// ITimerAction
//
// Command pattern. Timer callback.
//--------------------------------------------
class ITimerAction
{
public:
	virtual void OnTimerComplete(int id) = 0;
};

#include 

//--------------------------------------------
// Timer
//
// Class to count down to an event.
//--------------------------------------------
class Timer
{
public:
	Timer(int id);

	//----------------------------------------
	// Public interface
	//----------------------------------------
	// Start/stop the timer. Won't affect
	// the time remaining
	void		Start();
	void		Stop();

	// Check whether the timer is still running.
	// Can be not running but not expired.
	bool		IsRunning();

	void		SetTimeMs(clocktime ms);
	clocktime	GetTimeRemaining();
	bool		HasExpired();

	// Action interface
	void AddAction(ITimerAction* action);
	void RemoveAction(ITimerAction* action);

	void		Update(clocktime dt);
private:
	// internal types
	typedef std::vector	actionVec;
	typedef actionVec::iterator			actionVecIter;

	actionVec	m_Actions;

	clocktime	m_TimeMs;
	bool		m_IsRunning;

	// every Timer used by a class should have a
	// different ID. It's only used for letting
	// the action know which timer has expired
	// so there's no need for the id to be
	// unique across the whole system.
	int			m_ID;
};

#endif /*__TIMER_H__*/

I ended up adding a few things to the timer from the design. I figured that if we’re going to add actions, might be a good idea to be able to remove them too. Also, I added a few more accessors for if the Timer class is being updated manually, without the game clock. I also added an ID which I forgot in the design.

Timer.cpp
#include "Timer.h"

#include 

//----------------------------------------
// Timer
//----------------------------------------
Timer::Timer(int id)
:m_ID(id)
,m_IsRunning(true)
{
}
//----------------------------------------
void Timer::Start()
{
	m_IsRunning = true;
}
//----------------------------------------
void Timer::Stop()
{
	m_IsRunning = false;
}
//----------------------------------------
bool Timer::IsRunning()
{
	return m_IsRunning;
}
//----------------------------------------
void Timer::Update(clocktime dt)
{
	if(m_IsRunning && m_TimeMs > 0)
	{
		// tick the timer
		m_TimeMs -= dt;

		// check if we've expired
		if(m_TimeMs < 0)
		{
			// make sure the timer doesn't
			// get below 0
			m_TimeMs = 0;

			// let any attached actions know
			actionVecIter iAction;
			for(iAction = m_Actions.begin();
				iAction != m_Actions.end();
				iAction++)
			{
				(*iAction)->OnTimerComplete(m_ID);
			}
			m_IsRunning = false;
		}
	}
}
//----------------------------------------
void Timer::SetTimeMs(clocktime ms)
{
	m_TimeMs = ms;
}
//----------------------------------------
clocktime Timer::GetTimeRemaining()
{
	return m_TimeMs;
}
//----------------------------------------
bool Timer::HasExpired()
{
	return m_TimeMs <= 0;
}
//----------------------------------------
void Timer::AddAction(ITimerAction* action)
{
	// make sure that we haven't already
	// added this action, otherwise we
	// would get OnTimerComplete called
	// multiple times
	if(std::find(	m_Actions.begin(),
					m_Actions.end(),
					action) == m_Actions.end())
	{
		m_Actions.push_back(action);
	}
}
//----------------------------------------
void Timer::RemoveAction(ITimerAction* action)
{
	// lookup the action and remove
	actionVecIter iAction =
		std::find(	m_Actions.begin(),
					m_Actions.end(),
					action);
	if(iAction != m_Actions.end())
	{
		m_Actions.erase(iAction);
	}
}

So, it turned out that this class was nice and simple after all. There are only a few things to wrap up here. AddAction and RemoveAction are similar to creating the timers in the game clock, but we don’t manage the actions, we’re just assuming they’ll be cleaned up externally. That only leaves the update function. Going through this, it should be pretty self explanitory. Basically, we reduce the time remaining, then check if it’s complete, if so, we tell all the actions.

There we have it. Our timer system is complete. Now all we need to do is hook it up to what we had. First of all, we just need to add it to the TutorialGame class and expose it.

TutorialGame.h
#ifndef __TUTORIAL_GAME_H__
#define __TUTORIAL_GAME_H__

#include "MEngine.h"

#include "GameClock.h"

//--------------------------------------------
// TutorialGame
//
// Our friendly neighbourhood game class
// This class should wrap up any game specific
// functionality
//--------------------------------------------
class TutorialGame : public MGame
{
public:
	TutorialGame();
	~TutorialGame();

	//----------------------------------------
	// MGame virtuals
	//----------------------------------------
	virtual void update();

	//----------------------------------------
	// Public interface
	//----------------------------------------
	GameClock* GetGameClock() { return &m_Clock; }

private:
	GameClock m_Clock;
};

#endif /*__TUTORIAL_GAME_H__*/

So, what else is left? Oh yes, the actual usage of the timer. Let’s get to that then.

TutorialBehaviour.h
#ifndef __TUTORIAL_BEHAVIOUR_H__
#define __TUTORIAL_BEHAVIOUR_H__

#include "Timer.h"

#include 

//--------------------------------------------
// TutorialBehaviour
//
// Will give our happy little cube some
// character
//--------------------------------------------
class TutorialBehaviour : public MBehavior, public ITimerAction
{
public:
// snip
// ...

	//----------------------------------------
	// ITimerAction virtuals
	//----------------------------------------
	void OnTimerComplete(int id);

private:
	// timers
	enum
	{
		eBounceTimer,
	};

	// Exposed values
	float		m_RangeX;
	float		m_StepX;
	float		m_RangeZ;
	float		m_StepZ;

	// Member variables
	float		m_CurrDeltaX;
	float		m_CurrDeltaZ;
	MVector3	m_RootPosition;
	Timer*		m_BounceTimer;
	bool		m_IsBouncing;

	// Private interface
	void Setup(float step, float delta, float range);
};

#endif /*__TUTORIAL_BEHAVIOUR_H__*/

So, what’s changed here? Well, we’ve added the timer, we’ve added movement variables for the bounce (must remember that Z is up…). Also we’re using an enum for the timer ids. That’s about all here. Unfortunately the last file is a pretty big. But then that’s it.

TutorialBehaviour.cpp
#include "TutorialBehaviour.h"

#include "TutorialGame.h"

//----------------------------------------
// quick defines
//----------------------------------------
#define BOUNCE_TIMEOUT 5000

//----------------------------------------
// TutorialBehaviour
//----------------------------------------
TutorialBehaviour::TutorialBehaviour(MObject3d * parentObject)
:MBehavior(parentObject)
,m_IsBouncing(false)
{
	Setup(1.0f, 0, 10.0f);
}
//----------------------------------------
TutorialBehaviour::TutorialBehaviour(TutorialBehaviour & behavior,
							MObject3d * parentObject)
:MBehavior(parentObject)
,m_IsBouncing(false)
{
	Setup(behavior.m_StepX, behavior.m_CurrDeltaX, behavior.m_RangeX);
}
//----------------------------------------
void TutorialBehaviour::Setup(float step, float delta, float range)
{
	// setup the movement information
	m_StepX = step;
	m_CurrDeltaX = delta;
	m_RangeX = range;
	m_StepZ = step;
	m_CurrDeltaZ = delta;
	m_RangeZ = range;

	if(MObject3d* parent = getParentObject())
		m_RootPosition = parent->getPosition();

	MEngine* engine = MEngine::getInstance();
	if(MGame* game = engine->getGame())
	{
		// make a new timer and start it ticking
		TutorialGame* tutGame = (TutorialGame*)game;
		if(GameClock* clock = tutGame->GetGameClock())
		{
			m_BounceTimer = clock->CreateTimer(eBounceTimer);
			m_BounceTimer->AddAction(this);
			m_BounceTimer->SetTimeMs(BOUNCE_TIMEOUT);
			m_BounceTimer->Start();
		}
	}
}
//----------------------------------------
TutorialBehaviour::~TutorialBehaviour(void)
{
	MEngine* engine = MEngine::getInstance();
	if(MGame* game = engine->getGame())
	{
		TutorialGame* tutGame = (TutorialGame*)game;
		if(GameClock* clock = tutGame->GetGameClock())
		{
			clock->DestroyTimer(m_BounceTimer);
		}
	}
}
//----------------------------------------
void TutorialBehaviour::destroy()
{
	delete this;
}
//----------------------------------------
MBehavior* TutorialBehaviour::getNew(MObject3d* parentObject)
{
	return new TutorialBehaviour(parentObject);
}
//----------------------------------------
MBehavior* TutorialBehaviour::getCopy(MObject3d* parentObject)
{
	return new TutorialBehaviour(*this, parentObject);
}
//----------------------------------------
unsigned int TutorialBehaviour::getVariablesNumber()
{
	return 4;
}
//----------------------------------------
MVariable TutorialBehaviour::getVariable(unsigned int id)
{
	switch(id)
	{
	case 0:
		return MVariable("XRange", &m_RangeX, M_VARIABLE_FLOAT);
		break;
	case 1:
		return MVariable("XStep", &m_StepX, M_VARIABLE_FLOAT);
		break;
	case 2:
		return MVariable("ZRange", &m_RangeZ, M_VARIABLE_FLOAT);
		break;
	case 3:
		return MVariable("ZStep", &m_StepZ, M_VARIABLE_FLOAT);
		break;
	default:
		return MVariable("NULL", NULL, M_VARIABLE_NULL);
	}
}
//----------------------------------------
void TutorialBehaviour::update()
{
	MEngine* engine = MEngine::getInstance();

	MGame* game = engine->getGame();

	if(game == 0  || !game->isRunning())
		return;

	MObject3d* parent = getParentObject();

	if(!parent)
		return;

	if(MInputContext* input = engine->getInputContext())
	{
		if(input->isKeyPressed("SPACE"))
		{
			m_CurrDeltaX += m_StepX;

			if( abs(m_CurrDeltaX) >= abs(m_RangeX) )
				m_StepX = -m_StepX;
		}
	}

	// bouncing
	if(m_IsBouncing)
	{
		m_CurrDeltaZ += m_StepZ;

		// if we hit the top of the bounce
		if( m_CurrDeltaZ >= m_RangeZ )
		{
			// just start going down again
			m_StepZ = -m_StepZ;
		}
		// if we've hit the botoom of the bounce
		else if(m_CurrDeltaZ <= 0)
		{
			// reset the timer and prepare for the next bounce
			m_StepZ = -m_StepZ;
			m_IsBouncing = false;
			m_BounceTimer->SetTimeMs(BOUNCE_TIMEOUT);
			m_BounceTimer->Start();
		}
	}

	MVector3 pos = m_RootPosition;
	pos.x += m_CurrDeltaX;
	pos.z += m_CurrDeltaZ;

	parent->setPosition(pos);
}
//----------------------------------------
void TutorialBehaviour::OnTimerComplete(int id)
{
	// timer has finished, just check which one
	switch(id)
	{
	case eBounceTimer:
		m_IsBouncing = true;
		break;
	};
}

We’ve already talked a lot about how all this works, so it should be fairly easy to follow through. The bouncing motion is quite simple and very similar to the sideways movement. The only difference is that when it hits 0 again, it gets flagged as not bouncing any more and starts the timer.

So, that’s it. Done. Was a bit of a long winded way of doing it, but as the framework is now there, adding more timers into the game should be trivial, and it’s a step towards making the well designed game framework for the game we’re working on.

<-Previous Next->

Discuss

Notes:

  1. Whenever you start working on something, sort out a design. I won’t cover code design here, maybe later. However, one thing to keep in mind is that you don’t need to write it down. There’s no reason why you can’t  simply design it in your head, if the problem is trivial enough. However you do need to think through the problem. Having said that, diagrams are always good.
  2. There is always a risk of creating a “god class”. One class that knows and does all. You really REALLY don’t want to do that. If one part of your game design changes, it means delving into a gigantic behemoth of a class and figuring out what it all does again. There are also many other reasons why you don’t want to do things like this.

Leave a Reply

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

Diary of a Slacker