Модульная архитектура игрового движка
При работе над архитектурой игрового движка (на C++) появилось следующее соображение. Если игра состоит из нескольких глав или режимов, каждый из которых загружает свои ресурсы и отличается своей игровой логикой, то каждый режим или главу удобно оформить в виде отдельного класса со своими Init/Release Resources, Process и Draw. Ниже описана архитектура такого приложения, и я буду благодарен за любые конструктивные замечания и советы. Возможно, есть более простые и идиоматические подходы, которые вам известны.
В класс-Game я вынес 1. ссылки на синглтоны Майерса draw, input, audio 2. методы инициализации игры и освобождения памяти из-под неё. Game сам является синглтоном. Обратите внимание на идиому, которую я при этом использую: Game - абстрактный класс, все его члены статические. Невозможно создать экземпляр этого класса, но его статические методы можно использовать. В отличие от традиционной идиомы определения синглтона (Банда Четырех, Александреску, Майерс), здесь нет нужды 1. определять метод Instance() 2. описывать конструкторы, оператор присваивания и деструктор как приватные. При этом мы выделяем синглтон на синтаксическом уровне: все обращения к его членам из функции main() осуществляются через "Game :: method()".
Каждый класс-GameMode должен иметь прямой доступ к draw, input, audio, и запись Game::draw нас не устраивает. По этой причине я решил сделать классы-GameMode наследниками класса-Game.
Game::Register() позволяет зарегистрировать новый режим игры. При запуске Game::Start() указывается стартовый режим и включается главный цикл приложения (обработка сообщений Windows). Игра крутится до тех пор, пока не произойдет выход из какого-нибудь режима и метод GameMode::Process() не вернет id несуществующего режима. Это значит, что игра завершается, а не переходит в новый режим.
const int MAX_MODES = 5;
const int UNDEFINED_ID = -1;
class Game
{
static Game*
modes[MAX_MODES];
static int mode_count;
protected:
static Direct3d& draw; // Singleton
//static DirectInput& input;
//static DirectAudio& audio; //
DMusic + DSound
public:
static int Init()
{
cout << "Game: Init() " << endl;
draw = Direct3d :: Instance();
//input
= DirectInput :: Instance();
//audio
= DirectAudio :: Instance();
return
1;
}
static void Release()
{
cout << "Game: Release() " << endl;
}
static void Register( Game* mode )
{
if
(mode_count == MAX_MODES)
return;
modes[ mode_count ] = mode;
mode_count++;
}
static int Start( int mode_id )
{
cout << "Game: Start() " << endl;
//
Main Windows Loop
while
(mode_id != UNDEFINED_ID)
{
// Process returns id of
mode_id = modes[mode_id]->Process();
}
return
1;
}
virtual int Process() = 0;
};
Game* Game :: modes[MAX_MODES];
int Game :: mode_count = 0;
class GameMode1 : public Game
{
public:
int Process()
{
cout << "GameMode1: Process() " <<
endl;
//
InitResources();
//
Logic(); Show();
//
ReleaseResources();
return
1;
}
};
class GameMode2 : public Game
{
public:
int Process()
{
cout << "GameMode2: Process() " <<
endl;
//
InitResources();
//
Logic(); Show();
//
ReleaseResources();
return
2;
}
};
class GameMode3 : public Game
{
public:
int Process()
{
cout << "GameMode3: Process() " <<
endl;
//
InitResources();
//
Logic(); Show();
//
ReleaseResources();
return
-1;
}
};
void main()
{
if ( Game::Init() )
{
GameMode1 gm1;
GameMode2 gm2;
GameMode3 gm3;
Game::Register(&gm1);
Game::Register(&gm2);
Game::Register(&gm3);
Game::Start(0);
}
Game::Release();
}
Результат работы приложения:
Game: Init()
Game: Start()
GameMode1: Process()
GameMode2: Process()
GameMode3: Process()
Game: Release()
Press any key to continue . . .
В класс-Game я вынес 1. ссылки на синглтоны Майерса draw, input, audio 2. методы инициализации игры и освобождения памяти из-под неё. Game сам является синглтоном. Обратите внимание на идиому, которую я при этом использую: Game - абстрактный класс, все его члены статические. Невозможно создать экземпляр этого класса, но его статические методы можно использовать. В отличие от традиционной идиомы определения синглтона (Банда Четырех, Александреску, Майерс), здесь нет нужды 1. определять метод Instance() 2. описывать конструкторы, оператор присваивания и деструктор как приватные. При этом мы выделяем синглтон на синтаксическом уровне: все обращения к его членам из функции main() осуществляются через "Game :: method()".
Каждый класс-GameMode должен иметь прямой доступ к draw, input, audio, и запись Game::draw нас не устраивает. По этой причине я решил сделать классы-GameMode наследниками класса-Game.
Game::Register() позволяет зарегистрировать новый режим игры. При запуске Game::Start() указывается стартовый режим и включается главный цикл приложения (обработка сообщений Windows). Игра крутится до тех пор, пока не произойдет выход из какого-нибудь режима и метод GameMode::Process() не вернет id несуществующего режима. Это значит, что игра завершается, а не переходит в новый режим.
const int MAX_MODES = 5;
const int UNDEFINED_ID = -1;
class Game
{
static Game*
modes[MAX_MODES];
static int mode_count;
protected:
static Direct3d& draw; // Singleton
//static DirectInput& input;
//static DirectAudio& audio; //
DMusic + DSound
public:
static int Init()
{
cout << "Game: Init() " << endl;
draw = Direct3d :: Instance();
//input
= DirectInput :: Instance();
//audio
= DirectAudio :: Instance();
return
1;
}
static void Release()
{
cout << "Game: Release() " << endl;
}
static void Register( Game* mode )
{
if
(mode_count == MAX_MODES)
return;
modes[ mode_count ] = mode;
mode_count++;
}
static int Start( int mode_id )
{
cout << "Game: Start() " << endl;
//
Main Windows Loop
while
(mode_id != UNDEFINED_ID)
{
// Process returns id of
mode_id = modes[mode_id]->Process();
}
return
1;
}
virtual int Process() = 0;
};
Game* Game :: modes[MAX_MODES];
int Game :: mode_count = 0;
class GameMode1 : public Game
{
public:
int Process()
{
cout << "GameMode1: Process() " <<
endl;
//
InitResources();
//
Logic(); Show();
//
ReleaseResources();
return
1;
}
};
class GameMode2 : public Game
{
public:
int Process()
{
cout << "GameMode2: Process() " <<
endl;
//
InitResources();
//
Logic(); Show();
//
ReleaseResources();
return
2;
}
};
class GameMode3 : public Game
{
public:
int Process()
{
cout << "GameMode3: Process() " <<
endl;
//
InitResources();
//
Logic(); Show();
//
ReleaseResources();
return
-1;
}
};
void main()
{
if ( Game::Init() )
{
GameMode1 gm1;
GameMode2 gm2;
GameMode3 gm3;
Game::Register(&gm1);
Game::Register(&gm2);
Game::Register(&gm3);
Game::Start(0);
}
Game::Release();
}
Результат работы приложения:
Game: Init()
Game: Start()
GameMode1: Process()
GameMode2: Process()
GameMode3: Process()
Game: Release()
Press any key to continue . . .
