C++ с нуля: создание простых игр и приложений

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

1. Основы синтаксиса и переменные

Основы синтаксиса и переменные

Язык программирования C++ — это один из самых мощных и популярных инструментов в мире разработки. На нём написаны операционные системы, браузеры и, что самое интересное для нас, большинство современных высокобюджетных игр. Движок Unreal Engine, на котором созданы Fortnite, The Witcher 4 и S.T.A.L.K.E.R. 2, использует именно C++.

Компьютер не понимает человеческую речь. Он понимает только нули и единицы — машинный код. Язык программирования выступает в роли переводчика: мы пишем понятные нам команды, а специальная программа (компилятор) переводит их в нули и единицы. Чтобы этот переводчик нас понял, мы должны соблюдать строгие правила грамматики и пунктуации. В программировании эти правила называются синтаксисом.

Анатомия первой программы

Любая игра или приложение начинается с базового каркаса. Давайте посмотрим на самую простую программу на C++, которая выводит на экран приветствие для игрока.

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

  • #include <iostream> — это команда подключения библиотеки. Представьте, что вы собираете шкаф. Сами по себе ваши руки не могут закрутить шуруп, вам нужна отвертка. Эта строчка говорит компьютеру: «Возьми из кладовки набор инструментов для ввода и вывода данных (Input/Output Stream)». Без неё программа не сможет ничего написать на экране.
  • int main() { ... } — это главная точка входа в программу. Когда вы запускаете игру (кликаете по иконке), компьютер ищет именно функцию с именем main (главная) и начинает выполнять команды, спрятанные внутри фигурных скобок {}. Фигурные скобки обозначают границы: где начинается и где заканчивается главный блок кода.
  • std::cout << "Hello, Player!"; — это команда вывода текста на экран.
  • - std:: указывает, что мы берем инструмент из стандартного набора (standard). - cout расшифровывается как character output (вывод символов). - << — это стрелки, которые показывают направление. Мы буквально берем текст "Hello, Player!" и отправляем его в cout (на экран). - ; (точка с запятой) — это важнейший элемент синтаксиса C++. Она работает как точка в конце обычного предложения. Если её забыть, компилятор выдаст ошибку, потому что не поймет, где закончилась ваша мысль.
  • return 0; — эта команда сообщает операционной системе, что программа завершила свою работу успешно (вернула ноль ошибок).
  • Переменные: коробки для хранения данных

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

    Переменную проще всего представить как картонную коробку на складе.

    !Переменные как коробки для хранения данных

    У этой коробки есть три обязательных свойства:

  • Имя (маркер на коробке), чтобы мы могли её найти. Например: health, score, playerName.
  • Тип (форма коробки), который определяет, что именно можно положить внутрь. В коробку для чисел нельзя положить текст.
  • Значение (содержимое коробки) — то, что лежит внутри прямо сейчас. Например, число 100.
  • Базовые типы данных

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

    | Тип данных | Что хранит | Пример из игр | Пример в коде | | :--- | :--- | :--- | :--- | | int | Целые числа (без дробей) | Количество жизней, патроны, уровень | int level = 5; | | double | Дробные числа | Скорость бега, точные координаты, вес | double speed = 4.5; | | char | Один символ | Нажатая клавиша на клавиатуре | char moveForward = 'W'; | | bool | Логическое значение (да/нет) | Жив ли игрок? Открыта ли дверь? | bool isAlive = true; | | std::string | Текст (строка символов) | Имя персонажа, название локации | std::string name = "Hero"; |

    > Важное правило: имена переменных в C++ могут состоять только из латинских букв, цифр и знака подчеркивания _. При этом имя не может начинаться с цифры. Также в C++ имеет значение регистр букв: Score и score — это две совершенно разные переменные.

    Создание и использование переменных

    Процесс создания переменной называется объявлением. Вы можете сначала создать пустую коробку, а потом положить в неё значение, либо сделать это одновременно (это называется инициализацией).

    Знак = в программировании означает не математическое равенство, а команду присваивания. Он работает справа налево: «возьми то, что справа от равно, и положи в коробку, которая указана слева».

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

    Во второй строчке компьютер сначала смотрит на правую часть: берет текущее значение health (100), вычитает из него 20, получает 80, и затем кладет это новое значение обратно в коробку health. Старое значение стирается навсегда.

    Ввод и вывод данных: делаем игру интерактивной

    Программа, которая просто выводит текст и завершается, скучна. Настоящая игра должна реагировать на действия пользователя. Мы уже знаем, что std::cout используется для вывода информации на экран (как мегафон). Для получения информации от пользователя с клавиатуры используется его брат-близнец — std::cin (как микрофон).

    cin расшифровывается как character input (ввод символов). Обратите внимание, что стрелки для него направлены в другую сторону >>, так как данные текут от клавиатуры внутрь нашей переменной.

    Давайте напишем простую программу — экран создания персонажа. Мы спросим у игрока его возраст и желаемый уровень сложности.

    В этом примере мы использовали цепочку вывода в 5-м шаге. С помощью нескольких операторов << можно склеивать обычный текст в кавычках и значения переменных в одно длинное сообщение.

    Если игрок введет возраст 25 и сложность 2, на экране появится: Character created! Age: 25. Difficulty level: 2

    Математика в C++

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

  • Сложение: +
  • Вычитание: -
  • Умножение: *
  • Деление: /
  • Представим ситуацию: игрок нашел сундук с золотом. У него уже было 50 монет, а в сундуке оказалось 3 мешочка по 15 монет в каждом. Как рассчитать итоговое богатство?

    Программа выведет Total gold: 95. Как и в обычной математике, в C++ соблюдается приоритет операций (умножение выполняется раньше сложения), а круглые скобки () позволяют этот приоритет изменить или сделать код более читаемым.

    Понимание того, как создавать переменные, менять их значения и выводить результат на экран — это фундамент. Любая, даже самая сложная игра вроде Cyberpunk 2077, в своей основе состоит из миллионов таких переменных (координаты пуль, здоровье врагов, скорость машин), которые постоянно пересчитываются и выводятся на экран монитора.

    2. Условия и циклы

    Условия и циклы

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

    Настоящая игра должна уметь принимать решения. Если здоровье персонажа опускается до нуля, игра должна закончиться. Если у игрока достаточно золота, он может купить меч, а если нет — торговец должен ему отказать. Чтобы научить компьютер принимать такие решения, в программировании используются условия.

    Условный оператор if

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

    Представьте, что вы стоите на развилке. Если на указателе написано «Сокровище», вы идете направо. Если нет — просто стоите на месте. В C++ это выглядит так:

    Разберем анатомию этого заклинания:

  • Сначала пишется ключевое слово if.
  • Затем в круглых скобках () пишется само условие. Условие — это всегда вопрос, на который можно ответить только «Да» (true) или «Нет» (false). В нашем случае: «Правда ли, что золота больше или равно цене меча?».
  • Если ответ «Да», компьютер заходит внутрь фигурных скобок {} и выполняет все команды по очереди. Если ответ «Нет», компьютер полностью игнорирует блок в фигурных скобках и переходит к коду, который написан после них.
  • Операторы сравнения

    Чтобы задавать вопросы в круглых скобках, нам нужны математические знаки сравнения. В C++ они немного отличаются от тех, к которым мы привыкли в школе:

    | Математика | В коде C++ | Значение | | :--- | :--- | :--- | | | a > b | Строго больше | | | a < b | Строго меньше | | | a >= b | Больше или равно | | | a <= b | Меньше или равно | | | a == b | Равно (проверка на равенство) | | | a != b | Не равно |

    > Важнейшее правило C++: один знак равно = означает присваивание (положить значение в коробку). Два знака равно == означают сравнение (проверить, одинаковые ли значения в коробках). Если вы случайно напишете if (health = 0), программа не проверит здоровье, а сделает его равным нулю, что приведет к мгновенной гибели персонажа!

    Добавляем альтернативу: else

    Конструкция if отлично работает, когда нам нужно сделать что-то только при выполнении условия. Но что, если мы хотим дать игроку обратную связь, когда золота не хватает?

    Для этого существует ключевое слово else (иначе). Оно всегда идет в паре с if и срабатывает только тогда, когда условие в if оказалось ложным (false).

    Теперь у программы есть два четких пути. Если золота 30, условие дает ответ «Нет». Компьютер пропускает первый блок и сразу переходит в блок else, выводя сообщение об отказе.

    !Блок-схема работы условного оператора if-else: программа выбирает путь в зависимости от истинности условия.

    Цепочки условий: else if

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

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

    Логические операторы: усложняем правила

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

    Для объединения условий используются логические операторы:

  • Логическое И (&&). Условие сработает, только если оба утверждения правдивы.
  • Логическое ИЛИ (||). Условие сработает, если хотя бы одно утверждение правдиво.
  • Посмотрим на пример с дверью, которую можно открыть либо ключом, либо с помощью отмычки (если хватает ловкости):

    Так как переменная agility равна 8 (что больше 5), правая часть условия правдива. Оператору || достаточно одной правды, поэтому дверь откроется, даже если ключа нет.

    Циклы: заставляем компьютер работать

    Представьте, что ваш персонаж выпил зелье регенерации, которое восстанавливает по 5 единиц здоровья 10 раз подряд. Вы могли бы написать команду health = health + 5; десять раз подряд. Но что, если зелье действует 1000 раз? Код станет огромным и нечитаемым.

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

    Цикл while (пока)

    Цикл while работает почти так же, как if, но с одним отличием: после выполнения блока кода компьютер не идет дальше, а возвращается назад к условию. И делает он это до тех пор, пока условие не станет ложным.

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

    Как это работает шаг за шагом:

  • Проверка: ? Да. Наносим 12 урона. Здоровье становится 18.
  • Конец блока. Возвращаемся к while.
  • Проверка: ? Да. Наносим 12 урона. Здоровье становится 6.
  • Конец блока. Возвращаемся к while.
  • Проверка: ? Да. Наносим 12 урона. Здоровье становится -6.
  • Конец блока. Возвращаемся к while.
  • Проверка: ? Нет. Цикл прерывается, программа идет дальше и выводит «Монстр повержен!».
  • !Визуализация работы цикла while

    > Осторожно: бесконечный цикл! Если вы забудете написать строчку monsterHealth = monsterHealth - playerDamage;, здоровье монстра всегда будет равно 30. Условие будет правдивым вечно. Программа зависнет, бесконечно выводя текст на экран. Это одна из самых частых ошибок новичков.

    Цикл for (для)

    Цикл while отлично подходит, когда мы не знаем заранее, сколько раз нужно выполнить действие (мы не знали, сколько ударов потребуется монстру). Но если мы точно знаем количество повторений, удобнее использовать цикл for.

    Например, мы хотим создать 5 врагов на уровне. Анатомия цикла for состоит из трех частей, разделенных точкой с запятой:

    Разберем эти три части в круглых скобках:

  • Старт (int i = 1): Мы создаем переменную-счетчик i (от слова index) и задаем ей начальное значение 1. Это происходит ровно один раз перед началом цикла.
  • Условие (i <= 5): Перед каждым шагом компьютер проверяет, правдиво ли это условие. Если да — выполняем код. Если нет — цикл заканчивается.
  • Шаг (i++): Эта команда выполняется в самом конце каждого круга. Запись i++ — это сокращение от i = i + 1. Она увеличивает наш счетчик на единицу.
  • В результате программа выведет на экран пять строк, подставляя вместо i числа от 1 до 5. Как только i станет равно 6, условие окажется ложным, и цикл завершится.

    Условия и циклы — это основа логики любой программы. Комбинируя проверки if и повторения while или for, вы уже можете создавать простые текстовые квесты, симуляторы сражений или викторины. В следующей статье мы узнаем, как группировать код в удобные блоки с помощью функций, чтобы не писать одно и то же по несколько раз.

    3. Функции и структура программы

    Функции и структура программы

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

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

    Чтобы избежать этого, программисты придумали функции.

    Что такое функция?

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

    Можно сравнить функцию с заклинанием в книге мага. Вы один раз тратите время на то, чтобы записать сложную формулу огненного шара на страницу. Зато потом, в бою, вам достаточно просто произнести название заклинания, и магия сработает. В C++ вызов функции — это и есть произнесение заклинания.

    Анатомия функции

    Любая функция в C++ состоит из четырех главных частей:

  • Тип возвращаемого значения — что функция отдает нам после завершения работы (например, целое число, текст или вообще ничего).
  • Имя функции — как мы будем ее вызывать. Имя должно отражать действие (например, jump, calculateDamage, showMenu).
  • Параметры — данные, которые мы передаем внутрь функции для работы (ингредиенты).
  • Тело функции — сам код, заключенный в фигурные скобки {}, который выполняется при вызове.
  • !Анатомия функции в C++

    Давайте создадим самую простую функцию, которая ничего не принимает и ничего не возвращает, а просто выводит текст.

    Обратите внимание на слово void перед именем функции. В переводе с английского это означает «пустота». Мы используем void, когда функция просто выполняет действие (например, рисует меню на экране), но не должна возвращать нам никаких математических результатов для дальнейших расчетов.

    Параметры: передаем данные внутрь

    Функции становятся по-настоящему мощными, когда мы можем передавать в них данные. Эти данные называются параметрами или аргументами.

    Представьте кузницу. Сама по себе кузница (функция) ничего не делает. Но если вы принесете туда железную руду и уголь (параметры), кузнец скует вам меч.

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

    Когда мы пишем takeDamage("Артур", 15), компьютер берет строку "Артур" и кладет ее в переменную characterName, а число 15 кладет в damageAmount. Затем он выполняет код внутри функции с этими значениями.

    Возврат значений: получаем результат

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

    Например, мы хотим рассчитать итоговый урон, который зависит от базовой силы персонажа и бонуса от его оружия. Для этого вместо void мы указываем тип данных, который хотим получить назад (например, int), а внутри функции используем ключевое слово return (вернуть).

    Как только компьютер встречает слово return, функция немедленно завершает свою работу. Любой код, написанный внутри функции после return, никогда не будет выполнен. Само выражение calculateTotalDamage(10, 5) в коде буквально превращается в число 15.

    !Интерактивная визуализация работы функции

    Область видимости переменных (Scope)

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

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

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

    Переменная potionPower рождается в тот момент, когда функция healPlayer начинает работу, и навсегда уничтожается (стирается из памяти компьютера), как только функция доходит до закрывающей фигурной скобки }.

    Глобальные переменные

    Существуют также глобальные переменные. Они создаются вне всех функций (обычно в самом верху файла) и видны из любой «комнаты» вашей программы.

    | Тип переменной | Где создается | Кто имеет к ней доступ | Время жизни | | :--- | :--- | :--- | :--- | | Локальная | Внутри функции или блока {} | Только код внутри этих же скобок | Пока выполняется функция | | Глобальная | Вне всех функций | Любая функция в файле | Все время работы программы |

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

    Структура программы: кто здесь главный?

    Вы могли заметить, что во всех наших примерах есть функция main(). Это не просто еще одна функция.

    В языке C++ main — это точка входа. Когда вы запускаете игру, компьютер всегда ищет функцию с именем main и начинает выполнять код строго с ее первой строчки. Если в вашей программе нет main, она просто не запустится.

    Еще одно важное правило C++: компьютер читает код сверху вниз.

    Если вы попытаетесь вызвать функцию в main, а саму функцию напишете ниже, компилятор выдаст ошибку: «Я не знаю такого заклинания!».

    Есть два способа решить эту проблему:

  • Писать все функции до main. Это то, что мы делали в примерах выше. Это просто, но если функций станет 50, вам придется долго листать вниз, чтобы найти главную логику программы.
  • Использовать прототипы (объявления). Вы можете написать только «заголовок» функции сверху, чтобы предупредить компьютер о ее существовании, а само тело функции написать в самом низу.
  • Пример использования прототипа:

    Функции — это кирпичики, из которых строятся сложные программы. Они позволяют разбить огромную задачу (например, «создать игру») на множество маленьких, понятных шагов («передвинуть персонажа», «рассчитать урон», «нарисовать меню»). В следующей статье мы познакомимся с массивами — способом хранить инвентарь игрока или список врагов на уровне.

    4. Массивы и работа со строками

    Массивы и работа со строками

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

    А теперь представьте, что в рюкзаке героя 100 ячеек. Создавать 100 отдельных переменных — это катастрофа. Код станет огромным, а чтобы проверить, есть ли у героя ключ, вам придется написать условие if, состоящее из ста проверок!

    Для решения таких задач в программировании существуют массивы.

    Что такое массив?

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

    Можно сравнить массив с многоквартирным домом. У дома есть один общий адрес (имя массива), но внутри него находится множество квартир. Чтобы доставить письмо в нужную квартиру, почтальону нужно знать номер этой квартиры. В программировании этот номер называется индексом.

    !Схема массива в виде поезда с пронумерованными вагонами

    Создание массива в C++

    Чтобы создать массив, нам нужно указать три вещи: тип данных, которые будут в нем храниться, имя массива и его размер (количество ячеек) в квадратных скобках [].

    В этот момент компьютер выделяет в памяти 5 пустых ячеек, идущих строго друг за другом. Мы можем сразу заполнить эти ячейки начальными значениями, используя фигурные скобки {}:

    Главное правило программиста: счет начинается с нуля

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

    > В языке C++ (как и в большинстве других языков программирования) индексация массивов всегда начинается с нуля.

    Давайте посмотрим, как распределились значения в нашем массиве highScores:

    | Индекс ячейки | Значение | Как обратиться в коде | | :--- | :--- | :--- | | 0 | 1500 | highScores[0] | | 1 | 1200 | highScores[1] | | 2 | 900 | highScores[2] | | 3 | 500 | highScores[3] | | 4 | 250 | highScores[4] |

    Обратите внимание: размер массива — 5 элементов. Но последний элемент имеет индекс 4. Элемента с индексом 5 просто не существует.

    Чтобы получить значение из массива или изменить его, мы пишем имя массива и указываем индекс в квадратных скобках:

    Опасность выхода за пределы массива

    Что произойдет, если вы попытаетесь обратиться к highScores[10]? В некоторых языках программирования программа просто выдаст понятную ошибку и остановится. Но C++ доверяет программисту на 100%.

    Компьютер послушно отсчитает 10 ячеек памяти от начала массива и прочитает то, что там находится. Проблема в том, что эта память массиву не принадлежит. Там могут храниться данные другой программы, пароли или просто системный «мусор». Ваша игра может выдать случайное число вроде -847593, а может намертво зависнуть (это называется Segmentation fault). Всегда внимательно следите за тем, чтобы индекс не превышал размер массива минус один.

    !Интерактивная визуализация работы с массивом

    Массивы и циклы: идеальная пара

    Сами по себе массивы удобны, но их настоящая мощь раскрывается при использовании вместе с циклами, особенно с циклом for.

    Поскольку индексы массива — это последовательные числа (0, 1, 2, 3...), мы можем использовать переменную-счетчик цикла в качестве индекса. Это позволяет выполнить одно и то же действие для каждого элемента массива, написав всего пару строк кода.

    Давайте вернемся к нашему инвентарю и выведем все предметы на экран:

    Если бы в инвентаре было 1000 предметов, нам пришлось бы изменить только одну цифру в условии цикла (i < 1000), и программа вывела бы их все. В этом заключается главная сила автоматизации в программировании.

    Строки — это тоже массивы

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

    Строка текста — это, по сути, массив символов (тип char).

    Когда вы создаете переменную std::string playerName = "Hero";, компьютер воспринимает ее как массив из четырех букв: ['H', 'e', 'r', 'o'].

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

    Полезные функции для работы со строками

    Тип std::string в C++ очень «умный». В него встроено множество готовых функций, которые значительно упрощают жизнь разработчику.

    1. Узнать длину строки: .length() Часто нам нужно знать, сколько символов ввел пользователь (например, чтобы проверить, достаточно ли длинный пароль). Для этого к имени переменной через точку добавляется слово length().

    2. Склеивание строк (Конкатенация) Вы можете объединять несколько строк в одну, используя обычный математический знак плюса +.

    Практический пример: Шифратор сообщений

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

    Разберем логику цикла в этом примере. Если пользователь введет слово MAGIC (длина 5):

  • Цикл начнется с i = 1 (буква 'A').
  • Условие i < 4 (так как 5 - 1 = 4).
  • Цикл заменит символы с индексами 1, 2 и 3 на звездочки.
  • Индексы 0 ('M') и 4 ('C') останутся нетронутыми.
  • Результат: M*C.
  • Массивы и строки — это фундаментальные инструменты для хранения и обработки данных. Без них невозможно создать ни инвентарь, ни список врагов на уровне, ни систему диалогов.

    5. Базовые алгоритмы и логика игр

    Базовые алгоритмы и логика игр

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

    В программировании такие механизмы называются алгоритмами.

    > Алгоритм — это четкая последовательность действий, которая приводит к решению определенной задачи.

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

    В этой статье мы разберем фундаментальные алгоритмы, на которых строится логика большинства игр: от текстовых квестов до масштабных 3D-проектов.

    Сердце любой игры: Игровой цикл

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

    Эта непрерывная работа обеспечивается игровым циклом (Game Loop). Это бесконечный цикл while, который крутится десятки раз в секунду (это и есть те самые FPS — кадры в секунду).

    Каждый виток этого цикла состоит из трех обязательных фаз:

    | Фаза | Название | Что происходит в коде | Пример из игры | | :--- | :--- | :--- | :--- | | 1 | Ввод (Input) | Программа считывает нажатия клавиш или клики мышки. | Игрок нажал клавишу «Пробел». | | 2 | Обновление (Update) | Пересчитываются координаты, здоровье, таймеры. | Координата Y персонажа увеличивается (прыжок). | | 3 | Отрисовка (Render) | Обновленные данные выводятся на экран. | На мониторе рисуется летящий персонаж. |

    Давайте напишем каркас простого текстового игрового цикла на C++:

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

    Алгоритмы поиска: Как найти нужный предмет

    Представьте, что игрок подходит к запертой двери. Чтобы ее открыть, нужен «Ржавый ключ». У игрока в инвентаре (массиве) лежит 10 предметов. Как игре понять, есть ли среди них нужный ключ?

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

    !Схема линейного поиска в инвентаре

    Команда break здесь очень важна. Если ключ лежит в первой же ячейке, нам не нужно проверять остальные 99 ячеек огромного рюкзака. Это экономит ресурсы компьютера.

    Алгоритмы сортировки: Таблица рекордов

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

    Нам нужно отсортировать массив. Существует множество алгоритмов сортировки, но мы начнем с самого классического — сортировки пузырьком (Bubble Sort).

    Идея алгоритма: мы проходим по массиву слева направо и сравниваем два соседних элемента. Если левый элемент меньше правого (а нам нужно по убыванию), мы меняем их местами. В результате за один проход самое маленькое число «всплывает» в конец массива, как пузырек воздуха в воде.

    !Интерактивная визуализация сортировки пузырьком

    Проблема обмена значений

    Прежде чем писать код сортировки, нужно понять, как поменять значения двух переменных местами. Представьте, что у вас есть стакан с соком (переменная A) и стакан с молоком (переменная B). Вы не можете просто перелить сок в молоко — они смешаются. Вам нужен третий, пустой стакан.

    В программировании этим «пустым стаканом» выступает временная переменная (обычно ее называют temp от слова temporary).

    Код сортировки пузырьком

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

    Обратите внимание на вложенные циклы. Внутренний цикл j занимается сравнением соседей. Внешний цикл i гарантирует, что мы пройдемся по массиву достаточное количество раз, чтобы отсортировать все элементы.

    Игровая логика: Проверка дистанции и столкновений

    Как игра понимает, что стрела попала в монстра, или что игрок подошел достаточно близко к торговцу, чтобы начать диалог? Все это сводится к математике и координатам.

    Представим 2D-игру с видом сверху. У каждого объекта есть координаты X (по горизонтали) и Y (по вертикали). Для простоты давайте рассмотрим одномерный мир (только линия X, как в платформере, если игрок и враг стоят на одной ровной земле).

    Чтобы узнать расстояние между игроком и врагом, нам нужно вычесть координату одного из координаты другого. Но есть нюанс: если игрок стоит на отметке 10, а враг на отметке 15, то . Расстояние не может быть отрицательным!

    Поэтому в математике и программировании используется модуль числа — абсолютное значение без знака минус. Формула дистанции выглядит так: .

    В C++ для получения модуля используется функция std::abs() из библиотеки <cmath>.

    В этом примере дистанция равна 7 (). Так как 7 больше, чем дальность атаки (3), удар не пройдет. Если игрок сделает несколько шагов вперед и его координата playerX станет равна 10, дистанция сократится до 2, и условие distance <= attackRange сработает.

    Именно на таких простых проверках условий, циклах и базовой математике строятся даже самые сложные игровые механики. Поняв эти базовые алгоритмы, вы сделали огромный шаг от написания простых строчек кода к созданию полноценных интерактивных программ.