1. Архитектура на классах: Инкапсуляция игрока, врагов и использование функций
Архитектура на классах: Инкапсуляция игрока, врагов и использование функций
Приветствую, будущий создатель игр! Ты уже освоил фундамент C++: переменные, циклы и ветвления. Это кирпичи, из которых строится программа. Но чтобы построить не просто сарай, а сложный небоскреб (или в твоем случае — клон Cuphead), тебе нужен чертеж и правильная структура. В программировании эту роль играют классы и объектно-ориентированное программирование (ООП).
В этой статье мы перейдем от написания кода «сплошной простыней» в main() к созданию архитектуры. Мы спроектируем сущности Игрока и Врага, научимся прятать их данные (инкапсуляция) и заставим их взаимодействовать через функции-методы.
Почему процедурный код не подходит для Boss Rush?
Представь, что ты пишешь игру, используя только переменные и массивы. Для одного игрока тебе понадобятся:
* int playerX, playerY;
* int playerHP;
* int playerAmmo;
А теперь представь, что у тебя есть босс, который стреляет 50 снарядами. Тебе придется создавать массивы для координат каждого снаряда: int bulletX[50], int bulletY[50], bool bulletActive[50]. Код превратится в хаос, где изменение одной переменной может сломать всю игру. Это называется «спагетти-код».
Решение — Классы. Класс — это чертеж, описывающий сущность. Объект — это конкретный экземпляр, созданный по этому чертежу.
!Различие между классом (чертежом) и объектами (реализациями)
Инкапсуляция: Защита данных
Первый кит ООП, который нам нужен — это инкапсуляция. Это объединение данных и методов работы с ними в одну упаковку (класс) и скрытие деталей реализации от пользователя.
В играх типа Run and Gun здоровье игрока не должно меняться произвольно из любой части программы. Оно должно уменьшаться только когда игрок получает урон.
В C++ для этого используются модификаторы доступа:
* private: данные доступны только внутри самого класса (скрыты от внешнего мира).
* public: данные или методы доступны из любой части программы.
Проектируем класс Player
Давай создадим класс для нашего героя. У него должны быть координаты, здоровье и методы для движения.
Обрати внимание: мы не даем прямого доступа к hp. Мы даем метод takeDamage. Если в будущем мы захотим добавить звук при получении урона или эффект неуязвимости, мы добавим это в один метод, и это заработает везде.
Математика движения
В играх движение — это изменение координат с течением времени. В примере выше мы использовали простую арифметику. Однако для плавного движения в будущем нам понадобится формула изменения позиции.
Формула равномерного прямолинейного движения в дискретном времени (каждый кадр) выглядит так:
Где: * — новая позиция объекта (координата или ). * — текущая позиция объекта. * — скорость движения (пиксели в секунду или единицы в кадр). * — шаг времени (время, прошедшее с прошлого кадра).
В нашем простом консольном примере мы пока пренебрегаем и считаем, что один вызов функции — это один шаг времени, поэтому формула упрощается до .
Проектируем класс Enemy
Враги в Cuphead ведут себя иначе, чем игрок. Они часто движутся по паттернам. Создадим простой класс врага.
Здесь метод update() — это сердце логики врага. В игровом цикле мы будем вызывать update() для каждого врага, и они будут «жить» своей жизнью.
Связываем всё вместе: Игровой цикл
Теперь, когда у нас есть классы, функция main становится чистой и понятной. Она превращается в Игровой Цикл (Game Loop).
Игровой цикл обычно состоит из трех этапов:
!Основные этапы игрового цикла
Вот как это выглядит в коде (псевдо-код для консоли):
Преимущества такого подхода
Player mugman(150, 100);. Тебе не нужно копировать десятки переменных.main читается как сценарий: «Игрок двигается, враг обновляется, проверяем столкновение».Заключение
Мы сделали огромный шаг вперед. Вместо разрозненных переменных мы теперь мыслим объектами. Игрок — это объект, который «знает» свое здоровье и «умеет» бегать. Враг — это объект, который «умеет» патрулировать.
В следующей статье мы углубимся в работу с памятью и массивами объектов, чтобы создать настоящую армию врагов для нашего уровня.