
Полная версия
MINI DOOM современный С++ через игру том 1 учимся держать оружие

Алексей Примак
MINI DOOM современный С++ через игру том 1 учимся держать оружие
Предисловие
Привет. Если ты держишь эту книгу, значит, тебе хочется не просто «выучить C++», а сделать что-то своё. Отлично, потому что именно этим мы и займёмся. За двадцать девять глав мы с тобой с нуля напишем игру в духе Doom: с коридорами, дверями, импами, которые швыряются огнём, зомби с винтовками, дробовиком и статус-баром внизу экрана. Без готовых движков, без скачанных картинок и звуков — всё своими руками, строчка за строчкой.

Рисунок 0.1 — Так выглядит MINI DOOM в конце книги: титульный экран, имп вблизи, дробовик, адский зал. Ни одной картинки из интернета — всё нарисовано кодом
Посмотри на рисунок 0.1. Каждый пиксель на нём — результат кода, который ты напишешь. Текстура кирпича, рожа импа, огонь на титульнике и даже звук выстрела — всё это генерируется программой при запуске. Звучит как магия, но к концу книги ты будешь знать, как работает каждый фокус.
Для кого эта книга
Для тех, кто ни разу не программировал или пробовал, но бросил на третьей странице скучного учебника. Я не буду требовать от тебя знаний математики сверх школьной, а ту математику, что понадобится — синус, косинус, векторы, — объясню на пальцах прямо тогда, когда она понадобится, и ни минутой раньше.
Если ты уже немного знаешь C++, тоже не уходи. Первую часть можешь пролистать по диагонали, а со второй начнётся то, чего в обычных учебниках нет: своё окно, пиксели, псевдо-3D, искусственный интеллект монстров, синтез звука и многопоточность.
Книга маркирована 18+. Не потому, что тут кровь и кишки (хотя импы истекают пиксельной кровью), а потому что я разговариваю с тобой как с взрослым: иногда крепким словом, когда программа ведёт себя как последняя сволочь. Дозированно, как соль в супе.
Почему Doom
Потому что Doom — это идеальный учебный проект. Он достаточно простой, чтобы один человек мог написать его за книгу, и достаточно сложный, чтобы по дороге встретилось всё важное: массивы и классы, указатели и память, математика и алгоритмы, потоки и звук. А ещё потому, что стрелять по демонам веселее, чем считать среднюю зарплату сотрудников в отделе, как в половине учебников.

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

Таблица 0.1 — Пять частей книги
Внутри главы всё идёт по одному сценарию: сначала объясняю идею на пальцах, потом пишем код, потом смотрим на настоящий вывод программы и разбираем его, потом — грабли, на которые наступают все, и в конце упражнения и задачи. Ответы на них собраны в конце книги.
И главное правило: каждая глава заканчивается работающей программой. Не «допишем в следующей главе», а прямо сейчас: собрал, запустил, увидел результат. Я называю это контрольной точкой — как в игре, где можно сохраниться.
Как читать листинги
Код в книге выглядит как окно редактора. Сверху — имя файла и язык, слева — номера строк, на которые я ссылаюсь в тексте. Под кодом тёмная панель: это то, что программа напечатала, когда я её запустил. Вот пример — листинг 0.1.

Листинг 0.1 — Как выглядит листинг: код, номера строк и вывод программы
Первая зелёная строка в тёмной панели, «$ ./a.out», — это команда запуска. У тебя в Visual Studio никакую команду набирать не нужно: жмёшь Ctrl+F5, и открывается консоль с тем же самым выводом. Строки, начинающиеся с решётки, — мои комментарии, программа их не печатала.
Весь вывод в книге настоящий. Каждый пример я компилировал с максимально строгими предупреждениями и проверял санитайзерами — специальными инструментами, которые ловят ошибки работы с памятью. Если у тебя вывод отличается — скорее всего, отличается и код. Сверься с архивом примеров.
Если компилятор отказывается собирать программу, панель подписана «компилятор ругается». Такие листинги я показываю специально: читать ошибки компилятора — такой же навык, как писать код. Сообщения в них от компилятора GCC; Visual Studio скажет то же самое своими словами, и в главе 1 мы разберём, как переводить одно в другое.
Цветные врезки
По тексту разбросаны цветные врезки четырёх видов. Вот они все.




Что понадобится
— Компьютер с Windows 10 или 11. Игра использует только то, что есть в самой Windows, никаких сторонних библиотек.
— Visual Studio 2026, бесплатная редакция Community. Как её поставить, расскажу в главе 1.
— Желание разобраться. Серьёзно, это важнее всего остального.
Мышь тоже пригодится: в Doom без мыши играть можно, но это как есть суп вилкой.
Архив примеров
Весь код из книги лежит на GitHub: https://github.com/RushSex/MiniDoom. Скачать его можно одним архивом — зелёная кнопка Code, затем Download ZIP — или командой git clone; дальше в книге я называю его просто архивом примеров. В папке examples там по папке на каждую главу — от ch01 до ch29, — и в каждой папке программа в том виде, в каком она должна быть в конце главы. Если запутался — открой папку своей главы и сравни.
Но не превращай архив в копипасту. Код, набранный своими пальцами, запоминается в десять раз лучше, а опечатки, которые ты при этом наделаешь, научат читать ошибки компилятора. Это не баг обучения, это его фича.
Поехали
Хватит предисловий. Ставим Visual Studio, пишем первую программу и заставляем компьютер сказать «Hello, Doom!». Переворачивай страницу.
Пять томов
Книга «MINI DOOM. Современный C++ через игру» выходит в пяти томах — по одному на каждую часть. Каждый заканчивается работающей программой, и в каждом свои ответы, глоссарий и указатель.
— Том 1. Учимся держать оружие — главы 1–8: основы C++ в консоли, от первой программы до памяти и санитайзеров.
— Том 2. Окно в ад — главы 9–13: своё окно Windows, пиксели, игровой цикл, клавиатура и мышь, текст.
— Том 3. Третье измерение — главы 14–19: рейкастинг, стены, текстуры, пол и свет, двери.
— Том 4. Здесь кто-то есть — главы 20–25: спрайты, монстры, оружие, искусственный интеллект, смерть.
— Том 5. Шум, свет и финальные титры — главы 26–29: звук, экраны, уровни, CMake и тесты.
Все исходники серии — на GitHub: https://github.com/RushSex/MiniDoom. Папка examples/chNN для каждой главы, итоговая игра — examples/ch29/doom. Там же README с инструкцией по сборке в Visual Studio 2026.
Часть I. Учимся держать оружие
Прежде чем стрелять по демонам, надо научиться держать оружие. В этой части мы освоим основы C++ в самой простой обстановке — в чёрном окне консоли. Никакой графики, никаких окон Windows, только ты, код и компилятор. Зато к концу части у тебя будет консольный рогалик: персонаж бегает по карте, монстры бродят вокруг, а код устроен так, как пишут настоящие программисты.
Глава 1. Hello, Doom
Любое путешествие начинается с первого шага, а любой программист — с программы, которая печатает «Hello, World!». Мы поступим так же, только поздороваемся не с миром, а с Doom. В этой главе ты поставишь Visual Studio, создашь первый проект, настроишь его как профи, заставишь компьютер нарисовать титульный экран игры буквами и, что не менее важно, научишься не бояться, когда компилятор начинает ругаться.
Что такое программа и зачем нужен компилятор
Процессор твоего компьютера — тупой, но очень быстрый исполнитель. Он понимает только машинный код: длинные последовательности чисел вроде «возьми число отсюда, прибавь вот это, положи туда». Писать так руками можно, люди так и делали в пятидесятых, но удовольствия примерно как от заполнения налоговой декларации на латыни.
Поэтому придумали языки программирования. Ты пишешь текст, понятный человеку, а специальная программа — компилятор — переводит его в машинный код. Представь шеф-повара, который не знает русского, и переводчика, который берёт твой рецепт и переписывает его на язык повара. Рецепт — это исходный код, переводчик — компилятор, а блюдо — готовая программа, файл с расширением .exe.
На самом деле переводчиков двое. Сначала компилятор переводит каждый файл с кодом отдельно и получает объектный файл — полуфабрикат. Потом компоновщик (его ещё называют линковщиком) склеивает полуфабрикаты между собой и с готовыми кусками из стандартной библиотеки C++. Получается исполняемый файл. Весь путь показан на рисунке 1.1.

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

Ставим Visual Studio 2026
Visual Studio — это среда разработки: редактор кода, компилятор, компоновщик и отладчик в одной коробке. Для учёбы и личных проектов есть бесплатная редакция Community, её нам с головой хватит.
— Скачай установщик Visual Studio 2026 Community с сайта Microsoft и запусти его.
— Установщик спросит, какие «рабочие нагрузки» ставить. Отметь «Разработка классических приложений на C++». В ней компилятор MSVC, библиотеки Windows и всё, что понадобится до конца книги.
— Остальное не трогай и жми «Установить». Скачается несколько гигабайт, так что самое время сделать чай.


Первый проект
Запусти Visual Studio. На стартовом экране выбери «Создание проекта». В списке шаблонов найди «Консольное приложение» с пометкой C++ — проще всего ввести «консоль» в строку поиска и выбрать язык C++ в фильтре. Нажми «Далее», дай проекту имя, например MiniDoom_ch01, и нажми «Создать».
Visual Studio создаст проект и откроет файл с заготовкой программы. Заготовку можно сразу запустить комбинацией Ctrl+F5: появится консоль с текстом Hello World. Значит, всё установилось правильно. Остальные полезные клавиши собраны в таблице 1.1.

Таблица 1.1 — Горячие клавиши Visual Studio, которые понадобятся сразу
Настраиваем строгость
По умолчанию компилятор ведёт себя как добрый учитель: видит подозрительное место, вздыхает, пишет предупреждение мелким шрифтом и всё равно собирает программу. Предупреждения тонут в выводе, ты их не читаешь, а через неделю программа падает ровно там, где компилятор вежливо намекал. Мы сделаем из него злого прапорщика.
Щёлкни правой кнопкой по проекту в «Обозревателе решений» и выбери «Свойства». Вверху окна поставь «Конфигурация: Все конфигурации» и «Платформа: Все платформы», чтобы настройки применились и к отладочной, и к финальной сборке. Затем выставь значения из таблицы 1.2.

Таблица 1.2 — Настройки проекта, которые мы ставим в каждой главе

Hello, Doom
Удали из файла всё, что там написала Visual Studio, и набери код из листинга 1.1. Именно набери руками, не копируй: пальцы тоже должны учиться.

Листинг 1.1 — Первая программа
Нажми Ctrl+F5. Откроется консоль, и в ней будет ровно то, что в тёмной панели под кодом: строка Hello, Doom!. Поздравляю, ты программист. Ну, почти. Теперь разберём, что тут происходит, строчка за строчкой.
Строка 1. #include
Строка 3. int main() — это функция main, точка входа в программу. С неё всё начинается, когда ты запускаешь .exe. Слово int перед ней означает, что функция вернёт целое число, а пустые круглые скобки — что ей ничего не передают. Фигурные скобки на строках 3 и 6 отмечают начало и конец тела функции.
Строка 4. Главная. std::cout — это консоль, точнее поток вывода в неё. Префикс std:: говорит, что cout живёт в пространстве имён std — там лежит вся стандартная библиотека. Оператор << можно читать как «отправь туда»: строку "Hello, Doom!\n" отправили в консоль. Два символа \n в конце — это перевод строки: курсор переходит на новую строку, как от Enter. Точка с запятой в конце завершает инструкцию.
Строка 5. return 0; завершает функцию main и сообщает операционной системе: всё прошло нормально. Ноль по старой традиции означает успех, любое другое число — какую-то ошибку. У main есть привилегия: если return забыть, C++ сам вернёт 0. Но мы пока пишем явно, чтобы было видно.
Как оператор << гонит данные в консоль, показано на рисунке 1.2. Его удобно представлять конвейерной лентой: что положил слева, то и уехало направо, в том же порядке.

Рисунок 1.2 — Оператор << как конвейер: значения по очереди уезжают в std::cout и появляются в консоли

Рисуем титульный экран
Строка Hello, Doom! — это мило, но у настоящей игры должен быть титульный экран. Нарисуем его буквами, как делали в восьмидесятых, — это называется ASCII-арт. Код в листинге 1.2.

Листинг 1.2 — Титульный экран MINI DOOM из символов
Сравни код и вывод: картинка напечаталась ровно в том виде, в каком лежит в коде. Всё благодаря «сырой» строке R"(...)" на строках 7–13. В обычной строке обратный слэш — особый символ: \n означает перевод строки, а чтобы напечатать сам слэш, его надо удваивать. В нашем рисунке слэшей полно, и с обычной строкой пришлось бы аккуратно удваивать каждый. Сырая строка отключает эту магию: всё между R"( и )" попадает в программу буква в букву, вместе с переносами строк.
Строки 1, 5 и 6 начинаются с двух косых черт. Это комментарии: всё от // до конца строки компилятор пропускает. Комментарии пишут для людей — для коллег и для себя через полгода, когда забудешь, что тут происходит. А забудешь обязательно.
Обрати внимание на первую строку вывода: она пустая. Это не баг. Сырая строка начинается сразу после открывающей скобки, а там стоит перевод строки — он тоже попал в вывод.

Когда компилятор ругается
Рано или поздно — скорее рано — ты нажмёшь Ctrl+F5, а вместо программы получишь список ошибок. Это нормально. Опытные программисты видят ошибки компилятора по сто раз на дню и не расстраиваются, потому что ошибка компиляции — лучшая ошибка из возможных. Её нашли до запуска, а не пользователь посреди уровня.
Давай сломаем нашу программу специально и посмотрим, что скажет компилятор. Начнём с классики — забытой точки с запятой в листинге 1.3.

Листинг 1.3 — Забыли точку с запятой после строки 4
Сообщение выглядит пугающе, но устроено всегда одинаково. Разберём его по частям на рисунке 1.3.

Рисунок 1.3 — Анатомия сообщения об ошибке: файл, строка, столбец, тип и суть
Компилятор говорит: в файле main.cpp, в строке 4, в столбце 34 он ожидал точку с запятой перед словом return. Ниже он даже рисует, куда её поставить. Добавь ; в конец строки 4 — и всё соберётся.

Вторая классика — опечатка. В листинге 1.4 вместо cout написано cot.

Листинг 1.4 — Опечатка в имени: cot вместо cout
Компилятор честно говорит, что в std нет никакого cot, и даже пытается угадать, что ты имел в виду. Угадывает, правда, oct — это штука для печати чисел в восьмеричной системе, к нашей ситуации отношения не имеет. Подсказки компилятора полезны, но слепо им верить не стоит: он видит буквы, а не твои намерения.
Третий пример хитрее. В листинге 1.5 синтаксически всё правильно, но есть переменная health, которой мы так и не воспользовались. Переменные разберём в следующей главе, а пока просто посмотри, что происходит.

Листинг 1.5 — Переменная объявлена, но не используется
Без строгих настроек это было бы безобидное предупреждение, и программа собралась бы. Но мы включили режим «предупреждения как ошибки», поэтому сборка остановилась. Последняя строка так и говорит: все предупреждения считаются ошибками. Зачем нам такие сложности? Неиспользуемая переменная часто означает, что ты что-то забыл. Например, посчитал урон и забыл его нанести.
Сообщения в листингах — от компилятора GCC. Visual Studio скажет то же самое своими словами и с номером ошибки, который удобно гуглить. Соответствие собрано в таблице 1.3.

Таблица 1.3 — Как переводить ошибки GCC на язык Visual Studio
Главные правила чтения ошибок такие:
— Читай первую ошибку. Одна настоящая ошибка часто тянет за собой десяток наведённых, и после её исправления остальные исчезают сами.
— Смотри на номер строки и на строку выше.
— Читай суть целиком, а не только слово error. Компилятор обычно говорит прямо, чего ему не хватает.
— Если ничего не понятно — скопируй код ошибки и поищи его в интернете. С этой ошибкой до тебя уже встретились тысячи людей.
Итоги главы
— Компилятор переводит код в машинные команды, компоновщик склеивает из них программу.
— Visual Studio 2026 с нагрузкой «Разработка классических приложений на C++» — наш верстак до конца книги.
— Каждый проект настраиваем строго: C++20, /W4, /WX, /permissive-, /utf-8.
— Программа начинается с функции main, текст печатается через std::cout и оператор <<.
— Сырая строка R"(...)" сохраняет текст как есть — идеально для ASCII-арта.
— Ошибки компилятора не страшны: читаем первую, смотрим на строку и строку выше.
Контрольная точка: у тебя есть программа, которая печатает титульный экран MINI DOOM. В следующей главе на нём появятся числа — здоровье, патроны и урон, — и мы посчитаем, сколько выстрелов нужно, чтобы уложить импа.
Упражнения
Упражнение 1.1. Что выведет? Не запуская программу из листинга 1.6, напиши на бумаге, что она напечатает. Потом проверь себя.

Листинг 1.6 — Упражнение 1.1
Упражнение 1.2. Найди баг. Программа из листинга 1.7 не собирается. Почему? Попробуй догадаться до того, как соберёшь её.

Листинг 1.7 — Упражнение 1.2
Упражнение 1.3. Напиши сам. Выведи надпись PRESS ENTER в рамке из символов +, - и |. Рамка должна быть ровной: углы на своих местах, стороны одной длины.
Упражнение 1.4. Подумай. Программа с неиспользуемой переменной из листинга 1.5 прекрасно работала бы и без режима /WX. Зачем же мы сами себе мешаем? Приведи пример, когда лишнее предупреждение спасает от настоящего бага.
Задачи
Задача 1.1. Экран загрузки. Напиши программу, которая печатает заголовок с версией игры и несколько строк вида «Loading textures ...... OK», где слово OK во всех строках стоит строго в одном столбце. Закончи боевым кличем.
Задача 1.2. Свой баннер. Нарисуй в сырой строке титульник со своим ником. Потом попробуй вставить в рисунок сочетание символов )" — например, нарисуй смайлик. Что случится и как это обойти? Подсказка: у сырой строки можно задать свой разделитель.
Задача 1.3. Коллекционер ошибок. Сломай программу из листинга 1.1 пятью разными способами: убери скобку, закавычь не то, переименуй main, удали #include и так далее. Для каждой поломки выпиши первую строку ошибки и объясни её своими словами. Это лучшая прививка от страха перед компилятором.
Глава 2. Переменные и типы
Открою тебе страшную тайну: любая игра — это просто куча чисел. Здоровье игрока — число. Патроны — число. Координаты импа, угол поворота камеры, урон дробовика, время на уровне — числа, числа, числа. Картинка на экране и звук в колонках — тоже числа, только их очень много. Поэтому первое, чему надо научиться, — хранить числа и считать с ними, не отстрелив себе ногу.
В этой главе мы разберёмся, что такое переменная, какие бывают типы, почему 7 / 2 в C++ равно трём, а 0,1 + 0,2 не равно 0,3. А в конце напишем калькулятор, который посчитает, сколько выстрелов из пистолета нужно, чтобы уложить импа.
Переменная — коробка на складе
Представь огромный склад, где вдоль стен стоят пронумерованные коробки. Номер коробки — это адрес в памяти. На коробку можно наклеить этикетку с именем, например health, и положить внутрь число 100. Это и есть переменная: именованная коробка в памяти, в которой лежит значение.
У каждой коробки есть ещё и тип — он говорит, что в ней можно хранить и какого она размера. В коробку для целых чисел не положишь дробное, а в коробку для одной буквы не влезет целое предложение. Посмотрим на листинг 2.1.

Листинг 2.1 — Переменные игрока: создаём, печатаем, меняем
Строки 4–6 создают три переменные. Запись int health = 100; читается так: «заведи коробку типа int с этикеткой health и положи в неё 100». Тип int хранит целые числа, double — дробные. Здоровье и патроны у нас целые, а скорость — 3,4 клетки в секунду — дробная. Заметь: в коде дробную часть отделяют точкой, а не запятой.
Строки 8–10 печатают значения. Когда в std::cout отправляешь имя переменной, печатается не имя, а то, что лежит в коробке.
Строка 12 самая интересная. ammo = ammo - 1; с точки зрения математики — бред: число не может быть равно самому себе минус один. Но в C++ знак = означает не «равно», а «положи»: возьми, что лежит в ammo, вычти единицу и положи результат обратно в ammo. Было 50 — стало 49.









