Геймдев: генерация анимированных игровых спрайтов с помощью GPT 5.4 и Image 1.5
Недавно я выложил твит, где показал, как сделал анимированного пиксельного персонажа для пиратской игры.
С тех пор я немного продвинулся и улучшил процесс генерации.
Анимированный ассет для игры про пиратов, созданный в GPT 5.4 и Image 1.5.

Тема вызвала интерес (спасибо!), и посыпались вопросы о том, как устроен процесс. Вместо бесконечных тредов я решил протестировать статьи в X (X articles), чтобы наглядно всё показать.
Дисклеймер: Подход пока экспериментальный: я всё ещё ищу лучший способ добиваться крутых результатов. Относитесь к этому тексту как к черновику с идеями, а не как к ультимативному гайду.
Поехали!
Как устроен процесс
Суть проста:
- берем один готовый внутриигровой кадр
- просим GPT сгенерировать на его основе целую ленту анимации
- нормализуем ленту, разбивая на кадры нужного размера
- пересобираем ассеты и смотрим превью в движке
1. Берем готовый базовый кадр (shipped seed frame)
Оказалось, модель выдает куда более стабильный результат, если опирается на финальный игровой спрайт, а не на абстрактный концепт.
Для анимации получения урона (hurt) базовым изображением послужил этот кадр. (Позже я напишу отдельный туториал о том, как его получил, но в целом подход описан в этом твите).
idle/frame-01.png
Это важно, так как мы жестко фиксируем:
- лицо
- пропорции тела
- цветовую палитру
- силуэт
2. Подготавливаем холст для Edit API
Не стоит отправлять исходный спрайт 64x64 напрямую. Сначала нужно увеличить его без размытия (методом nearest-neighbor) и перенести на большой прозрачный холст, оставив место под будущие кадры.
Если взять наш idle/frame-01.png и кинуть на холст 1024x1024, получится такая заготовка под анимацию урона:
На холсте 1024x1024 хватает места для раскадровки
Сам холст я собираю простеньким скриптом на Python.
3. Генерируем ленту целиком, а не покадрово
Практика показала: если просить нейросеть рисовать кадры по отдельности, персонаж начинает «плыть» и терять сходство с оригиналом. Гораздо надежнее скармливать ей запрос на всю ленту сразу.
Промпт для генерации анимации урона выглядел так:
Цель: финальный спрайт-лист (кандидат) с анимацией получения урона для 2D-платформера про пиратов с видом сбоку. Отредактируй предоставленный прозрачный холст, превратив его в горизонтальную раскадровку из четырех кадров. Спрайт в крайнем левом слоте — это точный стартовый кадр idle-v2, он должен остаться первым в новой последовательности. Тот же компактный пират, вид сбоку (взгляд вправо), красная бандана, синяя туника, коричневые сапоги, загорелая кожа, читаемое лицо, те же пропорции и пиксельный силуэт. Композиция: фон прозрачный, кадры выстроены в один ряд — четыре одинаковых слота 256x256 по центру холста 1024x1024. Никаких наложений, лишних персонажей, надписей и UI. Действие: первый кадр — спокойная поза. Кадры 2–4 — короткая реакция на удар: пират отшатывается назад, торс отклоняется, голова дергается, на лице боль, затем быстрый возврат к норме. Размеры тела, головы и пропорции одежды строго одинаковы на всех кадрах. Стиль: аутентичный 16-битный пиксель-арт, четкие кластеры пикселей, ступенчатое затенение (stepped shading), сдержанная палитра. Это готовый игровой ассет, а не концепт. Ограничения: без мечей, оружия, окружения, пола, свечений, дымки, эффектов удара. Без теней за контуром спрайта, коллажей, постеров и размытия. Сохранить прозрачное пустое пространство вокруг всех слотов.
В результате получилась вполне цельная последовательность:
Сырой результат. Внешность персонажа сохранилась отлично, но размеры от кадра к кадру скачут.
4. Нарезка ленты на игровые кадры
Сырую ленту напрямую в игру не вставишь. Мы нормализуем её под стандартные фреймы 64x64 через второй Python-скрипт. В общих чертах он делает следующее:
- находит части спрайта на сгенерированной ленте
- берет наш первый, якорный кадр (idle/frame-01.png)
- вычисляет единый масштаб для всей анимации
- добавляет отступы, центрируя каждый кадр на прозрачном фоне 64x64
- при необходимости жестко заменяет первый кадр оригинальным спрайтом
Последний пункт особенно важен для анимации урона. Мы принудительно подменяем сгенерированный первый кадр на оригинальный. Так анимация плавно стартует ровно с того спрайта, который игрок уже видит на экране.
На выходе получаем ровный спрайт-лист: все кадры приведены к единому стандарту.
Результат работы скрипта: каждый кадр вписан в квадрат 64x64 с прозрачным фоном.
GIF

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

5. Подводные камни
5.1. Как быть со сложными позами
Проблемы начинаются, когда персонаж сильно меняет габариты:
- пират с занесенным мечом объективно выше, чем он же в стойке смирно
- если просто вписать кадр атаки в квадрат, сам персонаж сильно съежится по сравнению с остальными кадрами
На третьем кадре скрипт неверно оценил рост пирата — и в итоге беднягу просто сплющило.
Как я это починил:
- вычисляем единый глобальный масштаб для всей ленты
- даем персонажу выходить за привычные рамки, если того требует поза (например, из-за поднятого меча)
- выравниваем кадры через отступы и общий якорь, а не ресайзим их поодиночке
Итог
Чтобы ИИ выдавал нормальные, не дерганые анимации, алгоритм должен быть таким:
- берем конкретный игровой спрайт как якорь
- генерируем всю анимационную ленту одним промптом
- нарезаем кадры с единым глобальным масштабом
- жестко подменяем первый кадр оригинальным спрайтом, чтобы анимация начиналась с правильной стойки
- обязательно тестируем результат в движке
Именно этот подход убрал почти всю грязь из генераций и сделал результат стабильным.
Комментарии (14)
А нельзя сначала сделать видео или серию фото в хорошем качестве, а потом его попросить креативно сдаунскейлить?
Ну в каком-то смысле морфинг по видео так и делают. Но это нужно иметь серию видео/фото для начала, причем в очень похожем стиле.
по спрайту создаешь персонажа апскейлом. потом с ним работаешь. потом даунскейлишь. не?
Так а зачем это если можно сразу из спрайта спрайты делать?
ну, в общем, лучше так не делать, это как по бинарникам собирать другие бинарники без декомпиляции в исходный код. уменьшенная картинка потеряла много информации, без исходника этой информации нет, ее уже не восстановить, только придумать. и сеть её будет придумывать. для каких-то простых случаев подойдет (когда все цвета остаются на месте), но в целом -- не подойдет.
можно апскейленый реф в контекст докидывать к каждому запросу, в принципе норм будет скорее всего
ну вот я о том же)
PS обычно картинки при создании поста копируют себе локально, чтобы они у всех грузились потом.
Не хочу связываться с правоторговцами. Дайте сервак в РФ куда грузить картинки - буду грузить 😉
ну а сейчас они не показываются у меня. и на твит ссылка кстати тоже битая.
Все рабочее, у вас просто НВП не включен/не справляется
ну да, так он и не должен быть включен для российских сайтов)
вторая, которая в тексте "Недавно я выложил твит, где"
аа, эту пожрало, спасибо за сигнал
Войдите, чтобы комментировать