Спрайт-лист (sprite sheet) это одно изображение, в котором лежит много маленьких. Текстурный атлас это то же самое, только рядом лежит файл данных, где для каждого кадра записано имя, положение, размер, а иногда и то, был ли кадр обрезан или повёрнут. На практике оба слова означают одно и то же, а каждый движок и каждый упаковщик называет это немного по-своему.
В этом руководстве мы упакуем спрайт-лист бесплатными инструментами, разберём настройки, от которых зависит, будет ли он правильно выглядеть в игре, загрузим результат в Phaser 3, Unity, Godot и обычный CSS и пройдёмся по самым частым ошибкам. В конце есть короткий раздел для тех, кто продаёт наборы спрайтов.
Зачем вообще упаковывать спрайты
Причина одна на всех платформах: меньше отдельных объектов, с которыми приходится возиться видеокарте или браузеру.
- Меньше вызовов отрисовки (draw calls) в игре. Рендерер обычно умеет объединять спрайты с общей текстурой в один вызов. Если каждый кадр каждого персонажа это отдельное изображение, рендереру приходится постоянно переключать текстуры, и батчинг ломается. Положите кадры в один атлас, и десятки спрайтов уйдут одним пакетом.
- Меньше запросов в вебе. Сорок маленьких иконок отдельными файлами это сорок запросов. Один спрайт-лист это один запрос и одно декодирование. HTTP/2 удешевил множество мелких запросов, но одно изображение по-прежнему проще кешировать и подгружать заранее.
- Меньше потерянной памяти. Старые GPU и некоторые мобильные драйверы округляют текстуры до ближайшей степени двойки. Картинка 130 на 70 пикселей может занять в памяти 256 на 128. Упаковка многих изображений в один атлас подходящего размера тратит гораздо меньше.
- Проще управлять ассетами. Одну пару файлов (картинка плюс данные) проще версионировать, отгружать и загружать, чем папку из сотен кадров.
Упаковка нужна не всегда. Одиночный фон или и без того огромный спрайт от атласа ничего не выигрывают. Выигрыш появляется, когда много маленьких изображений рисуются вместе.
Бесплатные инструменты для упаковки
Чтобы получить хороший атлас, платить не обязательно. В таблице перечислены инструменты из этой статьи. «Бесплатно» значит, что рабочий атлас можно собрать без покупки лицензии; у некоторых есть платные уровни с дополнительными функциями.
| Инструмент | С чем работает | Типичный результат | Стоимость |
|---|---|---|---|
| TexturePacker | Phaser, Pixi, Unity, Godot, Cocos, CSS и многое другое | PNG + JSON, XML, файлы под конкретный движок | Бесплатный базовый режим, платные функции Pro |
| Free Texture Packer (free-tex-packer) | Phaser, Pixi, общий JSON, XML, CSS | PNG + JSON hash или array, XML | Бесплатно, открытый код |
| Aseprite | Любой движок, читающий JSON с кадрами | PNG + JSON hash или array | Платно, исходники доступны для самостоятельной сборки |
| Unity Sprite Atlas | Только Unity | Внутренний атлас, собираемый при сборке или в режиме игры | Встроен в Unity |
| Godot AtlasTexture | Только Godot | Ресурс, указывающий на область текстуры | Встроен в Godot |
| CSS-спрайт вручную | Браузеры | Один PNG или SVG + CSS background-position | Бесплатно |
Если запомнить из таблицы что-то одно: когда цель это JavaScript-движок вроде Phaser или Pixi, Free Texture Packer бесплатно покрывает почти всё. Когда вы работаете в Unity или Godot, сначала используйте встроенную систему атласов, а внешний упаковщик берите, только если нужно то, чего она не умеет.
Пошагово: упаковка в Free Texture Packer
Free Texture Packer это настольное приложение (есть и веб-версия), которое берёт папку изображений и записывает атлас. Для веб-игр это хороший выбор по умолчанию: в нём есть пресеты экспорта под распространённые JavaScript-движки.
- Подготовьте кадры. Экспортируйте каждый кадр отдельным PNG с прозрачностью. Называйте так, чтобы они правильно сортировались:
run_00.png,run_01.pngи так далее. Ведущие нули важны: без нихrun_10встанет раньшеrun_2. - Добавьте папку. Перетащите папку в приложение. Имена подпапок обычно становятся частью имени кадра, что удобно для группировки (
hero/run_00.png). - Выберите формат экспорта. Для Phaser 3 выберите пресет Phaser 3 или JSON hash. JSON array тоже подойдёт; разница описана ниже.
- Задайте padding и extrude. Начните с 2 пикселей отступа и 1 пикселя выдавливания. Почему, объясняется в разделе о настройках.
- Решите насчёт обрезки и поворота. Обрезка (trim) удаляет прозрачные края и экономит место. Поворот позволяет упаковщику разворачивать кадры на 90 градусов, чтобы уместить больше. Оставьте поворот выключенным, если не уверены, что движок поддерживает повёрнутые кадры в этом формате.
- Задайте максимальный размер. 2048 на 2048 это безопасный потолок для большинства платформ, включая телефоны. Если кадры не помещаются, разбейте их на несколько атласов, а не поднимайте лимит вслепую.
- Экспортируйте. Вы получите изображение и файл данных с одинаковым базовым именем. Держите их вместе.
TexturePacker устроен так же: добавляете спрайты, выбираете формат данных из длинного списка фреймворков, настраиваете отступы и обрезку, публикуете. Некоторые продвинутые опции помечены как Pro, и приложение предупредит, если выбранная настройка требует лицензии. Для обычного атласа PNG плюс JSON бесплатного режима достаточно.
Экспорт из Aseprite
Если вы рисуете пиксель-арт в Aseprite, во многих проектах отдельный упаковщик не нужен. Используйте File, Export Sprite Sheet. В диалоге выбирается раскладка (по строкам, по столбцам, плотная упаковка), какие слои и теги включать, отступы по краю и между фигурами и нужна ли обрезка. На вкладке вывода можно записать рядом с изображением JSON-файл данных в виде hash или array.
В JSON попадают и теги анимаций с диапазонами кадров, и длительность каждого кадра, заданная на таймлайне. Некоторые движки и плагины умеют читать их напрямую, и вам не придётся вбивать номера кадров вручную. При работе с несколькими файлами часто делают так: каждого персонажа экспортируют из Aseprite отдельными кадрами или полосой, а потом упаковывают всё вместе общим упаковщиком, чтобы вся игра использовала меньше текстур.
Настройки, которые действительно важны
Большинство визуальных багов с атласами происходят из четырёх-пяти настроек. Правильно выставить их и есть основное умение.
Padding и extrude
Когда GPU читает текстуру с включённой фильтрацией, он смешивает соседние пиксели. На краю кадра соседом может оказаться следующий кадр в атласе. В результате на границе спрайта появляется тонкая линия чужого цвета, часто заметная только при движении камеры или масштабировании спрайта. Это называется bleeding, «протекание».
Padding добавляет пустое пространство между кадрами, чтобы смешиваться было не с чем. Extrude повторяет крайний ряд пикселей каждого кадра наружу, так что если сэмплер всё же выйдет за границу, он возьмёт цвет самого кадра. Для тайлов, которые должны стыковаться вплотную, именно extrude убирает видимые швы. Два пикселя отступа и один пиксель extrude это разумная отправная точка; увеличьте их, если используете mipmap-уровни или сильно уменьшаете спрайты.
Обрезка (trim)
Обрезка срезает прозрачные края и записывает в файл данных исходный размер и смещение. Движок затем ставит обрезанный кадр туда, где он должен быть. Для персонажей с пустым пространством вокруг это экономит много места. Подвох: если движок или загрузчик игнорирует данные обрезки, анимация будет дрожать, потому что все кадры разного размера. Phaser, Pixi и распространённые упаковщики корректно обрабатывают это в форматах JSON hash и array. Если спрайт трясётся на месте, первым делом проверьте обрезку.
Поворот
Если разрешить упаковщику поворачивать кадры на 90 градусов, на тот же лист поместится больше. Движок должен поворачивать их обратно при отрисовке, а это умеет не каждый загрузчик. Если кадры появляются боком, выключите поворот. Сэкономленное место редко стоит отладки.
Степени двойки и максимальный размер
Современные настольные GPU нормально работают с текстурами не кратными степени двойки, WebGL 2 тоже. Размеры степени двойки (512, 1024, 2048, 4096) остаются безопасным выбором, когда нужны mipmap-уровни на старых устройствах, когда вы используете сжатые форматы текстур, которые их ожидают, или когда вы не уверены в железе. По максимальному размеру: 2048 на 2048 поддерживается почти везде. 4096 на 4096 работает на большинстве современных устройств, но на слабых телефонах может долго загружаться или вовсе не приниматься. Если игра для мобильных, держите атласы на 2048 и используйте несколько.
Пиксель-арт и фильтрация
Пиксель-арт нужно рисовать с выборкой по ближайшему соседу (nearest), а не с линейной фильтрацией, иначе он размоется. В Phaser поставьте pixelArt: true в конфиге игры. В Unity выставьте у текстуры Filter Mode в Point и отключите сжатие для небольших листов. В Godot установите фильтр текстуры Nearest на узле или в настройках проекта. С выборкой nearest протекание встречается реже, но отступы всё равно помогают, когда спрайты стоят на дробных координатах.
Форматы экспорта: JSON hash, JSON array, XML
Изображение почти всегда PNG. Различаются форматы файла данных.
- JSON hash хранит кадры как объект с ключом по имени кадра:
"frames": { "run_00.png": { ... } }. Удобно искать по имени. - JSON array хранит кадры списком, у каждого есть поле
filename. Порядок гарантирован, и некоторые инструменты это предпочитают. - XML, например в стиле Starling / Sparrow, перечисляет каждую подтекстуру с атрибутами x, y, width и height. Phaser умеет загружать этот стиль через
load.atlasXML.
Phaser 3 читает и JSON hash, и JSON array одним и тем же вызовом load.atlas, так что для Phaser выбор почти не важен. Выберите один и придерживайтесь его во всём проекте.
Загрузка атласа в Phaser 3
Вот минимальная сцена, которая загружает атлас, ставит из него спрайт и собирает анимацию бега из пронумерованных кадров.
class GameScene extends Phaser.Scene {
preload() {
// image and data file exported by the packer
this.load.atlas('hero', 'assets/hero.png', 'assets/hero.json');
}
create() {
this.anims.create({
key: 'run',
frames: this.anims.generateFrameNames('hero', {
prefix: 'run_',
start: 0,
end: 7,
zeroPad: 2,
suffix: '.png'
}),
frameRate: 12,
repeat: -1
});
const player = this.add.sprite(200, 300, 'hero', 'run_00.png');
player.play('run');
}
}
const config = {
type: Phaser.AUTO,
width: 800,
height: 600,
pixelArt: true,
scene: GameScene
};
new Phaser.Game(config);
Имена кадров в generateFrameNames должны точно совпадать с именами в JSON, включая расширение, если упаковщик его оставил. Откройте JSON хотя бы раз и посмотрите: большинство предупреждений о «пропавшем кадре» возникают из-за несовпадающего префикса или суффикса.
Unity Sprite Atlas и Godot AtlasTexture
В Unity создайте Sprite Atlas через меню Create в разделе 2D, затем перетащите папки или отдельные спрайты в его список объектов для упаковки. В инспекторе атласа есть отступ (padding), опция разрешения поворота и опция плотной упаковки. Плотная упаковка (tight packing) идёт по контуру спрайта, а не по его прямоугольнику, и экономит место, но может обрезать спрайты необычной формы, так что проверьте результат. Спрайты сохраняют свои ссылки в сценах и префабах; Unity подставляет атлас при сборке. Убедитесь, что упаковка спрайтов включена в настройках редактора проекта, иначе в режиме игры эффекта не будет видно.
В Godot AtlasTexture это ресурс, который указывает на прямоугольную область другой текстуры. Его можно создать в инспекторе, назначить большой лист как atlas и задать region. Для анимированных персонажей редактор SpriteFrames, которым пользуется AnimatedSprite2D, умеет нарезать лист сеткой и сразу добавить кадры, что часто быстрее, чем создавать много ресурсов AtlasTexture вручную. Для тайловых карт редактор TileSet работает прямо с листом и имеет свои настройки separation и margin.
CSS-спрайты для веба
Для иконок на сайте спрайт это одно изображение со всеми иконками и CSS, который показывает нужную часть. Многие упаковщики умеют экспортировать таблицу стилей; её можно написать и вручную.
.icon {
width: 32px;
height: 32px;
background-image: url('/img/icons.png');
background-repeat: no-repeat;
}
.icon-cart { background-position: 0 0; }
.icon-search { background-position: -32px 0; }
.icon-user { background-position: -64px 0; }
Для экранов высокой плотности экспортируйте лист в два раза больше и задайте background-size в исходных размерах, чтобы каждая иконка оставалась чёткой. Для простых одноцветных иконок SVG-спрайт с элементами <symbol> и <use> часто лучше PNG, потому что масштабируется без дополнительных файлов.
Частые ошибки
- Тонкие линии по краям спрайтов. Протекание из-за фильтрации. Добавьте padding и extrude или переведите пиксель-арт на выборку nearest.
- Анимация дрожит на месте. Обрезанные кадры, а загрузчик не читает смещения обрезки, или кадры экспортированы с разным размером холста. Держите один холст на анимацию или убедитесь, что данные обрезки используются.
- Кадры повёрнуты. Упаковщик их повернул, а загрузчик не поворачивает обратно. Выключите поворот.
- Атлас работает на компьютере, а на телефоне пусто. Лист больше максимального размера текстуры устройства. Разбейте на листы 2048.
- Предупреждения «frame missing». Имена не совпадают из-за префикса, суффикса, подпапки или отсутствия ведущих нулей.
- Переупаковка всё ломает. Позиции на листе сдвигаются, когда вы добавляете кадр. Всегда загружайте кадры по имени, а не по зашитым координатам, и следующая переупаковка ничего не сломает.
- Один гигантский атлас на всю игру. Группировка по уровням или экранам снижает расход памяти: загружается только то, что нужно текущей сцене.
Если вы продаёте наборы спрайтов
Покупатели работают в разных движках и с разными упаковщиками, поэтому самый полезный набор подходит всем. Несколько привычек делают набор проще для покупки и использования:
- Отгружайте обе формы. Положите отдельные кадры как самостоятельные PNG-файлы и упакованный атлас с JSON. Тем, кто переупаковывает со своими настройками, нужны отдельные кадры; тем, кто хочет просто вставить и работать, нужен атлас.
- Укажите факты в описании. Размер кадра в пикселях, число кадров в каждой анимации, padding и extrude в атласе, обрезаны ли кадры, формат данных (JSON hash, JSON array, XML) и частота кадров, под которую вы рисовали.
- Держите имена предсказуемыми. Ведущие нули, нижний регистр, один префикс на анимацию. Покупатели будут писать код под эти имена.
- Добавьте превью. Анимированный GIF или короткое видео каждой анимации отвечает на главный вопрос ещё до покупки.
- Чётко опишите лицензию. Скажите, можно ли использовать набор в коммерческих играх.
Если хотите посмотреть, как другие авторы оформляют свои наборы, или выставить свой на продажу, категория 2D-спрайтов на Getly это одно из мест, где продаются наборы спрайтов.
Упаковка спрайт-листа занимает несколько минут, когда понятны настройки: единообразные имена кадров, немного padding и extrude, аккуратная обрезка, никакого поворота, если вы не уверены в загрузчике, и размер атласа, который принимает целевое железо. Сделайте это правильно, и атлас уйдёт на задний план, где ему и место.


