C++ и SFML 3.0: Первая 2D-игра на Arch Linux

Практический курс для быстрого старта в разработке 2D-игр на C++ с библиотекой SFML 3.0. От установки на Arch Linux с Hyprland до готового игрового прототипа — платформера или аркады. Курс охватывает только необходимый минимум: окно, графика, события, ввод, базовая физика и столкновения.

1. Установка SFML 3.0 на Arch Linux/Hyprland и основы работы с окном и графикой

Установка SFML 3.0 на Arch Linux/Hyprland и основы работы с окном и графикой

Каждый раз, когда кто-то пишет «Hello World», мир становится чуть скучнее. А вот если вместо текстовой строки вывести на экран окно с анимированным квадратом — это уже игровой движок в миниатюре. Именно так начинается путь к собственному платформеру: с окна, в котором что-то происходит.

Зачем SFML, а не SDL или Raylib

Если вы уже знакомы с C++, то знаете: выбор библиотеки определяет, сколько боли вы получите на старте. SFML (Simple and Fast Multimedia Library) — это C++-библиотека для мультимедиа, которая предоставляет простой объектно-ориентированный интерфейс для работы с графикой, звуком, сетью и вводом. В отличие от SDL, которая написана на C и требует ручного управления ресурсами, SFML использует привычные вам классы, конструкторы и деструкторы.

> SFML — это C++ библиотека для мультимедиа, которая предоставляет простой объектно-ориентированный интерфейс для работы с графикой, звуком, сетью и вводом > > ps-group.github.io

Версия SFML 3.0 — это мажорный релиз, который принёс поддержку C++17, обновлённый API и исправления для Wayland. Именно Wayland и Hyprland — ваша среда, поэтому важно установить именно эту версию, а не устаревшую 2.x.

Установка на Arch Linux

На Arch Linux SFML доступна в официальных репозиториях. Откройте терминал и выполните:

Это установит библиотеку и заголовочные файлы. Проверьте версию:

Если версия ниже 3.0 — обновите систему командой sudo pacman -Syu. На момент написания курса Arch уже поставляет SFML 3.0 в репозиториях.

Для компиляции вам также понадобятся CMake и компилятор GCC:

Особенности Hyprland

Hyprland — это композитор на базе Wayland. Композитор — это программа, которая управляет отрисовкой окон на экране. SFML 3.0 поддерживает Wayland нативно, но по умолчанию может выбрать X11-backend. Чтобы принудительно использовать Wayland, установите переменную окружения:

Если окно SFML не появляется или мигает — попробуйте запустить с SFML_WINDOW_BACKEND=x11 как запасной вариант. На практике большинство проектов SFML 3.0 работают на Hyprland без дополнительной настройки.

Первое окно: разбор по строкам

Создайте файл main.cpp и вставьте следующий код:

Разберём ключевые моменты. sf::RenderWindow — это класс, создающий графическое окно. Конструктор принимает VideoMode (размер окна в пикселях) и строку заголовка. Метод setFramerateLimit(60) ограничивает частоту кадров до 60 FPS — без этого цикл будет крутиться на максимум и нагрузит процессор.

В SFML 3.0 изменился способ обработки событий: pollEvent() теперь возвращает std::optional<sf::Event>, а не bool. Это означает, что вы проверяете наличие события через while (const std::optional event = window.pollEvent()), а тип события определяете методом is<T>().

Микропример

Представьте, что окно — это холст, а window.clear() — это грунтовка перед каждой новой картинкой. Если не вызвать clear() перед отрисовкой, предыдущий кадр останется на экране — получится эффект «шлейфа».

Рисуем фигуры

SFML предоставляет готовые геометрические примитивы. Добавьте прямоугольник в игровой цикл:

Затем в теле цикла, между clear() и display():

RectangleShape — это прямоугольник, заданный размером в пикселях. Метод setPosition задаёт координаты верхнего левого угла. SFML использует экранные координаты: ось X направлена вправо, ось Y — вниз. Это важно запомнить, потому что в физике и математике Y обычно направлена вверх.

Помимо прямоугольников, SFML поддерживает:

  • sf::CircleShape — круг с заданным радиусом и количеством точек аппроксимации
  • sf::ConvexShape — произвольный выпуклый многоугольник
  • sf::Vertex и sf::VertexArray — отдельные точки и их массивы для произвольной геометрии
  • Система сборки через CMake

    Чтобы проект компилировался на любом компьютере с Arch Linux, создайте файл CMakeLists.txt:

    Сборка выполняется в три команды:

    После этого запустите ./game — должно появиться окно 800×600 с зелёным прямоугольником на тёмно-сером фоне.

    Координаты и цвета

    В SFML цвет задаётся через sf::Color, принимающий четыре компонента: красный, зелёный, синий и альфа-канал (прозрачность), каждый в диапазоне . Например, sf::Color(255, 100, 0, 200) — оранжевый с лёгкой прозрачностью.

    Координаты объектов — это sf::Vector2f, пара чисел с плавающей точкой (x, y). Если вы захотите передвигать объект, достаточно вызывать setPosition каждый кадр с новыми координатами. Именно так устроено движение в любой 2D-игре.

    Если из этой главы запомнить три вещи — это:

  • SFML 3.0 на Arch Linux ставится одной командой pacman -S sfml и работает на Hyprland через Wayland
  • Основной цикл игры: pollEvent()clear()draw()display() — повторяется каждый кадр
  • Все объекты на экране (прямоугольники, круги, спрайты) рисуются через window.draw() между очисткой и отображением буфера
  • 2. Обработка событий и ввод с клавиатуры/мыши в SFML 3.0

    Обработка событий и ввод с клавиатуры/мыши в SFML 3.0

    Представьте аркадный автомат: стоит в углу зала, на экране — космический корабль, и единственный способ с ним взаимодействовать — нажимать кнопки. Если автомат не реагирует на нажатия, это не игра, а скринсейвер. Ровно поэтому обработка событий — это первый шаг от «картинки» к «игре».

    Что такое событие в SFML

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

    В SFML 3.0 цикл обработки событий выглядит так:

    Ключевое отличие от SFML 2.x: pollEvent() возвращает std::optional<sf::Event>. Если очередь пуста — возвращается std::nullopt, и цикл завершается. Если событие есть — вы получаете объект, тип которого определяете через шаблонный метод is<T>().

    Клавиатура: нажатие и удержание

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

    Метод getIf<T>() — это способ извлечь конкретный тип события из std::optional. Если событие не того типа, возвращается nullptr.

    Второй способ — опрос состояния через sf::Keyboard::isKeyPressed(). Этот метод проверяет, зажата ли клавиша прямо сейчас, независимо от событий. Он подходит для непрерывного движения:

    Когда какой способ использовать

    | Ситуация | Способ | Пример | |----------|--------|--------| | Однократное действие по нажатию | Событие KeyPressed | Прыжок, выстрел, пауза | | Непрерывное действие при удержании | isKeyPressed() | Движение влево/вправо | | Реакция на отпускание клавиши | Событие KeyReleased | Прекращение бега |

    Микропример: если вы играете в платформер, нажатие пробела должно вызывать прыжок один раз — для этого нужен KeyPressed. А движение вбок должно продолжаться, пока клавиша зажата — для этого isKeyPressed().

    Мышь: позиция и кнопки

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

    Координаты возвращаются относительно окна. Если нужно координаты в мировом пространстве (при использовании sf::View), используйте window.mapPixelToCoords(mousePos).

    События мыши обрабатываются так же, как события клавиатуры:

    Обратите внимание: в SFML 3.0 поле позиции мыши в событии называется position, а не x/y как в версии 2.x.

    Практический пример: движущийся квадрат

    Соберём всё вместе. Создадим программу, в которой зелёный квадрат движется по нажатию клавиш WASD:

    Зачем нужен delta time

    Переменная dt — это delta time, время между кадрами в секундах. sf::Clock замеряет интервал с момента последнего вызова restart(). Умножая скорость на dt, вы получаете движение, которое не зависит от частоты кадров: квадрат проходит одинаковое расстояние за одну секунду, работает ли программа при 30 FPS или при 144 FPS.

    Без delta time квадрат на мониторе 144 Гц двигался бы в 4,8 раза быстрее, чем на 30 Гц. Именно поэтому speed * dt — это обязательный паттерн для любой игры.

    Событие изменения размера окна

    Если пользователь растянет окно, содержимое может исказиться. Чтобы этого избежать, обрабатывайте событие Resized:

    Это обновляет view — виртуальную камеру — так, чтобы координатная сетка растягивалась вместе с окном. Без этого обработчика графика будет масштабироваться некорректно.

    Типичные ошибки новичков

    Первая ошибка — забыть обработать Closed, тогда окно не закроется по крестику и процесс повиснет. Вторая — использовать события для движения вместо isKeyPressed(): если вы нажмёте клавишу и отпустите за один кадр (при низком FPS), персонаж не сдвинется. Третья — не умножать на delta time, из-за чего скорость привязана к железу.

    Если из этой главы запомнить три вещи — это:

  • pollEvent() в SFML 3.0 возвращает std::optional — проверяйте наличие события перед извлечением типа через getIf<T>()
  • Однократные действия (прыжок, выстрел) обрабатывайте через события KeyPressed, а непрерывное движение — через sf::Keyboard::isKeyPressed() вне цикла событий
  • Delta time через sf::Clock::restart().asSeconds() — обязательный элемент: без него скорость объектов зависит от частоты кадров
  • 3. Создание простой 2D-игры: спрайты, анимация и игровой цикл

    Создание простой 2D-игры: спрайты, анимация и игровой цикл

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

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

    Игровой цикл (game loop) — это бесконечный цикл, который выполняет три задачи в каждой итерации: обработку ввода, обновление игровой логики и отрисовку. Вы уже видели его в предыдущей главе, но теперь структурируем явно:

    Разделение на три фазы — не просто формальность. Оно позволяет независимо менять логику отрисовки, добавлять паузу (пропуск update при render) и тестировать каждую часть отдельно.

    > SFML (Simple Fast Multimedia Library) — это C++ библиотека для мультимедиа > > ps-group.github.io

    Спрайты и текстуры

    Текстура (texture) — это изображение, загруженное в видеопамять видеокарты. Спрайт (sprite) — это объект, который ссылается на текстуру и определяет, какую её часть и где на экране рисовать.

    Текстура загружается один раз и живёт в видеопамяти. Спрайтов может быть много — все они могут ссылаться на одну текстуру, но показывать разные её области. Это ключевой принцип текстурных атласов (sprite sheets): все кадры анимации хранятся в одном файле-сетке, а спрайт переключается между областями.

    TextureRect: выбор области текстуры

    Метод setTextureRect задаёт прямоугольную область текстуры, которую будет показывать спрайт:

    sf::IntRect принимает позицию верхнего левого угла и размер области в пикселях. Именно так выбирается конкретный кадр из sprite sheet.

    Покадровая анимация

    Анимация в 2D-играх — это последовательное переключение TextureRect через равные интервалы времени. Каждый кадр анимации — это отдельный прямоугольник на текстуре.

    Создадим класс Animation, который будет управлять этим процессом:

    Конструктор получает ссылку на спрайт, размер одного кадра, количество кадров и общую длительность цикла анимации в секундах. Метод update вызывается каждый кадр: если прошло достаточно времени, он переключает TextureRect на следующий кадр. Оператор % обеспечивает зацикливание — после последнего кадра анимация возвращается к первому.

    Микропример: если duration = 0.5f, frameCount = 6, то каждый кадр показывается секунды — это примерно 12 кадров в секунду, типичная скорость для спрайтовой анимации в ретро-играх.

    Класс Player: собираем вместе

    Объединим движение, анимацию и спрайт в класс игрока. Как отмечают разработчики на dtf.ru, класс Player должен отвечать за перемещение персонажа в пределах границ окна, анимацию в зависимости от направления и управление состоянием жизни/смерти.

    Обратите внимание на порядок вызовов в игровом цикле: сначала handleInput обновляет позицию, потом update переключает анимацию, потом draw выводит спрайт на экран.

    Загрузка текстур: менеджер ресурсов

    Одна из частых ошибок — загружать текстуру каждый раз, когда создаётся объект. Текстура должна загружаться один раз и передаваться по ссылке или указателю. Для небольшого проекта достаточно хранить текстуры в std::map:

    Это простейший менеджер ресурсов: при первом запросе текстура загружается с диска, при повторном — возвращается из кэша.Видеопамять не расходуется впустую, и нет риска загрузить одну и ту же картинку десять раз.

    Слои отрисовки

    В платформере фон рисуется первым, затем блоки, затем персонаж, затем интерфейс. SFML не имеет встроенного механизма слоёв, но порядок вызовов window.draw() и определяет порядок наложения. Рисуйте объекты от дальнего плана к ближнему:

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

    Если из этой главы запомнить три вещи — это:

  • Игровой цикл состоит из трёх фаз: handleInputupdate(dt)draw() — и этот порядок критически важен
  • Анимация — это переключение TextureRect через равные интервалы времени; все кадры хранятся в одном текстурном атласе
  • Текстуры загружаются один раз и передаются по ссылке — никогда не загружайте одну и ту же текстуру повторно
  • 4. Физика, столкновения и итоговый проект — сборка платформера

    Физика, столкновения и итоговый проект — сборка платформера

    Когда Марио прыгает на врага, происходит не магия, а арифметика: координаты двух прямоугольников сравниваются, и если они пересекаются — враг исчезает, а игрок отскакивает. Вся физика платформера сводится к трём вещам: гравитация, движение и проверка столкновений (collision detection). Разберём каждую и соберём рабочий проект.

    Гравитация: постоянное ускорение вниз

    В реальном мире гравитация — это ускорение . В играх используют упрощённую модель: каждый кадр к вертикальной скорости персонажа прибавляется константа. Если vy — вертикальная скорость, а gravity — ускорение свободного падения в пикселях, то:

    Значение gravity подбирается экспериментально. Для платформера типичные значения лежат в диапазоне от 500 до 1500 пикселей в секунду в квадрате. Меньше — персонаж «лунный», больше — падает мгновенно.

    Прыжок реализуется однократным заданием отрицательной скорости (помните: ось Y направлена вниз, поэтому отрицательная скорость — это движение вверх):

    Переменная onGround предотвращает двойной прыжок: пока персонаж не коснётся земли, повторный прыжок невозможен.

    Проверка столкновений: AABB

    Самый распространённый метод в 2D-играх — AABB (Axis-Aligned Bounding Box), проверка пересечения осевонаправленных ограничивающих прямоугольников. Два прямоугольника пересекаются, если они пересекаются одновременно по обеим осям.

    Как отмечают в руководстве по созданию платформера, каждый графический объект SFML имеет метод getGlobalBounds(), возвращающий глобальный ограничивающий прямоугольник, а метод intersects() проверяет пересечение двух таких прямоугольников.

    В SFML 3.0 метод называется findIntersection и возвращает std::optional<sf::FloatRect>, содержащий область пересечения. Если пересечения нет — возвращается std::nullopt.

    Разрешение столкновений

    Знать, что столкновение произошло — недостаточно. Нужно разрешить его: вытолкнуть персонажа из стены. Для этого определяем, с какой стороны произошло пересечение, по размеру области пересечения:

    Микропример: представьте, что вы идёте и упираетесь в стену. Если область пересечения узкая по горизонтали, но высокая по вертикали — вы подошли к стене сбоку. Если наоборот — вы наступили на платформу сверху.

    Структура проекта платформера

    Финальный проект состоит из нескольких файлов. Вот структура каталога:

    CMakeLists.txt

    Если окно не появляется, проверьте, что SFML 3.0 установлена: pacman -Qi sfml. Если текстура не загружается — убедитесь, что путь assets/player.png корректен относительно рабочей директории.

    Что дальше

    Этот проект — каркас. Следующие шаги для самостоятельного развития:

  • Добавьте анимацию: замените статический спрайт на sprite sheet с классом Animation из предыдущей главы
  • Добавьте врагов: создайте класс Enemy с простым ИИ — движение влево-вправо до стены с разворотом
  • Добавьте камеры: используйте sf::View, которая следует за игроком, чтобы уровень был больше экрана
  • Добавьте звук: sf::SoundBuffer и sf::Sound для прыжка, сбора монет, столкновений
  • Для более сложной физики рассмотрите библиотеку Box2D — как показано в практическом руководстве по созданию платформера, Box2D берёт на себя гравитацию, столкновения и физические взаимодействия, позволяя сосредоточиться на геймплее.

    Если из этой главы запомнить три вещи — это:

  • Гравитация в платформере — это прибавка константы к вертикальной скорости каждый кадр, а прыжок — однократное задание отрицательной скорости
  • AABB-столкновения проверяются через findIntersection в SFML 3.0, а разрешаются выталкиванием объекта по той оси, где пересечение меньше
  • Тайловая карта — это двумерный массив чисел, где каждое число обозначает тип тайла; столкновения проверяются только с ближайшими тайлами, а не со всей картой