пятница, 29 марта 2013 г.

Оптимизация Unity на мобильниках(3D графика)

update: (practical "hacks" -  http://rustam-kot.blogspot.ru/2013/09/optimize-performance-in-unity3d.html)

В последний месяц только и занимаюсь тем, что провожу предельную оптимизацию и глобальный рефакторинг. Поэтому начинаю описывать типичные техники, которые использую каждый день, в стиле маленьких советов. Все нижесказанное опробовано на собственном опыте и могут быть в корне неверны, поэтому если кто хочет обсудить что-либо, оспорить - you're welcome! А кто после прочтения поста не понял, почему его FPS шутер превратился в пошаговую стратегию - тот артист :)

Итак, у нас есть готовая рабочая версия игры, но она безбожно тормозит. Какие телодвижения в каком порядке произвести, чтобы выиграть процессорное и графическое время:

  1. В ПЕРВУЮ ОЧЕРЕДЬ юзаем профайлер. Даже если знаешь, где можно сделать быстрее, к примеру, уменьшить асимптотику у какой-нибудь структуры, все равно смотреть профайлер. Часто думаешь, что тормозит по одной причине, а на самом деле вылезает совершенно левый оверхед.
  2. Лидеры тормозов:
    • отрисовка - 70%
    • расчет физики - 20%
    • скрипты - 10%.
    И это совершенно понятно: графические карты на мобильниках слабы и весь боттлнек складывается именно в отрисовке. Поэтому дальнейшие оптимизации будут касаться в основном графики.
  3. Первый признак того, что все плохо: много draw-call-ов(далее dc). Немного теории: рисованием чего-либо занимается компонент Renderer(и различные его вариации: MeshRenderer, SkinnedMeshRenderer, ParticleRenderer, LineRenderer, etc), который принимает на вход материал с заданными параметрами(шейдер, текстура и т.д.) и отрисовывает. Видеокарта для каждого материала делает отдельный проход рисования, что и является dc. Ну так вот, чем больше материалов на сцене, тем больше страдает видеокарточка девайса-> тем больше страдает пользователь -> тем больше единичек в app store.
  4. А еще есть такая вещь, как batching, смысл его заключается в том, что несколько объектов, использующих один и тот же материал отрисуются за один проход. То есть, к примеру,  выгодней делать двадцать однотипных домов, как в Набережных Челнах, чем 4 дома как в центре Санкт-Петербурга, потому что двадцать однотипных домов отрисуется за один проход. Поэтому везде, где возможно, используем один и тот же материал.
  5. У батчинга есть проблемы:
    • если у двух объектов разный scale - батчинг не работает. Возможные пути решения:
      •  менять размер mesh-a, а не объекта, если это meshrenderer(к примеру, так делается в NGUI ).
      • строго следить за однотипными объектами и бить по рукам любого, кто не любит стандартные числа.
    • если объекты принимают тень в реалтайме(читай: не лайтмапятся) - батчинг не работает. 
    • если меш очень сложный(больше 900 вершин для тупо вершин, 300 вершин для uv, 180 вершин для uv1, uv2), то батчинг не работает. 
    • если объекты используют один и тот же материал, но у одного материала какое-нибудь свойство отличается - unity создает новый инстанс материала - тут разные материалы - не может быть и речи о батчинге
    • если шейдер использует более одного прохода - батчинг не работает. 
  6. На тему материалов: используем мобильные шейдера вместо обычных. Обычные шейдера используем только тогда, когда реально приспичит. Сегодня как раз столкнулись с веселой ситуацией: есть 50 совершенно одинаковых объектов(различаются только позицией и поворотом), они все помечены батчиться статически, при этом в игре с ними получается +50 dc. Начали искать, где проблема. Случайно решили вместо diffuse шейдера поставить mobile/diffuse - объекты забатчились. Вообще, юнитеки божатся, что мобильные шейдеры в десятки-сотни, а в некоторых случаях и тысячи раз быстрее десктопных.
  7. Потолок dc(буду судить только по ios, в android-е там совсем зоопарк):
    • iphone 3gs, ipod 4: 70 dc
    • iphone 4: 90 dc 
    • iphone 4s: 200 dc
    Тут еще несколько замечаний: 3gs парится больше относительно количества полигонов, для 4 айфона больше важно количество dc. 
  8. Оптимизировать количество материалов можно благодаря использованию текстурных атласов. Смысл очень прост: много картинок засовываем в одну большую, рендерерам говорим использовать определенные участки карты и видеокарта может отрисовывать это все в один проход. Я просто оставлю это здесь: http://www.digitalopus.ca/site/mesh-baker/, http://www.codeandweb.com/texturepacker.
  9. С атласами не все может быть так круто: четыре атласа 2048x2048, но загружаемые и разгружаемые в памяти лучше, чем один атлас 4096x4096. Пока хотя бы один материал использует  текстуру, видеокарта держит текстуру в памяти.
  10. Теперь придем к отрисовке текстур: используем сжатие на 146%. PVRTC для ios, ETC/DXT для android, mip-map-ы обязательно и тогда есть вероятность, что  видеокарточка не повесится, а скажет вам спасибо.
  11. 2D - это отдельная тема.
  12. Instantiate/Destroy объектов дорого не только для CPU, видеокарта тоже обязана загружать/освобождать ресурсы. Не заставляйте ее делать слишком часто, используйте  паттерн object pool.
  13. Ну и самая противная кнопка, подставленная на твой стул - это отрисовка где-нибудь в Update/FixedUpdate. На примере: вот так чуваки обновляют визуальный компонент подсчета очков:
    private void Update(){
      pointsMesh.text = Model.points.ToString();
    }
    
    Для далеких из мира Unity3d: Update вызывается каждый кадр! Вместо того, чтобы просто обновить один раз вьюшку, когда модель обновится, мы устраиваем маленький ddos мобильника. И вообще, нужно минимизировать количество Update/FixedUpdate и переходить к event-логике, а еще лучше к конечным автоматам.
  14. Да, совсем забыл про свет: везде, где возможно, заранее запекаем свет, предпосчитываем тени. Если тени не принципиальны(например, 2D графика), то убираем по возможности везде, где только возможно. Производительность увеличивается прямо на глазах!
Про графику вроде все. Вспомню, напишу еще. На следующей неделе напишу некоторые советы по оптимизации скриптовой части.

Вот что можно почитать у юнитеков на эту тему:

понедельник, 25 марта 2013 г.

Особенности национальной верстки или привет зоопарк устройств

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

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

  • (дизайнерам) Забудьте о pixel-perfect верстке. Ее не будет. Но именно благодаря этому на всех экранах все будет выглядеть одинаково.
  • (программистам) Свыкнитесь, что будут грязные хаки. Это вначале все будет идти гладко да хорошо, а потом вдруг запустишь аpk на 320x240 и удивляешься, что все друг на друга наплыло.
А теперь описание процесса, как мы достигаем вселенского счастья:
  • Есть UI (кнопки, слайдеры, менюшки), а есть бэкграунд(бэк). Ну так вот, бэк - это просто картинка(чаще всего однородная) на заднем фоне. Она нужна для того, чтобы по бокам не было черных фонов.
  • Мы делаем картинку бэка максимально большим и широким, а потом на каждом девайсе скейлим вниз пропорционально по высоте. К примеру, у нас бэк размером 2764 x1536.  На айфоне 5 отрисуется центральные 2726x640 пикселей. Таким образом, получается, что на самом деле наш бэк будет выходить за границы отрисовки. Скейл вверх нежелателен, но если есть ограничение по памяти, то тут никуда не денешься - никто не даст тебе рисовать картинки 4096x4096.
  • Бэк центрируется. Ваш кэп.
  • В нарезке UI сюрпризов нет, нарезаем по обычному.
  • При верстке UI абсолютно все располагаем относительно UIAnchor-ов. Каждый элемент экрана должен принадлежать к какой-то отдельной части. Да, на каждый чих индивидуального пространства создаем отдельный якорь. Типично используемых якорей хотя не так уж и много: центр, левый верхний угол, правый верхний угол, нижние углы по краям, верх и низ. Остальные используются редко.
Да, еще одно ограничение: надо уложиться в 50МБ выходного файла. Это было интро, а теперь требования и настоятельные рекомендации:
  • (дизайнерам) Размеры бэкграундов: 1460x768(HD), 1090x460(SD).
  • (дизайнерам) Верстка под экраны: 1024x768(HD), 2048x1536(если совсем не жалко памяти). 
  • (дизайнерам) Формат картинок: png
  • (дизайнерам) По максимуму избегать альфы (особенность unity - чем больше площадь наложения альф, тем сильнее просаживается fps)
  • (дизайнерам) По максимуму избегать градиента (из-за проблем со скейлом)
  • (дизайнерам) По возможности, используем nine-patch везде, где только можно.
  • (дизайнерам) Шрифты в растровом формате. Более подробно: на ютубе
  • (программистам) UI элементы пакуем в одну текстуру с помощью NGUI, бэки оставляем как отдельные картинки(преимущество от запаковки бэков теряется при огромном размере атласа).
  • (программистам) Сжатие текстур: PVRTC для ios, ETC/DXT(1/5) для android.
  • (программистам) Атласы имеют размеры 2^N
  • (программистам) Все используемые картинки - квадратные.
Может быть, что-то забыл. Что забыл, напишу позже.

воскресенье, 24 марта 2013 г.

Hack day?

У меня нет никаких идей, чем можно позаниматься в следующие выходные. И я давно ничего не писал в гаражном стиле - сесть и быстренько накатать. Корочеееее, идея: на следующих выходных устроить себе нечто наподобие hack day. То есть к пятнице нужно придумать идею, а к вечеру воскресения уже реализовать прототип. Чего-нибудь простого, интересного, но уже работающего. Если будет запор идей и ничего в голову не придет, то буду разбирать старые завалы, начну переписывать халял гайд.

Если вдруг у кого-нибудь есть идеи (в приоритете мобайл-дев, ios/android), напишите мне пожалуйста. Даже самая глупая идея имеет смысл. А вдруг кто тоже хочет поучаствовать - то тем более :)


суббота, 23 марта 2013 г.

Эффект молнии в Unity3d

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

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

Вот примерно это у меня в конце и получилось: 


Как решить этот вопрос?

Нулевая мысль: надо загуглить. 

В гугле много примеров, советов, много из них полезного, но большинство из них - просто болтовня.
Если нужно что-нибудь что-нибудь молниеподобное, то здесь их много. Только не все они отвечают на вышеописанные требования. Зато можно взять уже готовое и использовать их для себя. 

Устал искать, решил взять заготовку и написать сам.

Первая мысль: использовать партиклы. 

Как? Непонятно. У кого есть идеи, велком.

Вторая мысль: шейдером.

Сделать в любом редакторе 3d прямую, а потом создавать ее, применив фрагментный шейдер, который будет:
  • отрисовывать ее не сразу, а по какому-то времени. Таким образом, мы добьемся эффекта, что молния "бьет", можно управлять скоростью.
  • рисовала не прямую, а ломаную в заданной амплитуде. По сути, это просто: амплитуда - это просто абсолютное значение координаты относительно Y, нужно делать кривое зеркало в 1D пространстве.
  • ну и цвет - простое умножение на цвет. 
К слову сказать, первое место из той ссылки как раз и юзает такой шейдер, но меня он отпугнул своей неуклюжестью и тяжелой отрисовкой. 

Уже решение: взять и тупо рисовать.

И на мой взгляд, самое простое и гениальное решение: можно провести какой-нибудь "безбашенный" шатающийся отрезок из точки А в точку B, а в позициях, где он побывал, нарисовать пиксели.
Более того, в Unity уже есть так называемый trail renderer, в нем есть и толщина линии, и материал с нужным мне цветом. По названию понятно, что это штука, которая рисует на позиции, где побывал гейм-объект. Юнитеки будто знали :) То есть вся задача заключалась в том, чтобы создать пустой трансформ в точке А и довести ее до точки В, в update иногда сбивая ей маршрут.

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




понедельник, 18 марта 2013 г.

Разговаривать на общем языке

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

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

Теперь у меня новая проблема: мне надо объяснить моим коллегам, что когда нужна какая-либо функциональность, не нужно страдать синдромом not invented here, нужно взять готовую либу и, если нужно, добавить недостающую функциональность. И, блин, не унаследовав от базового класа, а используя композицию!

Когда я вижу простыню из 900 строк, у меня возникает желание сделать вот так:


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

 и обязательно ищу в нем баг, чтобы получилось вот так:


Вы не поверите, в 90% я его нахожу :)



Честно, не надо придумывать заново колесо, не надо проявлять фантазию в именах переменных, пользуемся готовыми паттернами, реюзаем код. Проще говоря, для себя вывел следующие правила:
  • Используем единый стиль форматирования, желательно тот же самый, что используется внутри общества, в котором вы живете. Потому что если вы пишете иначе, то вам сложно понять код других программеров, а другие будут плеваться на ваш код. Это все равно как русскому туристу в Китае на рынке договориться о скидке.
  • Самое сложное - придумывать названия (переменным, классам). Это правда. Вот не ленимся переименовать кнопку c ButtonX на MessageButtonBehaviour, anotherTemp на что-либо более подходящее. Я уж молчу о переменных типа ttt, secondVar, var5 (я не шучу - сегодня видел в боевом проекте).
  •  Юзаем паттерны, а не изобретаем их! И не надо делегат называть адаптером(привет андроид!), не надо хвалиться, что придумал новое супер решение, не прочитав шестую страницу банды четырех. 
  • Не забываем, что паттерны паттернами, но принципы KISS и YAGNI игнорировать нельзя. Вообще, сначала применяем здравый смысл, а потом бегите за стереотипным решением.
  •  Изучаем алгоритмы и структуры данных не для того, чтоб победить на топкодере и получить новую футболку, а чтоб знать внутренности структуры данных и алгоритмов, уметь применять на практике и если нужно, написать самому. 
  • И да, если знаем алгоритм или структуру, то применяем готовое решение, а не пишем собственный мегабыстрый класс! Сегодня рыдал от строчки типа такого: public List<string> itemsStack = new List<string>();. Угадайте, что это за структура данных? Угадайте, как автор этого шедевра делал push, pop, top? И это было в файле объемом 900 строк!!!!!1111 Я рискнул рефакторить, но позже понял, что это было плохой идеей, потому что этот гребанный стек применялся еще где-то и передавался ref-ом в какой-то левый класс, который зачем-то использовал его по-своему как список(linkedList)!
  • И да, не боимся пользоваться плагинами, не ходим грудью колесом аля "я все умею!", все самое лучшее уже давно написано за нас. Мы жмотимся на плагин стоимостью 100 баксов, но сами в течение пяти дней готовы допиливать собственный говнокод, почти выполняющий функции того плагина.
  • Не забываем развиваться, изучать новые фичи своего инструмента. Поверьте, вопреки распространенному мнению, обновление - это фикс багов и добавление новых фичей, а не наоборот! И если в языке X появилась штука, которая позволяет в одну строчку написать то, что вы писали в сорок - то даже дурачок Боб, не сумеющий написать свое имя наоборот(с), поймет, что надо юзать эту штуку!
Я очень рад, что в лкш ввели ручную проверку кода. Прикиньте, сколько бы еще можно было родить алгоритмических монстров, способных написать соптимизированную регекспу, но не способных разобраться в лапше из кучи классов. Надеюсь, они вряд ли столкнутся с теми проблемами, c которыми сталкиваюсь я.

P.S. На проекте у нас два программиста: коллега и я. Сегодня произошел священный спор по поводу египетских скобок. Я сторонник египетских скобок, аргументировал так:

  • меньше ненужного пространства при той же читабельности
  • большинство проектов (включая исходные коды юнитеков), написано с применением данного стиля, то есть он стандарт де-факто.
Он аргументировал это так:
  • египетские кнопки это плохо
  • я привык писать по bsd стилю, по-другому не умею.
  • весь мир неправ.
В общем, это был священный спор и каждый остался при своем мнении. Вдруг кто считает иначе, велкам :)








пятница, 15 марта 2013 г.

to-do or not to-do

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

Встретились с руководителем, обсудили, договорились о перспективах и согласились уточнить детали позже. Приблизительное время разработки 2-3 месяца, первая итерация: 3 недели. В течение первых нескольки итераций(это примерно полтора месяца) я буду вплотную работать с дизайнером (а может быть и дизайнерами). Во время обсуждения произошел следующий диалог:
 Я:- Как у вас там с дизайнерами? Они у вас насколько гордые?
 Руководитель: - Не, не гордые. Ты с ними легко найдешь общий язык.

Вот тут-то мы и встали. Дизайнер оказался очень гордым. Самым гордым из всех дизайнеров, с кем мне приходилось сталкиваться. Даже мем "упоротый" покажется уменьшительно-ласкательным. С другими дизайнерами, с которыми мне приходилось иметь дело, мы всегда находили общий язык. ВСЕГДА. И это происходило, в первую очередь, потому что мы оба понимали, что делаем ОБЩУЮ работу и стремимся к ОДНОЙ цели, находимся на ОДНОЙ стороне баррикад и не пытаемся вставить палки друг друг другу в колеса.

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

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

Позже отписался руководитель, сказал, что они поговорили, дизайнер будет со мной работать. От дизайнера тоже пришло холодное письмо, что он начал изучать нужные ему тулзы и будет спрашивать, если что. Видимо, пересилил себя.

Сейчас сижу и думаю: а что, если бы мы подписали договор и такой конфликт произошел в середине проекта? И самый главный вопрос: что нужно сделать, чтобы предотвратить конфликт? Пообщался со знакомым юристом, получил совет вставить пункт в договоре, защищающий меня в подобных случаях. Ну или как вариант вообще не подписывать. Вот чую пятой точкой, что будет эпопея с дизайнером, немало придется нервных волокон себе поубивать. А так нет договора - нет игры - нет проблем. А сэкономленное время можно потратить на другие проекты, не менее интересные и привлекательные. Но жалко, идею жалко. Даже не знаю, что делать.






среда, 13 марта 2013 г.

Всем PM-ам посвящается


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

воскресенье, 10 марта 2013 г.

Пользователь! Ты скотина!

Не, я серьезно! Пользователь, пользующийся АБСОЛЮТНО БЕСПЛАТНЫМ приложением и возникающий по поводу того, что что-то работает не так, как он хочет - ты последняя пользовательская СКОТИНА!

Как пример: Вот поищите в app store  приложение Ted - ну просто отличнейшее приложение для просмотра огромного количества видеолекций на самые разные темы, смотри -не хочу. Лучше приложения я вообще не видел. И маркетологи решили выпустить его на российском рынке.

Маркетологи, вы ошиблись! Если по всему миру у вас только положительные отзывы и пять звезд, то российский рейтинг еле дотягивает до трех звезд с типичными комментариями: нет русского языка! Я не верю, что доходов от наших нищебродов много, а рейтинг и хорошее имя от этого портится :(

А теперь к тебе, пользователь! Запомни несколько моментов:

  • Разработчики тебе ничем не обязаны, тем более если ты пользуешься полностью бесплатным приложением! И вообще нет разницы, это айфон пятый или андроид за 3000 рублей. Не устраивает - проходи мимо. 
  • Да, если у тебя слабый телефон - не жалуйся, что приложение тормозит или вообще не поддерживается у тебя; ты сам выбрал категорию hard life, когда покупал девайс. 
  • Если у тебя есть конструктивная критика - обратись к разработчикам (по мылу, сайту) и предложи, как можно сделать лучше. Поверь, разработчики ценят это и прислушиваются  к новым идеям! Даже врачу жалуются не просто так - он может предложить решение вашей болезни. 
  • Если ты рассказал свои идеи - не надейся, что завтра же твои хотелки будут реализованы - во-первых, программист тебе не обязан, а во-вторых, возможно, это трудно реализуемо или вообще отложено на другой срок. 
Вообще считаю, что пользователя, который поставил одну звезду приложению, нужно кастрировать. Вот сидишь в свободное время, делаешь не игру какую-нибудь, а реально полезные тулзы, чтобы жизнь стала хоть немного лучше, а тебе приходит фидбек, что ты все делаешь зря, мне синенький цвет не нравится и вообще ничего не работает(при этом совершенно не понятно, что же там за проблема). В такие моменты руки опускаются - хотел только помочь, а получилось наоборот. Да ну нафиг, сделаю лучше под заказ еще один angry birds - и приятнее, и деньги платят.