Часть №4: Создание уровней в игре «Breakout» на C++/OpenGL

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

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

 1356

В нашей версии игры «Breakout» должны присутствовать полноценные уровни с большим количеством кирпичиков. При этом мы хотим, чтобы эти уровни были сконфигурированы таким образом, чтобы:

   могли поддерживать любое количество строк и/или столбцов;

   имели твердые (неразрушаемые) кирпичи;

   поддерживали несколько вариантов раскраски кирпичей;

   информация о структуре уровня хранилась во внешних (текстовых) файлах.

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

Создание кирпичей

Для этого создадим компонент под названием игровой объект (англ. «game object»), который будет играть роль базового представления объектов внутри игры. Представленный компонент хранит в себе данные о состоянии описываемых им игровых объектах, например: координаты положения, размер, скорость, цвет, вращение, способность к разрушению (да/нет), а также изображение спрайта в виде переменной типа Texture2D.

Каждый объект игры будет представлен классом GameObject или производным от него классом.

#Класс GameObject

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

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

Уровень в игре «Breakout» состоит исключительно из кирпичей, поэтому можно его прям так и представлять: набором кирпичей. Поскольку для описания кирпича в виде игрового объекта требуются параметры состояния похожие на те, которые мы перечислили выше, то будем описывать каждый кирпич уровня в виде объекта класса GameObject.

Объявление класса GameLevel выглядит следующим образом:

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

1 1 1 1 1 1
2 2 0 0 2 2
3 3 4 4 3 3

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

   Число 0: Нет кирпича, пустое пространство внутри уровня.

   Число 1: Твердый кирпич, т.е. кирпич, который не может быть разрушен.

   Число > 1: Разрушаемый кирпич; каждое последующее число отличается только цветом.

Если бы мы обработали вышеприведенный уровень классом GameLevel, то результат выглядел бы следующим образом:

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

Переменная загруженных данных tileData передается в функцию init() для инициализации игрового уровня:

Функция init() перебирает каждое из загруженных чисел и добавляет столько объектов GameObject к вектору уровня GameLevel::Bricks, сколько чисел было обработано. Размеры каждого кирпича вычисляются автоматически (переменные unit_width и unit_height) в зависимости от общего количества кирпичей, в результате все они будут идеально вписаны в границы экрана.

Ниже представлены текстуры игровых объектов двух типов: разрушаемого блока и неразрушаемого (твердого) блока:

Правда тут есть один маленький трюк, а именно: эти текстуры выполнены полностью в градациях серого. Благодаря этому, в игровом коде мы можем аккуратно манипулировать их цветами, перемножая цвет текстуры с определенным цветовым вектором; точно так же, как мы это делали внутри SpriteRenderer.

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

#Класс GameLevel

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

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

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

Внутри игры


Будет здорово, если мы реализуем в игре поддержку нескольких уровней, поэтому давайте немного расширим класс игры, добавив вектор, содержащий переменные типа GameLevel. А также еще одну переменную, в которой будет храниться номер текущего активного уровня:

Наша версия игры «Breakout» будет включать в себя в общей сложности 4 уровня:

Уровень №1: Standard (файл — one.lvl):

5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
4 4 4 4 4 0 0 0 0 0 4 4 4 4 4
4 1 4 1 4 0 0 1 0 0 4 1 4 1 4
3 3 3 3 3 0 0 0 0 0 3 3 3 3 3
3 3 1 3 3 3 3 3 3 3 3 3 1 3 3
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2

Уровень №2: A few small gaps (файл — two.lvl):

1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 0 5 5 0 5 5 0 5 5 0 5 5 0 1
1 5 5 5 5 5 5 5 5 5 5 5 5 5 1
1 0 3 3 0 3 3 0 3 3 0 3 3 0 1
1 3 3 3 3 3 3 3 3 3 3 3 3 3 1
1 0 2 2 0 2 2 0 2 2 0 2 2 0 1
1 2 2 2 2 2 2 2 2 2 2 2 2 2 1
1 0 1 1 0 1 1 0 1 1 0 1 1 0 1

Уровень №3: Space invader (файл — three.lvl):

0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 2 0 0 0 0 0 0 0 2 0 0
0 0 0 2 0 0 0 0 0 2 0 0 0
0 0 0 5 5 5 5 5 5 5 0 0 0
0 0 5 5 0 5 5 5 0 5 5 0 0
0 5 5 5 5 5 5 5 5 5 5 5 0
0 3 0 1 1 1 1 1 1 1 0 3 0
0 3 0 3 0 0 0 0 0 3 0 3 0
0 0 0 0 4 4 0 4 4 0 0 0 0

Уровень №4: Bounce galore (файл — four.lvl):

1 2 1 2 1 2 1 2 1 2 1 2 1
2 2 2 2 2 2 2 2 2 2 2 2 2
2 1 3 1 4 1 5 1 4 1 3 1 2
2 3 3 4 4 5 5 5 4 4 3 3 2
2 1 3 1 4 1 5 1 4 1 3 1 2
2 2 3 3 4 4 5 4 4 3 3 2 2

Затем все текстуры и уровни игры инициализируются с помощью функции Game::Init():

Теперь всё, что осталось сделать, — это непосредственно визуализация уровня. Для этого у текущего активного уровня вызывается функция Draw(), которая, в свою очередь, вызывает функцию GameObject::Draw() у каждого объекта:

Следующим шагом мы визуализируем сцену с вот таким милым фоновым изображением:

В результате получается красиво прорисованный уровень:


Ракетка для игрока

Таким же образом в нижней части сцены мы можем ввести и ракетку, которая будет контролироваться игроком. Ракетка допускает только горизонтальные перемещения, и всякий раз, когда она касается любого из краев сцены, её движение прекращается. А в качестве текстуры мы используем следующее изображение:

Объект ракетки должен содержать информацию о своем положении, размере и текстуре спрайта, поэтому имеет смысл определить ракетку через класс GameObject:

В вышеприведенном фрагменте кода мы задали несколько констант, определяющих размер и скорость перемещения ракетки. В рамках функции инициализации игры Game::Init() мы вычисляем начальное положение ракетки внутри сцены. А также убеждаемся в том, чтобы её центр был выровнен с горизонтальным центром сцены.

Кроме того, необходимо добавить следующую строку кода в конец функции Game::Render():

Если бы вы прямо сейчас запустили игру, то увидели бы не только сам уровень, но и забавную ракетку, выровненную по нижнему краю сцены. На данный момент она ничего не делает, поэтому мы собираемся углубиться в функцию Game::ProcessInput(), чтобы реализовать возможность горизонтального перемещения ракетки всякий раз, когда пользователь нажимает клавишу A или D:

Как вы можете видеть, мы перемещаем ракетку либо влево, либо вправо, в зависимости от того, какую клавишу нажал пользователь (обратите внимание, мы умножаем скорость на переменную разницы времени dt). Если бы значение x-координаты ракетки было бы меньше 0, то она переместилась бы за пределы левого края окна, поэтому мы перемещаем её влево только в том случае, если значение её x-координаты больше значения х-координаты левого края окна (0.0). Проделываем то же самое для случая, когда ракетка достигает правого края окна, при этом необходимо сравнивать положение правого края окна с правым краем ракетки (т.е. вычесть ширину ракетки из x-координаты правого края окна).

Если сейчас запустить игру, то мы получим ракетку, которую можно горизонтально перемещать по всему нижнему краю окна:

#Класс Game

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

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

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

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

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

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

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