Arkanoid и SDL. Управление спрайтами

Продолжаем создавать игры с использованием библиотеки SDL. На этот раз обсудим вариант управления спрайтами на примере игры Arkanoid.
При реализации более-менее сложного проекта, очень помогает разбиение общей задачи на подзадачи. Следовательно, код удобно разбить на серию модулей по назначению, что и было сделано.
Все необходимые графические файлы можно позаимствовать на общедоступных ресурсах: https://opengameart.org/content/starfield-background — фон; https://opengameart.org/sites/default/files/breakout_pieces_2.png — остальные детали.
Структура и описание
Итак, в процессе определения разных задач игры, у меня получился такой список модулей:
- main.cpp — основной модуль
- Ball.cpp — расчет и отрисовка мяча
- Block.cpp — отрисовка отдельного блока
- Collision.cpp — алгоритмы определения столкновений разных объектов сцены
- Player.cpp — все, что связано с игроком (двигающейся платформой). Обработка событий клавиатуры, отрисовка платформы и т.д.
- Scene.cpp — алгоритмы обработки игрового процесса в целом
- SpriteBank.cpp — модуль для формирования базы спрайтов из набора графических изображений
- SpriteString.cpp — модуль для вывода текстовых сообщений
Для каждого исходника существует соответствующий заголовочный файл с необходимым описанием типов и структур данных.
Для внесения ясности скажу, что старался отделить работу с графикой от игровой логики и «физики». Ничего особенного в этом нет, так как это общий принцип. Подробно следует рассказать только о SpriteBank.
SpriteBank
Для себя я решил, что хорошо было бы сгруппировать спрайты в наборы по смыслу. Допустим, в нулевом наборе будут фоны уровней, а номер фона будет соответствовать номеру уровня. В первом наборе сосредоточены спрайты анимации персонажа в естественном порядке их следования. Во втором объединены все статичные объекты сцены и т.д. Это удобно, так как программист не задумывается о том как и из каких источников будет сформирован контент. Есть только номер набора картинок и назначение этого набора.
Желательно иметь возможность формировать такие множества любым способом (брать спрайты из разных графических файлов, использовать один и тот же спрайт в разных наборах, менять порядок следования и т.д.).
Набор спрайтов
Как решить такую задачу? Можно загрузить нужные изображения в память, пронумеровав их. Затем описать ряд прямоугольных областей из загруженных картинок. Для описания такой области достаточно указать номер области, номер изображения, координаты и размеры прямоугольника.
Подмножества (наборы)
Когда набор всех нужных спрайтов (прямоугольных частей исходных изображений) описан, можно приступить к описанию групп этих спрайтов. Для каждой группы понадобиться следующая информация: номер группы, количество спрайтов в ней, упорядоченный набор пар <номер спрайта>-<номер изображения>.
test.set
Всё вышеописанное храниться в файле test.set, который есть в архиве с исходниками. Но для понимания мы рассмотрим наиболее простой пример такого файла:
0 3 0 crypt.png 1 ../Tilesets/breakout_pieces.png 2 bg_1_1.png 5 0 0 0 0 64 64 1 0 64 0 64 64 2 1 48 8 32 16 3 1 84 8 32 16 4 2 0 0 640 480 3 0 2 0 0 1 0 1 2 2 1 3 1 2 1 4 2
Как следует воспринимать написанное? Первое число (0) — это идентификатор базы спрайтов. На тот случай, если баз будет несколько. Затем указано количество файлов-изображений (их 3). Первый файл имеет идентификатор 0 и название «crypt.png«, а последний — 2 и название «bg_1_1.png«. Затем указано общее количество спрайтов в базе (5) и соответствующее описание каждого спрайта. Например, строка «2 1 48 8 32 16» говорит нам, что спрайт под номером 2 взят как прямоугольная область картинки «../Tilesets/breakout_pieces.png» с координатами x:48, y:8, шириной 32 пикселя, высотой 16 пикселей.
А теперь из этих пяти спрайтов сформируем наборы. Тут всего три набора. Первый имеет номер 0, содержит 2 спрайта (0 и 1 из crypt.png, на что указывает строка «0 0 1 0»). Остальные два записаны в аналогичном формате.
В данном случае может показаться избыточным номер источника спрайта. Но такой шаг оправдан, если номера спрайтов уникальны только в рамках одного источника.
Таким образом, программисту для отображения игрового мира достаточно определиться с назначением подмножеств (наборов) и смыслом порядкового номера элемента в каждом подмножестве. К тому же, замена каких-либо спрайтов может осуществляться простым редактированием файла test.set.
SpriteString
Практически во всех играх присутствует хоть какой-то текст. Кроме того, выдавать сообщения игроку хотелось бы с помощью красивого, стилистически подходящего шрифта. Такой шрифт можно легко приспособить к проекту, если он существует в виде графического файла с изображением символов. В таком случае делаем каждый символ отдельным спрайтом, формируем соответствующий набор, сопоставляем его со строкой-алфавитом и создаем функцию отображения текста. Всё это реализовано в модуле SpriteString.
В общем и целом
Чем ценен данный проект? А тем, что SpriteBank можно использовать в своих целях, как и всё остальное в нём. На полноценную игру эта версия Arkanoid не претендует, так как весь игровой процесс сводится к уничтожению случайным образом созданного поля из кирпичей. У игрока есть три попытки пройти уровень. За каждый уничтоженный кирпич начисляются очки. Вот, собственно, и всё.
Исходники довольно легко доработать, если есть желание.
Добавить комментарий