Помогите с архитектурой игры
Помогите грамотно продумать архитектуру игры на WINAPI
Здравствуйте, только вникаю в ООП и никак не могу отойти от функционального программирования. Хочу написать игрушку (консольную) , что-то типа террарии курильщика. Цель скорее написать не супер мега игру с крутой графикой, а лучше разобраться в ООП, windows.h и немножно изучить клеточные автоматы, с помощью которых планирую делать генерацию мира. Поэтому игра консольная, а не написанная с использование SFML\SDL и пр, до них, к сожалению, еще не дошел. В общем, суть такая:
В кратце про игру: Запускаем, на экране появляется меню:
- играть
- редактор
- выход.
Взаимодействие с этим меню происходит мышкой.
Играть: программа предлагает загрузить созданный мир или сгенерировать новый(мир статичный, ограниченный размерами экрана). Мир состоит из 4 блоков: камень, песок, вода, пустота. По этому миру можно походить человечком, разрушать с помощью мышки блоки. Возможно будет еще функционал, но пока что хорошо вклинивающихся идей нет.
Редактор: программа перекидывает пользователя на полотно, где он может нарисовать мир. При заходе в редактор программа предлагает или рисовать на чистом полотне, или загрузить прошлые сохраненные миры. Интерфейс: В правой части экрана будет возможность выбрать блок и размер кисти. Т.е. тыкаешь на слово ширина, открывается менюшка, тыкаешь снова она закрывается. По нажатию на выход программа предлагает сохранить мир в свободную ячейку или просто выйти (мир загружается в бинарный файл).
Собственно, примерно архитекутуру я предсставляю так:
Есть класс-интерфейс для экрана:
class ScreenInterface
{
protected:
short width; //ширина экрана
short height; //высота экрана
char pathToScreenImage[20]; //строка, хранящая путь до файла, в котором хранится начальное изображение экрана
CHAR_INFO* ScreenBuffer; //буффер в виде массива типа CHAR_INFO. Тип хранит 2 значения - атрибут и символьное значение элемента
HHOOK hKeyboardHook; //хук привязанный к клавиатуре
HHOOK hMouseHook; //хук привязанный к мышке
public:
virtual LRESULT CALLBACK KeyboardEvent(int, WPARAM, LPARAM) = 0; //функции обработчики сообщений от хуков
virtual LRESULT CALLBACK MouseEvent(int, WPARAM, LPARAM) = 0; //виртуальные, т.к. у каждого экрана свои "паттерны" и реакции
void SetHook(); //привязываем хук к текущему экрану, функция обработчик хука указываем KeyboardEvent() и MouseEvent()
void DelHook(); //отвязывваем
void LoadScreenFromFile() = 0; //загрузка первоначального состояния экрана
void ShowScreen(); //не виртуальная, т.к. просто выводит на экран измененный буфер. Все изменения происходит в функция обработчиках
};
Есть его потомки экран для редактора:
class Canvas : public ScreenInterface
{
public:
bool isBlockMenuOpen; //хранят в себе текущее состояние меню экрана
bool isWidthMenuOpen; //открыты ли меню для выбора блока и ширины кисти
public:
Canvas();
~Canvas();
void SaveLevelToFile(); //сохранить уровень. Метод выводит на экран мини окно(точнее его эмуляцию), где можно посмотреть
//какие ячейки свободны для записи, какие заняты и собственно сохранить уровень или выйти без сохранений
void LoadLevelFromFile(); //загрузить уровень. Метод вызывается первым после перехода из меню в редактор. Т.е.
//на буфер экрана рисуется мини окошко, где пользователь выбирает загрузить или создать новый мир
void ShowBrushMenu(); //Показывает или закрывает меню для установки блока\ширины кисти
void SetBrushParameters() //устанавливает параметры для кисти, которой рисуем(блок-ширина)
//Решил не выделять кисть как отдельный класс т.к. по сути больше с ней мы никак взаимодействовать не можем
LRESULT CALLBACK KeyboardEvent(int, WPARAM, LPARAM); //функции обработчики для хуков, все изменения буфера происходят здесь
LRESULT CALLBACK MouseEvent(int, WPARAM, LPARAM);
};
Экран для игры:
class Game : public ScreenInterface
{
public:
Game(); //здесь происходит генерация мира
~Game();
void LoadLevelFromFile(); //аналогично, метод выводит окошко и пользователь выбирает что он хочет сделать
void WorkWithWater(); //метод, который будет отвечать за перемещение воды на экране, как реализовать пока не придумал
void WorkWithSand(); //метод, отвечающий за физику песка, тоже пока что написана просто для плана
static LRESULT CALLBACK KeyboardEvent(int, WPARAM, LPARAM);
static LRESULT CALLBACK MouseEvent(int, WPARAM, LPARAM);
};
Для каждого экрана будет свой хук на клавиатуру и мышь. В функциях KeyboardEvent и MouseEvent будет обработка сообщений от пользователя для конкретного экрана, все изменения буфера будут происходить в этом обработчике.
т.е. по сути main будет выглядеть как-то так:
int WinMain()
{
MenuScreen menu;
CanvasScreen canvas;
GameScreen game;
MSG msg; //структура для принятия сообщений
int mode = 0 // 0-меню, 1-игра, 2-редактор, 3-выход
while(true)
{
if(mode == 0)
{
menu.SetHook();
//пока получаем сообщения от пользователя и не находим инструкцию к переходу, принимаем их
while(GetMessage(&msg))
{
//отправляем в функции обработчики этих сообщение
TranslateMessage(&msg);
DispatchMessage(&msg);
if(/*если получили сообщение о переходе*/)
{
mode = (то, что выбрали(1, 2 или 3))
menu.DelHook();//получили сообщение о выходе,
break;
}
}
}
//запускаем игру, если mode == 1
if(mode == 1)
{
game.LoadLevelFromFile(); //загружаем уровень
game.SetHook(); //устанавливаем хук
//пока получаем сообщения от пользователя и не находим инструкцию к переходу, принимаем их
while(GetMessage(&msg))
{
//отправляем в функции обработчики этих сообщение
TranslateMessage(&msg);
DispatchMessage(&msg);
if(/*если получили сообщение о выходе*/)
{
mode = 0;
game.DelHook(); //удаляем хук
break;
}
}
}
//запускаем редактор, если mode == 2
if(mode == 2)
{
canvas.LoadLevelFromFile(); //выводим окошко с вариантами загрузки уровня в редактор
canvas.SetHook(); //устанавливаем хук
//пока получаем сообщения от пользователя и не находим инструкцию к переходу, принимаем их
while(GetMessage(&msg))
{
//отправляем в функции обработчики этих сообщение
TranslateMessage(&msg);
DispatchMessage(&msg);
if(/*если получили сообщение о переходе*/)
{
mode = 0;
canvas.SaveLevelToFile(); //сохраняем уровень(или нет)
canvas.DelHook(); //получили сообщение о выходе,
break;
}
}
}
if(mode == 3)
{
//выход из программы
}
}
return 0;
}
В общем, примерный план программы вот такой... Теперь вопроc:
- насколько все плохо? Какие классы добавить, где логика корявая? Что добавить\убрать пока не перешел от плана к реализации?
- Трудно ли потом будет переписать проект от статического мира, с размерами экрана, до динамического, т.е. больше чем размер экрана? Примерный план перехода у меня есть, но решил вначале сделать для статического мира, а потом переделать под динамический. Не ошибочка ли это?
Тяжко дается переход от функционального к ООП, плюсы изучаю месяц от силы... буду признателен любому замечанию =).