Найти в Дзене
Универ на диване

Пишем простую игру на С++

Здравствуйте, Дорогие друзья! Сегодня мы немного отвлечемся от создания приложения для автосервиса на WinApi и напишем настоящую мини-игру для ПК. Скажу сразу, пояснения к коду я буду давать только там, где это необходимо, то есть только в тех ситуациях, которые в прошлых статьях мы с Вами не рассматривали. Что же за игру мы будем писать? Самым простым вариантом было бы начать с игрушки из древних аркадных автоматов, в которой нужно управлять космическим кораблем и сбивать корабли пришельцев. Будет ли практическая польза от этой статьи? Будет! Ведь она задумывается не просто так: сегодня мы познакомимся с таймерами (сами понимаете, в приложении для автосервиса таймеры ни к чему, поэтому нужно рассмотреть принцип их реализации на чем-то другом). Итак, первым делом определимся с фоном приложения. Предлагаю сделать его следующим: черный фон, по бокам звезды. Теперь продумаем логику: пусть в игре будут корабли противника, метеориты и ящики со снарядами и запчастями. Корабли мы должны сбива

Здравствуйте, Дорогие друзья! Сегодня мы немного отвлечемся от создания приложения для автосервиса на WinApi и напишем настоящую мини-игру для ПК. Скажу сразу, пояснения к коду я буду давать только там, где это необходимо, то есть только в тех ситуациях, которые в прошлых статьях мы с Вами не рассматривали. Что же за игру мы будем писать? Самым простым вариантом было бы начать с игрушки из древних аркадных автоматов, в которой нужно управлять космическим кораблем и сбивать корабли пришельцев. Будет ли практическая польза от этой статьи? Будет! Ведь она задумывается не просто так: сегодня мы познакомимся с таймерами (сами понимаете, в приложении для автосервиса таймеры ни к чему, поэтому нужно рассмотреть принцип их реализации на чем-то другом). Итак, первым делом определимся с фоном приложения. Предлагаю сделать его следующим: черный фон, по бокам звезды.

Рисунок 1 – Фон для нашей игры (850 х 500 пиксел)
Рисунок 1 – Фон для нашей игры (850 х 500 пиксел)

Теперь продумаем логику: пусть в игре будут корабли противника, метеориты и ящики со снарядами и запчастями. Корабли мы должны сбивать, от метеоритов уворачиваться, ящики – ловить. Если столкнулся с кораблем противника или метеоритом – GAME OVER! Как мы будем реализовывать всё это? Можно конечно прорисовать курсоры в виде кораблей и метеоритов, но мы обойдемся обыкновенными статиками. Тем более фон у нашей игры черный и однотонный, а это значит и мудрить особо не придется. Нарисуем картинки, наклеим эти картинки на статики и будем двигать их постепенно по полю игры. В момент уничтожения вражеского корабля будем просто подменять картинку на соответствующем статике. Итак, рисуем корабль игрока. За размер клетки примем область 66 х 66 пиксел. «Рисуем», конечно, громко сказано: идем в Интернет и по запросу «Пиксельная графика для игр космос» находим подходящую картинку. Обрабатываем ее и ву-а-ля:

Рисунок 2 – Истребитель игрока
Рисунок 2 – Истребитель игрока

Теперь корабли противника. Найдем что-нибудь интересное. Например, вот, неплохой вариант:

Рисунок 3 – Корабль противника
Рисунок 3 – Корабль противника

Осталось сделать метеорит, ящик с патронами и ящик с запчастями:

Рисунок 4 – Метеор
Рисунок 4 – Метеор
Рисунок 5 – Ящик с запчастями (ремонт)
Рисунок 5 – Ящик с запчастями (ремонт)
Рисунок 6 – Ящик с боеприпасами
Рисунок 6 – Ящик с боеприпасами

Также нам понадобится картинка уничтожения корабля противника и картинка выстрела:

Рисунок 7 – Выстрел (пришлось скринить, так как картинка всего 7х7 пиксел)
Рисунок 7 – Выстрел (пришлось скринить, так как картинка всего 7х7 пиксел)
Рисунок 8 – Цель уничтожена
Рисунок 8 – Цель уничтожена

Что ж, кажется, все заготовки сделали. Теперь можно браться и за реализацию игры. Заходим в Dev и создаем классическое приложение. Я назову его SpaceFighter. Как всегда, на старте имеем пустую заготовку:

Рисунок 9 – Заготовка приложения
Рисунок 9 – Заготовка приложения

Делаем, что уже умеем: устанавливаем необходимый размер окна, меняем заголовок, загружаем фоновой рисунок. Эти действия я подробно описывать не буду, так как всё это делалось в статьях по созданию приложения для автосервиса.

Рисунок 10 – Добавляем фон, масштабируем окно, меняем название
Рисунок 10 – Добавляем фон, масштабируем окно, меняем название

Теперь добавим наш истребитель. Снизу оставим место под полосу здоровья. Думаю 50 пиксел вполне хватит. Ширина окна приложения 870 пиксел. Высоту сделаем побольше: 640. При этом нужно учитывать и высоту рамки. Размеры картинки с истребителем 66х66 пиксел. Значит координаты статика под него: х=870/2-33=402, у=640-66-50-60=476. Картинку на статик мы еще не загружали. Чтобы это сделать нужно воспользоваться функцией LoadBitmap() для создания объекта типа картинка в программе, а затем отправить сообщение статику с кодовым словом STM_SETIMAGE. В файле resources.h подгружаем картинку с истребителем:

Рисунок 11 – Содержимое файла resources.h
Рисунок 11 – Содержимое файла resources.h

Расписывать синтаксис функции LoadBitmap() я не буду, так как мы уже рассматривали ее в первой статье цикла по WinApi. Теперь перейдем в main.cpp и сразу после создания статика под истребитель, отправим ему сообщение с помощью функции SendMessage():

Рисунок 12 – Добавляем картинку на статик
Рисунок 12 – Добавляем картинку на статик

Компилируем, смотрим, что у нас получилось:

Рисунок 13 – Мы добавили в игру истребитель
Рисунок 13 – Мы добавили в игру истребитель

Отлично! Картинка есть, но толку от нее, если мы не можем ее двигать с помощью клавиатуры. Обрабатывать сигналы от клавиатуры мы с Вами уже умеем. Не пугайтесь: мы не будем использовать для этого хуки, ведь у нас всего один объект, которым мы должны управлять с клавиатуры, да и фокус ввода он у главного окна забирать не будет. Договоримся, что наш истребитель сможет летать только влево-вправо. Вперед-назад мы его двигать не будем. Вы уже знаете (если читали предыдущие статьи цикла), что когда мы нажимаем какую-либо клавишу на клавиатуре, система отправляет процедуре главного окна сообщение с кодом нажатой кнопки. Код сообщения для нажатой клавиши «влево» - VK_LEFT, для кнопки «вправо» - VK_RIGHT. Двигать статик мы будем уже знакомой нам функцией SetWindowPos():

Рисунок 14 – Двигаем статик с помощью кнопок влево-вправо
Рисунок 14 – Двигаем статик с помощью кнопок влево-вправо

Компилируем, запускаем. Сначала сдвинем статик с истребителем влево, затем вправо, потом еще раз повторим.

Рисунок 15 – Статик двигается
Рисунок 15 – Статик двигается

Мы не только кликали кнопки «влево» и «вправо», но и подолгу удерживали их. В обоих случаях программа работала без ошибок.

Теперь нам нужно ограничить движение статика внутри главного окна. Делаем это с помощью простых условий:

Рисунок 16 – Добавляем условия, при которых будут работать клавиши «влево» и «вправо»
Рисунок 16 – Добавляем условия, при которых будут работать клавиши «влево» и «вправо»
Рисунок 17 – Теперь движение истребителя ограничено
Рисунок 17 – Теперь движение истребителя ограничено

Двигаться наш космолет мы научили, теперь нужно научить его стрелять. Это значит, что пришло время создать наш первый таймер. Таймер – это счетчик, который задействует определенную аппаратную часть процессора для отсчета времени. Таймер должен быть независимым, то есть он не должен реагировать на наше взаимодействие с программой.

Сначала напишем код, который будет обрабатывать только одно нажатие на пробел.

Рисунок 18 – Обрабатываем нажатие на пробел
Рисунок 18 – Обрабатываем нажатие на пробел

Для создания таймера мы используем функцию SetTimer(). Найдем ее в справочнике:

Рисунок 19 – Смотрим в справочнике информацию о функции SetTimer() (Источник: https://learn.microsoft.com/ru-ru/windows/win32/api/winuser/nf-winuser-settimer, Дата обращения: 07.11.2025г.)
Рисунок 19 – Смотрим в справочнике информацию о функции SetTimer() (Источник: https://learn.microsoft.com/ru-ru/windows/win32/api/winuser/nf-winuser-settimer, Дата обращения: 07.11.2025г.)

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

Второй параметр, nIDEvent – Идентификатор таймера. Идентифицируем его в файле resources.h и присвоим ему произвольное числовое значение.

Третий параметр, uElapse – время таймера в миллисекундах.

Четвертый параметр, TIMERPROC – сюда либо указывается идентификатор сброса, либо NULL. Если укажем NULL, то через каждые uElapse миллисекунд таймер будет отправлять окну hwnd сообщение WM_TIMER.

Добавим в обработчик блок кода, который будет обрабатывать сообщение WM_TIMER:

Рисунок 20 – Обрабатываем сообщения от таймера
Рисунок 20 – Обрабатываем сообщения от таймера

Мы сделали переменные, которые отвечают за координаты статиков глобальными и теперь можем двигать их автоматически.

Проверим, как работает программа. Запустим приложение и нажмем пробел:

Рисунок 21 – Работа таймера
Рисунок 21 – Работа таймера

Таймер действительно работает, однако, если мы повторно нажмем пробел, первый «снаряд» остановится в полете, а второй мгновенно догонит его и начнет двигаться с той позиции игрека, на которой тот остановился. Это происходит потому, что при повторном нажатии на пробел, создается новый статик с именем hShot, а со старым связь теряется.

Чтобы этого избежать нам нужно создать функцию, которая будет проверять сколько выстрелов сделал истребитель и добавлять в программу новый статик. Сделаем так, чтобы одновременно в полете не могло быть больше десяти снарядов. Соответственно, добавим в программу еще десять дескрипторов: от hShot_0 до hShot_9, а также по десять переменных, в которых будем хранить координаты «снарядов».

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

Рисунок 22 – Фрагмент кода функции ItShot()
Рисунок 22 – Фрагмент кода функции ItShot()

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

Рисунок 23 – Фрагмент кода обработчика команд
Рисунок 23 – Фрагмент кода обработчика команд

Запускаем приложение, смотрим, что у нас получилось:

Рисунок 24 – Работа приложения
Рисунок 24 – Работа приложения

Отлично! Мы можем сделать сразу 10 залпов. Мало? Возможно. Но от этого игра будет только интереснее. Позже нужно будет сделать шкалу здоровья и шкалу боеприпасов. А пока что добавим в программу корабли противника. Пусть они появляются в рандомной иксовой координате, но притом в фиксированной игрековой. Корабли противника будут появляться раз в пять секунд (для первого раунда хватит). Создадим функцию, которая будет создавать корабли врага. Организуем ее по уже знакомому нам принципу:

Рисунок 25 – Фрагмент кода функции, создающей корабли противника
Рисунок 25 – Фрагмент кода функции, создающей корабли противника

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

Рисунок 26 – Фрагмент кода обработчика команд WM_TIMER
Рисунок 26 – Фрагмент кода обработчика команд WM_TIMER

Ну и, собственно, как-то так мы будем пока что задавать промежуток времени, через который будут появляться корабли:

Рисунок 27 – Фрагмент кода обработчика команд WM_TIMER
Рисунок 27 – Фрагмент кода обработчика команд WM_TIMER

Проверим, как работает наша программа:

Рисунок 28 – Работа таймера
Рисунок 28 – Работа таймера

Видите: таймер один, а объектов, которыми мы с помощью него управляем, много.

Истребитель есть, корабли противника есть и появляются они постепенно, как и должны. Теперь нам нужно реализовать функцию, которая будет отслеживать положение корабля противника и выпущенного нашим истребителем снаряда, и при их «встрече», уничтожать корабль, кратковременно подменяя его изображение на статике на то, которое приведено на рисунке 8, а затем удалит и сам статик. Функция будет работать по простейшему принципу: возьмет диапазон значений по иксу и проверит, попадает ли в него одна из переменных, обозначающих иксовые координаты «залпов».

Рисунок 29 – Пишем функцию, «взрывающую» корабли противника
Рисунок 29 – Пишем функцию, «взрывающую» корабли противника

В обработчик команд, в case WM_TIMER вносим правки:

Рисунок 30 – Корректируем код в case WM_TIMER
Рисунок 30 – Корректируем код в case WM_TIMER

Все действия выполняем однотипно: вызываем функцию boolInRange() при проверке каждого статика корабля противника, и если функция возвращает true, то картинку на соответствующем статике на ту, что приведена на рисунке 8, а при последующей итерации таймера – удаляем статик (строка 71 и подобные ей). Не бойтесь, объясняю я сумбурно только в этой статье. В остальных статьях цикла по программированию все пояснения будут как всегда подробными и понятными. Здесь же мы вскользь рассматриваем работу таймеров и создаем простое приложение – игрушку. Проверяем, как будет работать наша функция:

Рисунок 31 – Работа нашего приложения
Рисунок 31 – Работа нашего приложения

Добавим счетчик сбитых кораблей. По достижению 10 мы остановим игру и выведем сообщение о победе в первом туре.

Рисунок 32 – Добавляем счетчик сбитых кораблей противника
Рисунок 32 – Добавляем счетчик сбитых кораблей противника

Проверяем:

На зависания в гиф внимания не обращайте – это все бандикам. Первый тур игры создали. Осталось довести его до ума. Для начала нужно рандомизировать цели нашего истребителя, то есть добавить метеориты и боксы с запчастями и снарядами. Модернизируем функцию Enemy(). Добавим параметр, который будет определять, что именно программа будет направлять навстречу истребителю. Для каждой картинки пропишем свой HBITMAP:

Рисунок 34 – Загружаем картинки для статиков из ресурсов
Рисунок 34 – Загружаем картинки для статиков из ресурсов

В функции Enemy() пропишем конструкцию с условием:

Рисунок 35 – Модернизируем код в Enemy()
Рисунок 35 – Модернизируем код в Enemy()

В обработчике команд, в case WM_TIMER, пропишем rand для переменной, которую будем передавать в качестве параметра Selection в функцию Enemy():

Рисунок 36 – Генерируем различные картинки для статиков
Рисунок 36 – Генерируем различные картинки для статиков

Проверяем, как будет работать наша новая функция:

Рисунок 37 – Наша функция работает
Рисунок 37 – Наша функция работает

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

Переделаем RandomGoal из простой переменной в функцию типа int:

Рисунок 38 – Функция рандомизации
Рисунок 38 – Функция рандомизации

Думаю, одной «лечилки» и одного чемоданчика с запчастями, игроку вполне хватит. Что ж, проблему с излишней щедростью игры решили. Теперь добавим счетчик здоровья и счетчик патронов. Пусть, если мы пропустим один вражеский истребитель, наш корабль потеряет 10% здоровья, если пропустим метеор, то 20%. Ну, а если произойдет столкновение с кораблем противника или метеором – 100%. Сбить истребитель можно будет одним выстрелом, метеор – двумя. Чемоданчик с запчастями будет давать нам 50% здоровья, чемоданчик с патронами – 10 снарядов. Счетчики построим следующим образом:

Рисунок 39 – Создание шкалы здоровья
Рисунок 39 – Создание шкалы здоровья

В процессе игры будем отправлять статикам сообщения м STM_SETIMAGE:

Рисунок 40 – Актуализация данных здоровья
Рисунок 40 – Актуализация данных здоровья

И так 15 элементов. Для боезапаса всё сделаем аналогично: в начале игры создастся 10 статиков, окрашенные в «снаряды», а затем, будем убавлять или добавлять их количество, просто перекрашивая окна. Счет будем вести в специально созданных переменных типа int: ValueHealthи ValueAmm. Будем уменьшать ValueHealth на 1 после каждого пролета корабля противника на охраняемую нами территорию (для метеоритов чуть позже поправим скрипт).

Рисунок 41 – Уменьшаем счетчик
Рисунок 41 – Уменьшаем счетчик

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

Рисунок 42 – Счетчик здоровья работает
Рисунок 42 – Счетчик здоровья работает

Включим в код счетчик снарядов? Будем вызывать его по нажатию пробела:

Рисунок 43 – Вызываем AmmCounter() из case VK_SPACE
Рисунок 43 – Вызываем AmmCounter() из case VK_SPACE

Проверяем. Запускаем приложение и десять раз жмем на пробел:

Рисунок 44 – Счетчик снарядов работает
Рисунок 44 – Счетчик снарядов работает

Теперь ограничим счетчик здоровья только кораблями и истребителями. Создадим десять интовых переменных Enemy_0…Enemy_9, в которые будем записывать тип картинки на летящем к нашему кораблю статике. Если это вражеский корабль – запишем 1, если метеорит – 2, если лечилка – 3, если патроны – 4. В зависимости от типа, значения счетчика здоровья будут уменьшаться по-разному. Если типы будут 1, то счетчик уменьшится на 1, если метеорит, то есть 2, то на 2, а если 3 или 4, то не будут уменьшаться. Задавать тип будем в момент создания статика.

Рисунок 45 – Добавляем условия, регламентирующие работу счетчика здоровья
Рисунок 45 – Добавляем условия, регламентирующие работу счетчика здоровья

Теперь, если мимо пролетит истребитель, счетчик уменьшится на 1, если метеорит – на 2, если любой чемоданчик, то ничего не изменится. Проверяем:

Рисунок 46 – Счетчик игнорирует чемоданчики
Рисунок 46 – Счетчик игнорирует чемоданчики

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

Рисунок 47 – Код, отслеживающий положение корабля относительно ящичка с запчастями
Рисунок 47 – Код, отслеживающий положение корабля относительно ящичка с запчастями
Рисунок 48 – Код, отслеживающий положение корабля относительно ящичка со снарядами
Рисунок 48 – Код, отслеживающий положение корабля относительно ящичка со снарядами

Не забываем также вызвать AmmCounter() из этого условия. Проверяем, как будут работать наши условия:

Рисунок 49 – Счетчики здоровья и снарядов работают
Рисунок 49 – Счетчики здоровья и снарядов работают

Теперь обработаем столкновение истребителя с кораблем противника или с метеоритом.

Рисунок 50 – Создаем условие, срабатывающее при столкновении истребителя с противником
Рисунок 50 – Создаем условие, срабатывающее при столкновении истребителя с противником

При столкновении мы подменяем картинку на статике истребителя на «взрыв», обнуляем счетчик здоровья и останавливаем таймер.

Проверяем:

Рисунок 51 – Функция «уничтожения» корабля работает
Рисунок 51 – Функция «уничтожения» корабля работает

Осталось немного:

- Добавить функцию, которая при обнулении счетчика здоровья остановит игру и выведет сообщение с текстом «Вы проиграли!»;

- Заменить MessageBox(), который выскакивает при выигрыше на статик с аналогичным сообщением и кнопкой «Далее»;

- Организовать 10 уровней игры;

- «Пофиксить баг» с зависанием игрековых координат Shot-ов.

Идем по порядку. Создадим картинку для статика с надписью «Вы проиграли!». Пусть размер статика будет 500х300.

Рисунок 52 – Картинка для статика
Рисунок 52 – Картинка для статика

Подгружаем картинку в программу в файле resources.h, а в main.cpp пишем функцию, в которой создадим статик под нее. Также на этом статике отобразим две кнопки: «Try again» и «Exit». Первая, как Вы уже догадались будет перезапускать игру, а вторая закрывать приложение. При создании кнопок не забываем, что они дочерние не для статика, а для hwnd. Соответственно и координаты кнопок указываем относительно hwnd.

Рисунок 53 – Создаем функцию YouLose()
Рисунок 53 – Создаем функцию YouLose()

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

Рисунок 54 – Если счетчик здоровья опустится в ноль, то игра будет окончена
Рисунок 54 – Если счетчик здоровья опустится в ноль, то игра будет окончена

Проверяем:

Рисунок 55 – Выводится окно с сообщением о проигрыше
Рисунок 55 – Выводится окно с сообщением о проигрыше

У нас есть одна существенная ошибка: допустим, у нас осталось три деления здоровья, мы пропустили два метеора и… игра продолжается. Почему? Потому что мы можем проиграть только если счетчик здоровья станет равным 0. А при данных условиях, он будет уже отрицательным, то есть условие не сработает. Значит нужно заменить условие с «равно нулю» на «меньше либо равно нулю». Так и поступим.

Идем дальше. Нужно создать сообщение о победе в раунде. Делаем его аналогичным сообщению о проигрыше:

Рисунок 56 – Сообщение о победе
Рисунок 56 – Сообщение о победе

На статик добавляем кнопку Continue:

Рисунок 57 – Функция, которая сработает в случае нашей победы
Рисунок 57 – Функция, которая сработает в случае нашей победы

Вызовем ее из WM_TIMER:

Рисунок 58 – Вызываем функцию из WM_TIMER
Рисунок 58 – Вызываем функцию из WM_TIMER

Теперь, при уничтожении десятого противника мы получим сообщение о победе:

Рисунок 59 – Сообщение о победе после уничтожения десятого противника
Рисунок 59 – Сообщение о победе после уничтожения десятого противника

Добавим в игру счетчик уровней. Назовем его LevelCounter. После каждого нажатия на «Continue» он будет увеличиваться на единицу.

Рисунок 60 – Запускаем новый раунд
Рисунок 60 – Запускаем новый раунд

Скорость движения объектов с каждым раундом будет увеличиваться в то количество раз, которому равно LevelCounter. Также обработаем нажатия на клавиши «заново» и «выход».

Рисунок 61 – Скрипт для клавиш «Заново» и «Выход»
Рисунок 61 – Скрипт для клавиш «Заново» и «Выход»

При нажатии на «Заново» мы воссоздаем начальные условия игры, а при нажатии на «Выход» закрываем приложение. Перенесем счетчик уровней из case кнопки «Далее» в строку, из которой вызываем функцию YouWin(), и при достижении счетчиком значения 11, будем выводить поверх статика не кнопку «Далее», а кнопки «Заново» и «Выход». Проверим, работают ли эти кнопки:

Рисунок 62 – Проверка работы клавиш
Рисунок 62 – Проверка работы клавиш

Как видим, все кнопки работают как надо. Остается сделать три вещи:

- При каждом уничтожении снаряда сбрасывать его икс координату до нуля;

- Добавить кнопку «Старт» в начало игры, чтобы она начиналась по нажатию, а не автоматически.

Рисунок 63 – Сбрасываем все xShot в ноль после удаления статика
Рисунок 63 – Сбрасываем все xShot в ноль после удаления статика

Добавляем кнопку Старт. Можно сделать это прямо в WM_CREATE:

Рисунок 64 – Создаем кнопку «Старт»
Рисунок 64 – Создаем кнопку «Старт»

Тут же убираем отсюда всё, что относится к таймеру и переносим это под команду нажатия кнопки Старт.

Рисунок 65 – Переносим инициализацию таймера в WM_COMMAND
Рисунок 65 – Переносим инициализацию таймера в WM_COMMAND

Теперь при запуске приложения, игра не начинается автоматически, как раньше:

Рисунок 66 – Наша игра
Рисунок 66 – Наша игра

Реализовывать двойной выстрел по метеорам, пожалуй, не будем. Игра и так получилась вполне неплохой.

Вот, собственно, и всё. Мы с Вами написали простую многоуровневую мини-игру для ПК в стиле старых аркадных автоматов. Заметьте: для реализации объектов игры мы использовали только статики и ничего больше!

Ниже приведу код из всех файлов программы. Все рисунки, которые в ней использовались, есть выше в статье.

А теперь поговорим для кого написана эта статья. Для тех, кто уже прочел статьи цикла WinApi, где мы с Вами разрабатываем приложение для автосервиса. Данная статья – это своеобразная разгрузка, а также способ показать Вам, что не всегда обязательно использовать макросы для реализации сложных программ. Ну и к тому же, я сам немного отвлекся от сложных объяснений в цикле статей по С++ и написал свою первую игрушку (раньше как-то не приходилось, но давно хотелось).

Что ж, Надеюсь, что статья была для Вас интересной. Скоро мы вернемся к другим темам и предметам (физике, математике и программированию). А пока что, спасибо, что читаете. Удачи в учебе и труде!

PS: Ах да, если статья понравилась, поддержите лайком: Вам не сложно, а я буду знать, что не зря старался!

Содержимое файла resources.rc

#include "resources.h"

FonC BITMAP "Fon.bmp"

Содержимое файла resources.h

#define FonC 1

#define Again 2

#define Exit 3

#define ContinueS 4

#define StartS 5

#define TimerStart 10

#define TimerStop 11

#include <windows.h>

HWND hwnd;

HWND hFighter;

HWND hShot_0;

HWND hShot_1;

HWND hShot_2;

HWND hShot_3;

HWND hShot_4;

HWND hShot_5;

HWND hShot_6;

HWND hShot_7;

HWND hShot_8;

HWND hShot_9;

HWND hEnemy_0;

HWND hEnemy_1;

HWND hEnemy_2;

HWND hEnemy_3;

HWND hEnemy_4;

HWND hEnemy_5;

HWND hEnemy_6;

HWND hEnemy_7;

HWND hEnemy_8;

HWND hEnemy_9;

HWND hHealth_0; HWND hHealth_1; HWND hHealth_2; HWND hHealth_3; HWND hHealth_4; HWND hHealth_5; HWND hHealth_6; HWND hHealth_7;

HWND hHealth_8; HWND hHealth_9; HWND hHealth_10; HWND hHealth_11; HWND hHealth_12; HWND hHealth_13; HWND hHealth_14;

HWND hAmm_0; HWND hAmm_1; HWND hAmm_2; HWND hAmm_3; HWND hAmm_4; HWND hAmm_5; HWND hAmm_6; HWND hAmm_7; HWND hAmm_8; HWND hAmm_9;

HWND hAmm_10; HWND hAmm_11; HWND hAmm_12; HWND hAmm_13; HWND hAmm_14; HWND hAmm_15; HWND hAmm_16; HWND hAmm_17; HWND hAmm_18; HWND hAmm_19;

HWND hLose;

HWND hWinn;

HWND btnAgain;

HWND btnExit;

HWND btnContinue;

HWND btnStart;

int xFighter = 402;

int yFighter = 476;

int xShot_0 = 0; int xShot_1 = 0; int xShot_2 = 0; int xShot_3 = 0; int xShot_4 = 0;

int xShot_5 = 0; int xShot_6 = 0; int xShot_7 = 0; int xShot_8 = 0; int xShot_9 = 0;

int yShot_0 = 440; int yShot_1 = 440; int yShot_2 = 440; int yShot_3 = 440; int yShot_4 = 440;

int yShot_5 = 440; int yShot_6 = 440; int yShot_7 = 440; int yShot_8 = 440; int yShot_9 = 440;

int xEnemy_0 = 0; int xEnemy_1 = 0; int xEnemy_2 = 0; int xEnemy_3 = 0; int xEnemy_4 = 0;

int xEnemy_5 = 0; int xEnemy_6 = 0; int xEnemy_7 = 0; int xEnemy_8 = 0; int xEnemy_9 = 0;

int Enemy_0 = 0; int Enemy_1 = 0; int Enemy_2 = 0; int Enemy_3 = 0; int Enemy_4 = 0;

int Enemy_5 = 0; int Enemy_6 = 0; int Enemy_7 = 0; int Enemy_8 = 0; int Enemy_9 = 0;

int yEnemy_0 = 20; int yEnemy_1 = 20; int yEnemy_2 = 20; int yEnemy_3 = 20; int yEnemy_4 = 20;

int yEnemy_5 = 20; int yEnemy_6 = 20; int yEnemy_7 = 20; int yEnemy_8 = 20; int yEnemy_9 = 20;

int EnemyTimer = 90;

int ValueHealth = 10;

int ValueAmm = 10;

int LevelCounter = 1;

int DownedShips = 0;

int xStep_1 = 0; int xStep_2 = 0;

int RandomGoal();

int ValH();

HBITMAP bmpFighter = (HBITMAP)LoadImage(NULL, "Starfighter.bmp", IMAGE_BITMAP, 66, 66, LR_LOADFROMFILE);

HBITMAP bmpShot = (HBITMAP)LoadImage(NULL, "piu.bmp", IMAGE_BITMAP, 7, 7, LR_LOADFROMFILE);

HBITMAP bmpEnemy_1 = (HBITMAP)LoadImage(NULL, "Enemyship.bmp", IMAGE_BITMAP, 66, 66, LR_LOADFROMFILE);

HBITMAP bmpEnemy_2 = (HBITMAP)LoadImage(NULL, "Meteor.bmp", IMAGE_BITMAP, 66, 66, LR_LOADFROMFILE);

HBITMAP bmpEnemy_3 = (HBITMAP)LoadImage(NULL, "repair.bmp", IMAGE_BITMAP, 66, 66, LR_LOADFROMFILE);

HBITMAP bmpEnemy_4 = (HBITMAP)LoadImage(NULL, "ammunition.bmp", IMAGE_BITMAP, 66, 66, LR_LOADFROMFILE);

HBITMAP bmpBang = (HBITMAP)LoadImage(NULL, "bum.bmp", IMAGE_BITMAP, 66, 66, LR_LOADFROMFILE);

HBITMAP bmpHealth = (HBITMAP)LoadImage(NULL, "Health.bmp", IMAGE_BITMAP, 7, 25, LR_LOADFROMFILE);

HBITMAP bmpAmm = (HBITMAP)LoadImage(NULL, "Amm.bmp", IMAGE_BITMAP, 7, 25, LR_LOADFROMFILE);

HBITMAP bmpBlack = (HBITMAP)LoadImage(NULL, "Black.bmp", IMAGE_BITMAP, 7, 25, LR_LOADFROMFILE);

HBITMAP bmpLose = (HBITMAP)LoadImage(NULL, "Lose.bmp", IMAGE_BITMAP, 500, 300, LR_LOADFROMFILE);

HBITMAP bmpWinn = (HBITMAP)LoadImage(NULL, "Winn.bmp", IMAGE_BITMAP, 500, 300, LR_LOADFROMFILE);

bool IsTimer = FALSE;

bool IsSPACE = FALSE;

bool IsEnemy = FALSE;

bool DestEnemy_0 = FALSE; bool DestEnemy_1 = FALSE; bool DestEnemy_2 = FALSE; bool DestEnemy_3 = FALSE; bool DestEnemy_4 = FALSE;

bool DestEnemy_5 = FALSE; bool DestEnemy_6 = FALSE; bool DestEnemy_7 = FALSE; bool DestEnemy_8 = FALSE; bool DestEnemy_9 = FALSE;

bool InRange(int lxEnemy, int vyEnemy);

void Objects(HWND hwnd, HINSTANCE hInstance);

void ItShot(HWND hwndParrent);

void Enemy(HWND hwndParrent, int Selection);

void HealthCounter(int Value, HWND hWndParrent);

void AmmCounter(int Value, HWND hWndParrent);

void YouLose();

void YouWinn();

Содержимое файла main.cpp

В отдельной публикации.