Часть №2: Подготовка к созданию игры «Breakout» на С++/OpenGL

  Дмитрий Бушуев  | 

  Обновл. 20 Дек 2020  | 

 1894

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

Каркас игры

Вначале, для нашей игры «Breakout» определим класс Game, который будет содержать весь соответствующий код рендеринга и геймплея. Идея создания подобного игрового класса заключается в том, что он организует наш игровой код, а также отделит код создания окна и вывода графики от кода с игровой логикой. Таким образом, мы без особых проблем сможем использовать тот же самый класс в совершенно другой оконной библиотеке (например, SDL или SFML).

Примечание: Существуют тысячи различных способов абстрагирования и обобщения игрового/графического кода на классы и объекты. То, с чем вы встретитесь на следующих уроках, — это всего лишь один (относительно простой) подход к решению данного вопроса. Но если вы чувствуете в себе силы и уверены, что можете сделать лучше, то ничего не мешает вам попробовать придумать свою собственную реализацию.

Класс Game содержит функцию инициализации, функцию обновления, функцию обработки игрового ввода с клавиатуры и функцию рендеринга:

Мы инициализируем игру параметрами разрешения окна (ширина и высота), с которым мы собираемся играть и используем функцию Init() для загрузки шейдеров, текстур и инициализации начального состояния всего игрового процесса. Обрабатывать коды нажимаемых клавиш клавиатуры, сохраняемых в массиве Keys, мы будем при помощи функции ProcessInput(), и обновлять все события игрового процесса (например, перемещения игрока/мяча) в функции Update(). Наконец, визуализация игры будет происходить с помощью функции Render(). Обратите внимание, что мы отделяем логику перемещения объектов от логики рендеринга.

Класс Game содержит переменную State типа GameState. Определение данного типа приведено ниже:

Благодаря вышеописанной переменной, мы можем отслеживать, в каком состоянии находится игра в данный момент. Таким образом, у нас появляется возможность изменить параметры рендеринга и/или обработки в зависимости от её текущего состояния.

На данный момент функции игрового класса являются пустыми, нам еще предстоит написать непосредственный код игры, но ниже представлены уже готовые заголовочный файл и файл реализации игрового класса.

#Класс Game

Заголовочный файл — game.h:

Файл реализации — game.cpp:

Служебные функции


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

Поэтому давайте определим шейдерный класс, который генерирует скомпилированный шейдер (или выдаст сообщения об ошибках, если компиляция потерпит неудачу) на основе двух или трех (если присутствует геометрический шейдер) передаваемых ему строк. Шейдерный класс будет иметь множество служебных функций для быстрого задания значений uniform-переменных. Мы также определим текстурный класс, задача которого состоит в генерировании 2D-текстурного изображения (в зависимости от определенных свойств) на основе массива байтов данных и заданных параметров ширины/высоты изображения. Опять же, в текстурном классе будут представлены некоторые служебные функции.

Мы не будем углубляться в детали классов, так как вы уже и сами способны легко понять суть и принципы их работы. Ниже приведены заголовочный файл и файл реализации класса Shader.

#Класс Shader

Заголовочный файл — shader.h:

Файл реализации — shader.cpp:

#Класс Texture2D

Заголовочный файл — texture.h:

Файл реализации — texture.cpp:

Обратите внимание, что текущий класс текстур предназначен исключительно для 2D-текстур, но его можно легко расширить для реализации поддержки и других типов текстур.

Управление ресурсами

Хотя шейдерный и текстурный классы прекрасно функционируют сами по себе, для их инициализации требуется либо массив байтов, либо список строк. Мы могли бы легко встроить код загрузки файлов в сами классы, но это немного нарушает принцип единственной ответственности. Предпочтительнее, чтобы эти классы фокусировались только непосредственно на своих объектах, т.е. на текстурах или шейдерах (соответственно), а не на механизме загрузки их данных из файлов.

По этой причине часто рассматривается более организованный подход к созданию единого объекта (или сущности), предназначенного для загрузки всех связанных с игрой ресурсов, называемый менеджером ресурсов. Существует несколько подходов к созданию менеджера ресурсов; на этом уроке мы будем использовать одноэлементный статический менеджер ресурсов, который (в силу своей статической природы) всегда доступен на протяжении всего проекта, содержащий в себе все загруженные ресурсы и соответствующие функции для их загрузки.

Использование одноэлементного класса со статической функциональностью имеет ряд преимуществ и недостатков, причем к основным его недостаткам относятся потеря некоторых свойств ООП и меньший контроль над построением/разрушением объектов. Однако относительно небольших проектов, подобных нашему, с ним очень легко работать.

#Класс ResourceManager

Заголовочный файл — resource_manager.h:

Файл реализации — resource_manager.cpp:

Используя менеджер ресурсов, мы можем легко загружать в программу шейдеры:

Описанный класс Game вместе с менеджером ресурсов и с легко управляемыми классами Shader и Texture2D образуют основу для следующих уроков, поскольку мы будем широко использовать данные классы для реализации игры «Breakout».

Программа


Теперь для игры необходимо создать окно и задействовать смешивание. Мы не включаем тестирование глубины, так как игра выполнена полностью в 2D. Все вершины определены с одинаковыми z-значениями, поэтому включение режима тестирования глубины было бы бесполезным и, скорее всего, вызвало бы z-конфликт.

Стартовый код игры «Breakout» относительно прост: мы создаем окно с GLFW, регистрируем несколько callback-функций, создаем игровой объект и распространяем всю соответствующую функциональность на игровой класс.

Главный файл программы — program.cpp:

Запуск нашей программы должен привести к следующему результату:

  GitHub / Часть №2: Подготовка к созданию игры «Breakout» на С++/OpenGL — Исходный код

Теперь у нас есть прочная основа для следующих уроков; мы будем постоянно расширять игровой класс, добавляя в него необходимый функционал.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (5 оценок, среднее: 5,00 из 5)
Загрузка...

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *