Помогите с архитектурой игры

Рейтинг: 0Ответов: 1Опубликовано: 03.04.2023

Помогите грамотно продумать архитектуру игры на WINAPI

Здравствуйте, только вникаю в ООП и никак не могу отойти от функционального программирования. Хочу написать игрушку (консольную) , что-то типа террарии курильщика. Цель скорее написать не супер мега игру с крутой графикой, а лучше разобраться в ООП, windows.h и немножно изучить клеточные автоматы, с помощью которых планирую делать генерацию мира. Поэтому игра консольная, а не написанная с использование SFML\SDL и пр, до них, к сожалению, еще не дошел. В общем, суть такая:

В кратце про игру: Запускаем, на экране появляется меню:

  1. играть
  2. редактор
  3. выход.

Взаимодействие с этим меню происходит мышкой.

  1. Играть: программа предлагает загрузить созданный мир или сгенерировать новый(мир статичный, ограниченный размерами экрана). Мир состоит из 4 блоков: камень, песок, вода, пустота. По этому миру можно походить человечком, разрушать с помощью мышки блоки. Возможно будет еще функционал, но пока что хорошо вклинивающихся идей нет.

  2. Редактор: программа перекидывает пользователя на полотно, где он может нарисовать мир. При заходе в редактор программа предлагает или рисовать на чистом полотне, или загрузить прошлые сохраненные миры. Интерфейс: В правой части экрана будет возможность выбрать блок и размер кисти. Т.е. тыкаешь на слово ширина, открывается менюшка, тыкаешь снова она закрывается. По нажатию на выход программа предлагает сохранить мир в свободную ячейку или просто выйти (мир загружается в бинарный файл).

Собственно, примерно архитекутуру я предсставляю так:

Есть класс-интерфейс для экрана:

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:

  1. насколько все плохо? Какие классы добавить, где логика корявая? Что добавить\убрать пока не перешел от плана к реализации?
  2. Трудно ли потом будет переписать проект от статического мира, с размерами экрана, до динамического, т.е. больше чем размер экрана? Примерный план перехода у меня есть, но решил вначале сделать для статического мира, а потом переделать под динамический. Не ошибочка ли это?

Тяжко дается переход от функционального к ООП, плюсы изучаю месяц от силы... буду признателен любому замечанию =).

Ответы

▲ 0Принят

Начни с крестиков ноликов хотя бы, твой вопрос здесь просто проигнорят, потому что ты говоришь - это нечетко, неясно, в некоторых местах очевидно, что у тебя хоть и есть макет, но он ни на чем не стоит - это просто догадки.

Он в целом правильный, но ты должен решить это с помощью инструментов winapi, поэтому начни с маленького проекта, сделай его или лучше несколько, потом задавай более конкретные вопросы.

Архитектура - это очень широкое понятие, но то что написал ты - это просто обычные человеческие мысли, но не архитектура.

Месяца 3 поделай проекты поменьше и задавай такие вопросы.

Вот тебе роадмап:

  1. Крестики нолики. (поработаешь с нажатиями мыши и распараллеливанием процессов для двух игроков)
  2. Телефонная книга. (освоишь основы работы с текстовыми документами и будешь пользоваться классом string, как своей рукой)
  3. Тетрис. (научишься делать графику и с привязкой ко времени скидывать фигуры + генерировать их различной формы, прописывать сложную логику)