Полноэкранная анимация в iOS игре, или как быть если графику не реально загрузить в память

в 11:44, , рубрики: cocos2d, iOS, xcode, обработка изображений, Песочница, разработка игр для iOS, разработка под iOS, метки: , , ,

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


Итак, все видели наверное игру с говорящим котом, который повторяет смешным голосом все что ему говорят? Так вот у нашего заказчика было желание сделать подобную игру, но с другим персонажем.

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

Итак, в процессе переговоров с заказчиком выяснилось, что графика для анимаций у них уже готова в JPEG формате, во весь экран, для всех типов iOS устройств, включая iPad3 с его диким разрешением экрана (напомню, это 2048x1536). Причем анимации были отрендерены исходя из 60 кадров в секунду. И попробовав реализовать игру своими силами, они столкнулись с массой трудностей, которые они предпочли свалить на голову кому-нибудь другому. Для чего и обратились к нам.

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

Другое, что сразу пришло в голову — с таким размером кадра использование текстурных атласов отпадает сразу, потому что предельный размер текстуры, который поддерживается девайсами (а это либо 2048x2048, либо 4096x4096 в зависимости от девайса и версии iOS) не вместит и пары кадров. Стало быть при загрузке анимации будем каждый кадр грузить отдельно из файловой системы.

И пожалуй стоит уговорить их срезать заложенный fps анимаций до, скажем, 15-ти кадров в секунду, что, по моему опыту, вполне достаточно для анимации движений персонажей. Что в общем-то легко удалось. В итоге за основу взяли каждый четвертый кадр.

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

Попытка номер раз

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

Полноэкранная анимация в iOS игре, или как быть если графику не реально загрузить в память

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

Будучи в полной уверенности, что мы на правильном пути, мы обработали таким образом все их анимации и только затем начали собирать тестовые примеры.

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

Хорошо, решили мы, выходит единственный вариант — загружать их непосредственно перед запуском. И это могло сработать для коротких анимаций, но любая более менее длинная вызывала неприемлемые задержки, вплоть до 10-12 секунд, на загрузку. Оно и понятно iOS вынужден где-то развернуь JPG картинку в чистый битмап, и затем передать его OpenGL для использования в качестве текстуры. А в таком виде это уже несколько мегабайт, даже не смотря на то что исходное изображение весит меньше 100KB.

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

Попытка номер два

У PVRTC (мы использовали PVRTC4444) есть несколько преимуществ по сравнению с JPEG.
Во-первых, он не требует никаких преобразований внутри девайса, картинка может использоваться в качестве текстуры сразу, что дает серьезную экономию времени при первоначальной загрузке графики. Еще одно важное обстоятельство — картинки в виде PVRTC весят меньше, чем сырые битмапы, в которые превращается в итоге любая JPEG картинка в OpenGL, а это значит, что даже самых длинных анимаций мы сможем держать в памяти сразу несколько одновременно.
Но есть и минусы. Главный из которых — размер приложения. Хоть они и весят меньше чем сырые битмапы, но по сравнению с JPEG — это в разы большие файлы.
Т.е. выигрывая в скорости предзагрузки графики мы значительно увеличиваем вес приложения.

Другой минус — это ограничения, связанные с геометрическим размером и пропорциями. Все файлы в формате PVRTC должны быть обязательно квадратными со стороной равной степени двойки.
Прикинув объем всех файлов, если для iPad3 мы приведем все наши кадры к размеру 2048x2048, мы сразу отказались от этой идеи. И приняли решение ограничится двумя размерами. Для iPhone использовать кадры размером 512x512, а для всех iPad ограничиться текстурами размером 1024x1024. Потому что во время проигрывания анимации все равно на глаз не заметно различие в качестве картинки на iPad3 и iPad2, так как геометрический размер экрана, все таки, такой же и потом, картинки быстро сменяются.
Значит нужно было каким-то образом привести всю нашу графику с ее хаотичными размерами кадров (они могли быть например такими — 1355x1278) к виду 1024x1024 (и 512x512 под iPhone).

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

Полноэкранная анимация в iOS игре, или как быть если графику не реально загрузить в память

Пришлось потратить два дня на масштабирование и переформатирование наших картинок в PVRTC. Для переформатирования использовалась утилита, идущая в комплекте с XCode и написанный нами простенький shell скрипт для обработки всех файлов в папке, а для масштабирования, конечно, фотошоп с его экшенами).
Интересная деталь — вес каждого файла в PVRTC определяется только его геометрическими размерами. Т.е. каждый файл iPhone анимации занимал 128KB, а каждый квадрат для iPad весил 512KB, не зависимо от содержимого.
Конечно размер приложения резко вырос до неприличных сотен мегабайт, зато загрузка анимаций происходила практически мгновенно и для большинства анимаций можно было поступать просто — загружать их в тот момент когда они нужны, проигрывать и сразу же выгружать из памяти. Но оставались еще длинные анимации которые все таки требовали одну-две секунды для предзагрузки, что конечно же, резало глаз заказчику.

Тогда мы решили сделать «вскрытие» и заглянуть как же все таки устроена игра про кота. Выяснилось несколько важных моментов:
Во-первых, вся графика анимаций у них полноэкранная в JPG формате, при этом чудовищно сильно пожата, — видимо, как и мы, ее разработчики делали ставку на то, что во время проигрывания трудно разглядеть качество картинки.
Во-вторых, судя по количеству кадров и фактическому времени проигрывания анимаций в игре, они ограничились скоростью в 5-6 кадров в секунду.
Так же мы выяснили что для обоих iPad-ов у них использовалась одна и та же графика.

После этого мы решили проверить одно предположение.

Попытка номер три

Мы предположили, что возможно эти ребята просто гоняют все анимации в UIKit, не используя даже OpenGL. Да и в самом деле, зачем тебе OpenGL, если ты собираешься довольствоваться 5тью кадрами в секунду.

Смеха ради мы решили попробовать нашу исходную графику проиграть средствами UIKit — просто последовательно загружая каждый полноразмерный кадр в UIImageView.
Оказалось, что в этом режиме iPad2 успевает проигрывать ее со скоростью 30 кадров в секунду, a iPad3 (не смотря на в четверо больший размер картинки) выдает, тоже вполне приличные 20 кадров в секунду!
И это без предварительной загрузки всей анимации, мы просто загружаем один кадр, отображаем его и тут же, выгрузив его из памяти, заменяем его другим не выставляя никакой задержки между кадрами.
Вот так открытие! Можно было не делать вообще ничего, все и так прекрасно работало бы и графика в JPG формате дала бы в разы меньший размер приложения, и в памяти ничего держать не нужно. При таком подходе анимация может быть любой длины, ее ограничивает только желаемый размер приложения.
В итоге мы переписали игру, используя этот подход.

Вывод из всего этого я бы сделал такой. Если вам приходится иметь дело с большими анимациями и вас устроит не слишком высокий фреймрейт, то возможно стоит посмотреть в сторону использования UIImageView (может быть даже в отдельном слое поверх вашей Cocos2d сцены). Очень может быть, что это окажется оптимальным решением.

Справедливости ради, хочу все таки заметить, что здесь я опустил другие важные моменты, которые нам приходилось учитывать в наших решениях. Например в игре должна была быть возможность записи происходящего в виде видео, для чего мы изначально хотели использовать один популярный фреймворк, который работает только с Cocos2d, поскольку записывает видео из OpenGL. Это была одна из причин, почему мы держались за Cocos2d. Но в итоге, поскольку все анимации были реализованы через UIKit, нам пришлось реализовать запись видео самостоятельно.

Автор: OlegKlk

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js