Introduction
I took a task of writing a white paper about designing a game engine. For this work, I started implementing a framework in C++ which involves the basic implementation of Design Patterns and C++ concepts like Policy based design. This article talks about my design, and it includes a compiled code snippet as well.
Background
This describes a framework which makes use of famous design paradigms like Policy based design, Decorator and Strategy Patterns, and is implemented using C++.
Using the Code
Use of Policy Based Design for Settings
Before even diving into the nifty gritty of a Football gaming engine design, any game during start-up has to allow the user to choose the Difficulty level. I am assuming there are three levels of difficulty, by default: Low, Medium, and High. Since the level has to be chosen at the beginning, this gives me an opportunity to use Policy based design using template classes (based on Andrei Alexandrescu's book, Modern C++ Design). So, the difficulty level can be a policy here. Also, I am going to add another policy for the playing mode. The playing mode can be Auto (in this case, you will play against the machine) or Multi (you can play with your friend).
Policy based design has been described as a compile-time variant of the Strategy pattern
template <typename T>
struct DifficultyLevel_High
{
static void setDifficultyLevel(T&) { cout << "Setting to High difficulty
level" <<endl; }
};
template <typename T>
struct DifficultyLevel_Med
{
static void setDifficultyLevel(T&)
{ cout << "Setting to Medium difficulty level" <<endl; }
};
template <typename T>
struct DifficultyLevel_Low
{
static void setDifficultyLevel(T&)
{cout << "Setting to Low difficulty level" <<endl; }
};
template <typename T>
struct Playmode_policy_auto
{
static void setPlaymodepolicy(T&)
{ cout << "Setting to auto Playmode" <<endl; }
};
template <typename T>
struct Playmode_policy_multi
{
static void setPlaymodepolicy(T&)
{ cout << "Setting to multi Playmode" <<endl; }
};
class FootballEngineType
{
public:
FootballEngineType()
{ cout << "Engine set as Football " << endl;
}
};
template< typename T,
template <typename> class DifficultyLevel_policy,
template <typename> class Playmode_policy >
class GamingEngine
{
public:
void Run()
{
DifficultyLevel_policy<T>::setDifficultyLevel(engineType);
Playmode_policy<T> ::setPlaymodepolicy(engineType);
start();
}
private:
T engineType;
};
The next important thing is that a team should be able to change the logic and strategy of the game at runtime. For example, a user can opt to go for a defend instead of an attack. This can be made possible using the Strategy pattern. We can have a list of algorithms for defending, attacking etc., so that the user is able to choose the team strategy at runtime. This is possible by using the Strategy pattern. Here, I have defined three strategies, and the strategies can be set by the GameLogic
class.
class Strategy
{
public:
Strategy() {}
virtual void Apply()=0;
virtual ~Strategy() {}
};
class DefendStrategy : public Strategy{
public:
DefendStrategy():Strategy() { cout << "Defend strategy set" << endl; }
void Apply() { cout << "Defend strategy applied" << endl; }
virtual ~DefendStrategy() {}
};
class AttackStrategy: public Strategy
{
public:
AttackStrategy():Strategy() { cout << "Attack strategy set" << endl; }
void Apply() { cout << "Attack strategy applied" << endl; }
virtual ~AttackStrategy() {}
};
class MediumStrategy: public Strategy
{
public:
MediumStrategy() :Strategy(){ cout << "Medium strategy set" << endl; }
void Apply() { cout << "Medium strategy applied" << endl; }
virtual ~MediumStrategy() {}
};
class GameLogic
{
public:
StratType StrategyType;
GameLogic()
{
m_Strategy = NULL;
}
void SetStrategy(StratType type)
{
if (m_Strategy) delete m_Strategy;
if (type == Med)
m_Strategy = new MediumStrategy();
else if (type == Defend)
m_Strategy = new DefendStrategy();
else if (type == Attack)
m_Strategy = new AttackStrategy();
}
void Exec() { m_Strategy->Apply(); }
~GameLogic() { if (m_Strategy) delete m_Strategy; }
private:
Strategy *m_Strategy;
};
Then comes the different roles each entity can perform. Each team has a list of players, coach, physiotherapist, manager, referee, and team CEO etc. Each one in a team can perform one or more roles, and the role can be assigned at runtime based on the credibility and other parameters. Also, each person as a player can have different responsibilities like Forward, Defender, Midfielder, and Goalkeeper etc. This should also be done at runtime without using sub classing.
Both the roles and responsibilities are assigned at runtime using the Decorator pattern.
Following are the helper functions used to get the roles and responsibilities of a gaming entity (a person) at runtime:
template <class T>
T* getresponsibility_entity(GameEntity *pEnt)
{
return dynamic_cast<T*>(pEnt->GetResponsibility(T::RESP_CLSID));
}
template <class T>
T* getroles_entitiy(GameEntity *pEnt)
{
return dynamic_cast<T*>(pEnt->GetRole(T::ROL_CLSID));
}
Following is the code snippet which creates a gaming entity and assigns roles and responsibilities at run time and retrieves those objects using the above mentioned helper functions (for the full implementation, please refer to the attached CPP file):
GameEntity* play1 = new GameEntity("Beckham", "David");
play1->AddRole(new Player(play1));
Player *playRole = getroles_entitiy<Player>(play1);
play1->AddResponsibilities(new ToPlay(play1));
play1->AddResponsibilities(new ToManage(play1));
Also, different teams can play for different league matches in different playgrounds with different settings. For example, each entity in a Football game can be clubbed as a team, and then different presentation settings can be applied. You can add players to a specific football club (dress colour) and add different teams for a league match (like English premier league etc.). Here, the actual abstraction of each entity (Team/League match) can be decoupled from the implementation (League/Football ground/Presentation settings etc.) using the Bridge pattern. Hence the implementation and the abstraction can vary independently. Alternatively, the Builder pattern can also be used for the same task.
The entire design is implemented/compiled and tested in the Visual Studio 2005 (VC8.0) compiler. The implemented CPP files FootballEngine.cpp and stdafx.h are attached.
Points of Interest
Future Add-Ons
I was thinking of using the Subject-Observer pattern to notify players (Gaming entity) about the strategy and the position of the ball and the co-ordinates of other opponents. I will add it in a later stage.
History
N/A