Урок №15. Создание игры «Арканоид» на С++/Qt5

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

  |

  Обновл. 12 Фев 2021  | 

 32858

 ǀ   18 

Разработка компьютерной игры — это отличный способ узнать больше о Qt5. Поэтому сегодня мы попробуем создать свою версию известной аркадной игры под названием «Арканоид» (англ. «Breakout»), разработанной в 1976 году не менее известной компанией Atari Inc. В этой игре игрок перемещает подвижную ракетку только вправо или влево и пытается отбросить летящий к нему мяч в верхнюю часть экрана, где расположена группа кирпичей/блоков. Цель состоит в том, чтобы, отбивая мяч, уничтожить все блоки.

Разработка игры «Арканоид»

Для начала давайте перечислим основные моменты игры. У нас есть одна подвижная Ракетка, один Мяч и тридцать Кирпичей/Блоков. Еще нам потребуется таймер, который будет использоваться для создания игрового цикла. Чтобы сделать проект как можно проще и понятнее, мы не будем рассматривать вопросы реализации начисления игровых очков, бонусов и пр.

Итак, начнем с заголовочного файла для объекта paddle (подвижная Ракетка). INITIAL_X и INITIAL_Y — это константы, которые представляют начальные координаты объекта paddle.

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

Как уже было упомянуто, Ракетка может перемещаться только вправо или влево.

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

В конструкторе класса Paddle мы инициируем переменную dx и загружаем изображение Ракетки. Далее получаем прямоугольник, ограничивающий загруженное изображение Ракетки, и устанавливаем его в исходное положение:

Метод move() перемещает прямоугольник с изображением Ракетки. Направление движения задается переменной dx:

Функция resetState() устанавливает Ракетку в исходное положение:

Теперь рассмотрим заголовочный файл для объекта brick (Кирпич/Блок). Если Кирпич разрушен, то значением переменной destroyed становится true.

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

Класс Brick представляет объект brick.

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

Конструктор класса Brick загружает изображение Кирпича, инициирует переменную-флаг destroyed и устанавливает изображение в исходную позицию:

У объектов класса Brick есть переменная-флаг destroyed. Если её значение установлено как true, то Кирпич считается разрушенным, и не отображается в окне:

Переходим к заголовочному файлу объекта ball (Мяч). В переменных xdir и ydir хранится направление движения Мяча.

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

В начале игры Мяч движется в направлении вправо-вверх:

Метод autoMove() вызывается в каждом игровом цикле для перемещения Мяча по экрану. Если Мяч достигает границ окна (за исключением нижней), то он меняет свое направление. Если же Мяч попадает в нижнюю границу окна, то назад он не отскакивает, а игра при этом считается завершенной:

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

Ракетка управляется с помощью клавиш-стрелок на клавиатуре. В игре мы следим за событиями нажатия клавиш клавиатуры с помощью следующих методов:

В переменной x хранится текущее положение Ракетки по оси X. Переменная timerId используется для идентификации объекта timer. Это необходимо в тех моментах, когда мы приостанавливаем игру:

Константа N_OF_BRICKS задает количество Кирпичей в игре:

Константа DELAY управляет скоростью игры:

Когда Мяч пересекает нижний край окна, то игра заканчивается:

Переменные-указатели на объекты Мяча, Ракетки и массива Кирпичей:

Следующие 4 переменные отвечают за различные состояния игры:

В файле breakout.cpp находится логика нашей игры.

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

В конструкторе класса Breakout мы создаем экземпляр тридцати Кирпичей:

В зависимости от переменных gameOver и gameWon мы либо заканчиваем игру, выдавая соответствующие сообщения, либо продолжаем отрисовывать в окне игровые объекты:

Метод finishGame() отображает завершающее сообщение в центре окна. Это либо "Game lost" ("Игра проиграна"), либо "Victory" ("Победа"). Метод QFontMetrics::width() используется для вычисления ширины строки соответствующего сообщения:

Метод drawObjects() отрисовывает в окне все объекты игры: Мяч, Ракетку и Кирпичи. А так как данные объекты представлены изображениями, то при помощи метода drawImage() мы отображаем и их изображения:

В теле метода timerEvent() мы сначала перемещаем объекты, а затем проверяем, не столкнулся ли Мяч с Ракеткой или Кирпичом. В конце генерируем событие отрисовки:

Метод moveObjects() отвечает за перемещения объектов Мяч и Ракетка. В нем вызываются их собственные методы перемещения:

Когда игрок отпускает кнопку или , то мы присваиваем переменной dx Ракетки значение 0. В результате Ракетка перестает двигаться:

В методе keyPressEvent() мы отслеживаем события нажатия клавиш, относящиеся к нашей игре. Кнопки и перемещают объект Ракетки. Они влияют на значение переменной dx, которое затем будет добавлено к координате х самой Ракетки. Кнопка P ставит игру на паузу, кнопка "Пробел" запускает игру, а кнопка Esc завершает работу приложения:

Метод startGame() сбрасывает состояния объектов ball и paddle; они перемещаются в исходное положение. В цикле for мы устанавливаем значение флага destroyed равным false для каждого Кирпича, таким образом отображая их в окне. Переменные gameOver, gameWon и gameStarted получают свои начальные логические значения. Наконец, с помощью метода startTimer() мы запускаем таймер:

Функция pauseGame() используется для приостановки и запуска уже остановленной игры. Данное состояние игры управляется с помощью переменной paused. Также мы храним и идентификатор таймера. Чтобы приостановить игру, мы «убиваем» таймер с помощью метода killTimer(). Чтобы перезапустить его, мы вызываем метод startTimer():

В методе stopGame() мы «убиваем» таймер и устанавливаем соответствующие флаги:

В методе checkCollision() мы делаем проверку столкновения Мяча с игровыми объектами. Игра заканчивается, если Мяч попадает в нижний край окна:

Проверяем количество разрушенных Кирпичей. Если все Кирпичи уничтожены, то мы выиграли:

Если Мяч попадает в верхнюю часть Ракетки, то меняем направление полета Мяча (в зависимости от движения Ракетки в момент столкновения), например, на влево-вверх:

Если Мяч ударяется о нижнюю часть Кирпича, то мы меняем направление y Мяча — он идет вниз.

Основной файл программы — main.cpp:

Результатом является игра «Арканоид»:


  GitHub / Урок №15. Создание игры «Арканоид» на С++/Qt5 — Исходный код

Заключение


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

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

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

Комментариев: 18

  1. Finchi:

    Моя версия на Qt6 в среде MS VS2022 Enterprise Edition:

    Я не стал разделять мячик, ракетку и кирпич на отдельные классы, а сделал их одним типом данных — Item.

    Item.h

    Item.cpp — отсутствует, так как класс Item слишком простой.

    Breakout.h

    1. Finchi:

      Breakout.cpp

      main.cpp

       

  2. Vladislavskiy:

    Где ball.cpp ? ))

    1. Фото аватара Юрий:

      В ГитХаб-репозитории.

  3. Alexandr:

    Подскажите пожалуйста, как скомпилировать всё статически или динамически, но чтобы готовый файл или проект можно было запустить на другой машине?

    1. Фото аватара Дмитрий Бушуев:

      Вам нужна программа windeployqt.exe из поставки Qt. Обычно она располагается в папке с компилятором. К примеру, у меня это папки "C:\Soft\Qt\5.15.0\mingw81_64\bin" или "C:\Soft\Qt\5.15.0\msvc2019_64\bin". Находите её, далее запускаете командную строку -> прописываете пусть к этой программе -> пробел -> путь к исполняемому файлу вашей программы -> enter.
      В результате этого, windeployqt.exe добавит все необходимые *.dll в папку с вашей программой.

      1. Alexandr:

        Спасибо, попробую!

  4. Спасенный студент:

    Спасибо Вам огромное!Вы святой

    1. Фото аватара Дмитрий Бушуев:

      Аминь 🙂

  5. Andrew:

    Добрый день. Огромная благодарность автору, за сей сайт!
    Хотелось бы продолжение но уже по QML.

  6. Obe1:

    Отличные уроки, только, кажется, в этой статье не хватает листинга ball.cpp

    1. Николай:

      Да(Там ниже реализована только одна функция.Без этого работает?

  7. Михаил Харитонов:

    А будут ли уроки QT по подключению базы данных к форме и их взаимодействию ?

  8. Мгер:

    И еще почему код не в гит-репозитории? можно было бы изменять, дополнять и тд)

    1. Фото аватара Дмитрий Бушуев:

      Тогда и статью нужно будет переместить в гит-репозиторий 🙂

  9. Мгер:

    Спасибо автору, очень интересно читать.
    Хочется продолжения: модель-представлений, таблиц, QML, работы с потоками, может мобильная разработка?

    1. Фото аватара Дмитрий Бушуев:

      Насчет продолжения — продолжение будет.
      P.S.: Всегда пожалуйста. 🙂

Добавить комментарий для Дмитрий Бушуев Отменить ответ

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