вторник, 21 октября 2014 г.

Весело о грустном или почему твоя первая игра провалится


- А вы слышали об энгри бердс? Такая тупая игра и она взлетела!!!111!
- Почему такая тупая механика, и эта игра успешная?
- Что сделать, чтобы твою игру качали? 
-А как заработать на игре?
-О, у меня отличная идея! Короче, 
давай я поделюсь, ты сделаешь, прибыль - пополам?
(с)ОЛОЛОШИ

- Если хочешь заработать на играх - сыграй лучше в казино, 
там вероятность больше
(с)Денчик

(геймдевелопер: ожидание)



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

Печальный факт №1: ваша игра не оправдает ваших ожиданий


А причин этому несколько:

  • разработка игры - это не разработка приложения. Если приложение решает ПРОБЛЕМУ, то игра приносит УДОВОЛЬСТВИЕ. И если можно более-менее оценить пользу от решения проблемы("приложение экономит 15 минут в день" или "оно заставит Вас заниматься спортом"), то понятие удовольствия оценить адекватно сложно.
  • первая игра - это первый ребенок. Поскольку ты его не рожаешь, а как двоечник Папа Карло, будешь строгать, то он получится инвалидом, с одной рукой, тремя щупальцами и уродливым лицом. Тебе все будут говорить, что это отстой, что надо первое, пятое, десятое, но ты не будешь слушать - ведь ты защищаешь своего лебедя.
  • ты нифига не знаешь. Ни как сделать красивую графику, ни оптимизировать под все устройства, ни придумать game loops, ни начальный траффик влить, не говоря уже о банальном маркетинге - тебе ведь достаточно уметь создавать коллайдеры в юньке.


Печальный факт №2: если ты начинаешь делать игру, чтобы заработать "свой миллион", то, естественно, ты сделаешь клон чего-нибудь. И он провалится.
Не, нуачо, вот на твоих глазах flappy bird - саксесс стори, у тебя игра, естественно, лучше чем другие десятки клонов (синдром not invented here, ага). Но первоначальный восторг и ажиотаж сменится полным разочарованием. А все потому что ты не до конца изучил существующую механику оригинала, не позаботился о начальных пользователях, не поработал над retention-ом.

Печальный факт №3: если ты геймер с рождения, то ты выпустишь игру с клевым геймплеем. Но ты не заработаешь. Проект провалится.

А провалится он по простой причине: ты получил ровно то, что хотел. Ты хотел сделать игру - ты ее сделал, ты не хотел заработать на этом деньги. Если изначально не подумать, на чем ты будешь зарабатывать, ничего не получишь.

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

Печальный факт №4: На свою первую игру ты потратишь неимоверное количество времени, сил. Но от этого она не станет лучше

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


Игровая индустрия является самой доходной нишей если не на всем IT-поле, то на мобильных уж точно. И не секрет, что в индустрии крутятся миллиарды-триллионы долларов. Печальный факт заключается в том, что большинство из этой лавины достается динозаврам индустрии. Инди, работающие ради искусства и питающиеся роллтонами, миллионов не видят.
Так что же делать?
  1. Сделать свою первую игру. Да, она провалится. Да, будет отстой. Зато наберешься опыта. И либо уйдешь, либо захочешь еще попробовать. Оба варианты ведут к лучшему.
  2. Если уже есть игра - ты крут. Это не сарказм. Всего лишь 20% людей берутся делать и заканчивают. Если ты зарелизил - ты уже в тех 20%.
  3. Не берись за все подряд. Сделай анализ рынка, определись со своей нишей. 
  4. Изучи, что популярно, поиграйся. Пойми, что цепляет, если не понимаешь - проведи коридорное тестирование.
  5. Читай. МНОГО. Начиная от Art of Game design, заканчивая блогами успешных разработчиков и геймдизов.
  6. Работай. Вкалывай. Не, не так. Вджобывай. Клепай десятки игр, экспериментируй, пробуй, ошибайся.
  7. Учись. На своих ошибках, на чужих. Принимай знания и перерабатывай.
  8. Когда количество выпущенных игр перевалит за пяток, у тебя появится ощущение, как надо делать "правильно". Это как увидел тучи, значит скоро будет дождь - ты будешь на уровне интуиции понимать, что если сделать так, получишь такой импакт. Объяснить не сможешь, но будешь осознавать.
  9. Делай на совесть. Чтобы игра была успешной(то есть с большим количеством закачек, с хорошим процентом ретеншна), она должна быть ИГРОЙ, а не плевком в лицо игроку. Делайте годноту - получите лояльных клиентов. 
  10. Слушай пользователей, прислушивайся к фидбеку. Чаще показывай свое "тварение" другим, получай фидбек. Чем раньше получишь предупреждение, тем раньше сможешь среагировать.
  11. Контактируй с другими людьми из индустрии. Дружи командами. Помогай. Это никогда не вредно.
  12. Получай удовольствие от геймдева. Разработка игр - это ведь действительно фан:)
  13. И еще раз РАБОТАЙ. Сделай уже свою angry birds!



(как пацаны к успеху пришли)

понедельник, 28 июля 2014 г.

Unity + winphone = ?

UPD: всех мусульман с праздником ураза-байрам! По этому случаю татарский словарь на ios в течение трех дней будет бесплатным. Православных поздравляем с днем крещением Руси и тоже советуем скачать этот словарик :)

Ок, опыт по разработке и паблишингу win phone приложений появился.

Я научился делать как нативные приложения (к примеру, на основе дефолтного шаблона сделан словарь lugat, а также мультипайвотный турецко-русский и русско-турецкий словарь), так и unity игр. При разработке нативных приложений надо следовать тем же паттернам, что при разработке на iOS. Если понимаешь, что такое делегат и как работает data provider, проблем не будет. Однако верстка страницы больше напоминает андроид - там больше работаешь вручную с XML-ками, нежели тянешь курсором за констрейны.

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

воскресенье, 1 июня 2014 г.

Сопливый пост: о помощи, о беге, о процессе

Disclaimer: поскольку более-менее технические и полезные посты собираюсь писать на technoballgame.com, в этом блоге буду писать свои мысли, вести сопливый дневник. Вряд ли ты что-нибудь полезное почерпнешь отсюда, дорогой читатель, это все я пишу для себя.


О помощи

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

четверг, 22 мая 2014 г.

Мысли об игре

Ок, игра закончилась.

Она не является новой, это вольная интерпретация игры бм (которая в свою очередь является интерпретацией тренингов Кови), в которой, если выражаться игровой терминологией, были исправлены некоторые косяки баланса


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

воскресенье, 20 апреля 2014 г.

Большие и маленькие компании


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

Небольшой совет молодым и талантливым разработчикам, которые ищут работу, — никогда не идите работать в большие компании. Никогда, ни за какие деньги, ни при каких обстоятельствах. Даже если Вы проработаете всего год, оттуда Вы уйдете уже другими людьми, лишитесь лучшего, что у Вас сейчас есть. Поработав «по графику» с унылым пузатым менеджерьем, Вы станете беспомощным отработанным материалом с рабско-потребительской ментальностью. Ваш опыт работы в Google, Яндексе или Mail.ru — мощная антирекомендация для любого здравого руководителя маленькой команды.
Бездельничайте, учитесь, играйте, рисуйте, создавайте музыку, занимайтесь фрилансом, открывайте стартапы, делайте никому не нужные проекты, голодайте — но никогда не идите работать в корпорации. Помните: всякий раз, когда молодой и талантливый разработчик идет работать в большую компанию, умирает котенок. 
(с)Дуров

Все совпадения случайны и являются выдумками читающего индивидуума


Как-то меня спросили: куда идти работать юристом - в Тошиба или менее известную DS Law. Вопрос был непрофильным для меня, тем не менее его можно перефразировать как-то так: где лучше работать - в большой компании-корпорации или маленькой студии-стартапе?

Для меня ответ простой: it depends.
У каждой стороны есть свои "особенности". Плюсами или минусами это назвать сложно, потому что положительность зависит от восприятия человека, поэтому просто опишу эти особенности. Начнем с больших компаний:

пятница, 14 марта 2014 г.

Сжатие текстур андроида в Unity

В айфонах почти все хорошо. Сделал приложение, потестил на паре девайсов (iphone 4s, ipad2) и можешь быть почти уверен, что с остальными не будет головной боли.

У андроида зоопарк устройств. Это приносит огромные проблемы. В первую очередь, с тестированием и оптимизацией. Далеко не факт, что оптимизация для одних девайсов положительно скажется для других девайсов тоже.

Поскольку очень много оптимизации связывается с отрисовкой, нужно уделить большое внимание отрисовываемым текстурам, а именно сжатию. Unity поддерживает несколько видов сжатия для разных видеокарт: ETC1, ATC, DXT, PVRTC, ETC2. Более того, можно не только руками для каждой текстуры выставлять желаемое сжатие, а задать AndroidBuildSubtarget - при билде все запакуется в лучшем виде.
Внимание, вопрос: в какой формат нужно запаковывать? Ответ: зависит от того, сколько APK вы хотите вылить в google-play.
Дело в том, что магазин приложений гугл поддерживает выливку нескольких билдов для разных девайсов. Это сделано специально для того, чтобы приложения поддерживало как можно больше устройств. Там можно задать разные apk для разных видеокарт, разных пропорций экранов, разных разрешений и т.д. Но есть одно большое НО: рекомендуется использовать эту возможность только тогда, когда есть полное понимание, что одним билдом не обойтись.  Потому что разработку нескольких билдов тяжело поддерживать, а также должна быть правильная систематизация версий, ведь в том же мануале сказано, что гугл отдает поддерживаемый билд с наивысшей версией. То есть, если видеокарта поддерживает etc билд и для нее предпочтительнее atc, но версия etc > версии atc, гугл отдаст etc билд.

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

0411079, или более наглядно 04 1 1079, где первые две цифры - min android sdk(04 - это что-то типа android froyo или еще древнее), второе число - это номер сжатия(об этом позже), а последние цифры - это номер версии билда, без точек(1.07.9).

Как правильно выбрать номер сжатия, чтобы приоритетный билд попал в нужное устройство. Надо исходить из логики: что реже используется, то и надо выше ставить приоритет. А статистика такова: etc > atc > dxt > pvrtc > etc2. Именно в таком порядке их можно и занумеровать.

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

[MenuItem("Custom/Build apks for all Texture Compressions")]
    private static void BuildApksForAllCompressions()
    {
        BuildAndroid(AndroidBuildSubtarget.DXT);
        BuildAndroid(AndroidBuildSubtarget.ATC);
        BuildAndroid(AndroidBuildSubtarget.PVRTC);
        BuildAndroid(AndroidBuildSubtarget.ETC);

        Debug.Log("Finished building");
    }

    //Version code looks like this: 04 1 1079
    //first two numbers = API LEVEL (04)
    //second two numbers = Android Subtarget (1)
    //Last numbers = version number, revision (1079, got from "1.07.9")
    private static int GenerateVersionCode(AndroidBuildSubtarget target)
    {
        string number = string.Format("{0}{1}{2}", ((int) PlayerSettings.Android.minSdkVersion).ToString("D2"),
            GetTargetVersion(target), PlayerSettings.bundleVersion);
        number = number.Replace(".", "");
        int result = int.Parse(number);
        Debug.Log("Android " + target + " version: " + number + ", " + result);
        return result;
    }

  //all devices support etc, so it has lowest value
    private static int GetTargetVersion(AndroidBuildSubtarget target)
    {
        switch (target)
        {
            case AndroidBuildSubtarget.ETC:
                return 4;
            case AndroidBuildSubtarget.ATC:
                return 5;
            case AndroidBuildSubtarget.DXT:
                return 6;
            case AndroidBuildSubtarget.PVRTC:
                return 7;
            default:
                return 0;
        }
    }

    private static void BuildAndroid(AndroidBuildSubtarget target)
    {
        if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android)
            EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTarget.Android);
        EditorUserBuildSettings.androidBuildSubtarget = target;

        //to include more build options use bitwise OR, for instance: BuildOptions options = BuildOptions.Development | BuildOptions.ShowBuiltPlayer;
        var bo = BuildOptions.None;
        PlayerSettings.Android.keystoreName = "keystore_location";
        PlayerSettings.Android.keyaliasName = "alias";
        PlayerSettings.Android.keystorePass = "password";
        PlayerSettings.Android.keyaliasPass = "password";

        PlayerSettings.bundleIdentifier = "yourbundle";
        PlayerSettings.Android.bundleVersionCode = GenerateVersionCode(target);

        try
        {
            string apkName = "your_apk_name"
            BuildPipeline.BuildPlayer(
                (from scene in EditorBuildSettings.scenes where scene.enabled select scene.path).ToArray(), apkName,
                BuildTarget.Android, bo);
        }
        catch (Exception e)
        {
            Debug.Log("Error: " + e.Message);
        }
    }
Как-то так. Да, при выливании в сторы, нужно заливать билды в том же порядке. Если перепутать, то стор просто отвергнет билд с меньшим номером.

четверг, 13 марта 2014 г.

Успешная игра - каждому по приоритету

Мою квартиру обокрали. Тупо взломали дверь ломом. Видно, взломали первую попавшуюся квартиру, все шкафчики открыты - видимо, искали повсюду. Самое странное - сперли только ноутбук. Хотя у меня, гиканутого нерда, окулусов, райзер хидр и прочей техники было на гораздо большую сумму. Люди не знали их стоимость, поэтому сперли только один ноутбук - можно сказать, легко отделался.


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

Совсем недавно нам удалось реализовать игру, которая подходит под мое определение успешности, как программиста. Нет, это не road smash, хотя, судя по статистике, она очень понравилась пользователям и наверняка приносит хороший доход.

Игра называется Clumsy Fino (тыц для ios), это очередной клон Flappy Bird с респавном и дракончиком. Вроде ничего примечательного, но игра успешна. Объясню почему:

  1. У нас отличная команда - множество непересекающихся скиллов. Альберт Александровский взял на себя роль издателя, он занимался продвижением, поиском подходящих запросов, делал завораживающие картинки. Игорь Фомичев делал игровую часть, делал баланс. Я прикручивал плагины, занимался неигровой частью. Каждый занимался тем, что он лучше всего умеет. В условиях "экстремальной разработки" эта тактика сыграла как нельзя лучше.
  2. С самого начала мы решили делать максимально быстро. Первая версия игры была сделана за два дня по вечерам, содержала в себе все мастхэвы и вылита в сторы. Это была самая продуктивная итерация на моей памяти.
  3. Вторая итерация заняла чуть больше времени(пять дней "грязного времени", где-то три человекодня на всех), за это время мы полностью обновили GUI, добавили важную фичу - респавн птицы при смерти. После апдейта количество скачиваний удвоилось (спасибо, Альберт!), а игровая сессия стала больше в среднем на три минуты (спасибо, Игорь).
  4. В разработке не было факапов. Вообще. Причины две: 
    1. мы делали только те фичи, которые казались нам нужными. Те, которые не очень привлекательны игрокам или трудозатратны - мы не делали. Наконец-то смогли применить принцип Парето.
    2. Никто не лез в чужую область, каждый делал то, что умел лучше всего. Минимум ресерча, просто берешь и получаешь результат. А через четыре часа люди радуются этому результату.
  5. Не знаю, как остальные ребята, лично я получил огромное удовольствие. Это было подобно очень короткому кэмпу, где ты просто делаешь то, что тебе нравится. Результат не заставил себя ждать. 
  6. Эта игра уже окупила наши трудозатраты. Доход до сих пор растет :)
Не знаю, как другие программисты, но я очень хочу, чтобы моими продуктами пользовались. Очень сладостно ощущение того, что ты делаешь не зря. И совершенно пофиг, сколько денег от этого ты зарабатываешь.




За ноутбук не сильно обидно - там стоял deep freeze, хрен они там смогут им воспользоваться. При более тщательной ревизии всех вещей оказалось, что эти уроды сперли купленный вчера торт! Спереть ноутбук и торт вместо того, чтобы спереть дорогостоящую технику? У меня нет слов. 

    четверг, 13 февраля 2014 г.

    Делайте быстрее!


    Вася и Петя одновременно начали писать один и тот же продукт.

    Вася был «ориентирован на результат» и начал сразу писать говнокод не продумав толком архитектуру.
    А Петя месяц разрабатывал архитектуру, месяц делал удобный интуитивный интерфейс, которому позавидывал бы Джони Айв, потом месяц писал тесты, потом два месяца писал сам код и получил идеальное стабильное приложение.
    Но Вася выпустил уже через месяц первую версию программы, пусть и не идеальную, пусть с багами, но рабочую, и начал её продавать. Ещё через месяц выпустил вторую версию исправляющие баги первой и добавляющие новые баги. Ещё через месяц на доходы от продаж нанял двух толковых программеров, которые за два месяца перелопатили весь код, согласно пожеланиям пользователей допилили интерфейс и выпустили третью версию программы.
    Итого, через пять месяцев у Васи было два работника, куча клиентов и сносно работающее приложение отвечающее желаниям клиентов.
    У Пети было вылизанное никому не известное приложение, минус на банковском счёте и ни одного клиента.

    В завершение этого выдуманного примера можно сказать, что через полгода Вася купил все наработки Пети, Петю взял в штат тестировщиком, а сам по пьяни разбился на своём новеньком Туареге

    История №1

    В мае 2013 мы начали делать технобол, сделали прототип, придумывали фишки, участвовали в конкурсах (в результате которых появлялись забавные модификации). Все вроде хорошо. Первая действительно хорошая демка была готова к ноябрю. Между маем и ноябрем было сделано две промежуточные версии, людям была показана вообще только одна. И совершенно немногие знали о том, что делается игра.К первой демке мы осознали, что делаем все неправильно и надо сильно менять геймплей (техническим языком - выкидывать половину того, что все написано). Было принято волевое решение: выливаем существующую демку как есть и, учитывая ошибки, начинаем делать нормально.

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


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


    История №2
    Игра road smash делается с февраля 2013, скоро ожидается полноценный релиз, я присоединился в конце сентября. Ребята делали игру, завели блог разработчика, к первой публичной бете (которая была, честно говоря, пре-альфой) были фанаты, игра за месяц набрала миллион загрузок без рекламы и продвижения. Мы пережили падение сервера, кучу клиенстких багов, вопли детей "Разраб, верни галду!", факапы длиной в месяц, разработка фич, которые никому не нужны. Большинство факапов связано с привычкой все усложнять, болезнью абстрактно ориентированного программиста
    Итог: игра делается в сумме год, публичная бета(которая пре-альфа) неожиданно взлетела.
    Совершенные ошибки: over-engineering, построение сложных систем, вместо того, чтобы сделать по простому. Разработка того, что не принесет пользы.
    Вывод: yagni и почаще думать головой. Головой думать всегда полезно, не только в разработке.
    Простота - ключ к надежности
    (с)Дейкстра

    Хотел еще историю написать, лучше ограничусь этими двумя.
    Общая мораль: никому не интересен идеальный продукт, выпущенный поздно. Всем нужно прямо здесь и уже вчера. Поэтому если вы собрались делать мобильные приложения, то лучше делать максимально просто и быстро, получая фидбек как можно скорее. И в первую очередь нужно делать самое важное, а потом уже остальное.

    P.S. Потратил полтора часа на написание статьи, а у самого лежит бизнес-план, пишущийся с января и сделанный на 80%. Ну не идиот ли я? 



    суббота, 25 января 2014 г.

    UIViewController for dummies

    UIViewControllers became harder to understand with Storyboards - there are lots of auto-layouts you might not handle.

    Just a liitle hint to help debugging:

    UIViewController's method invoke sequence is as below
    • awakeFromNib
    • viewDidLoad
    • viewWillAppear
    • viewWillLayoutSubviews
    • viewDidLayoutSubviews
    • viewDidAppear
    • viewWillLayoutSubviews
    • viewDidLayoutSubviews
    So, if you have views building by code, make sure that storyboard layout will not break it into mess.

    понедельник, 9 декабря 2013 г.

    5 things you need to implement in any mobile game

    русская версия - здесь

    I have three posts in drafts(called "You don't need server" and "3 reasons to switch from Asset Server", "Execute impossible to pardon: how to deal with hackers"), but decided to write this one just because it seems more timely than others.

    We had discussion with Yuri about what makes any game perfect and what features must be implemented and come up with some common and must-have features:

    • in-app-purchases. No comments here. If you want to make money from your game, you'd better have well-tested, stable in-apps;
    • analytics. The thing is you can not predict user behaviour unless you have any clue what your user is doing in the game. And it's better to have as much information as possible - you know where is your problem in your game mechanics and can easily fix it up;
    • local and remote push notifications. We didn't implement them in the first releases of Road Smash and we feel regret about it. What is the easiest way to let the users play updated game? Obviously, push notifications.I know that users may hate pushes and remove your app just because they are got annoyed by your app, but in 70% it works.
    • Anticheat protection. You know, cheaters always exist, especially on android and you need to prevent your game from being hacked. Somebody may say that you need to protect everything, somebody have an opinion that you need to give up on hackers and let them play. I think that hackers wouldn't pay. They would keep hacking the game until they find something or even delete it from their devices, but they would never pay. So I came up with the decision here: protect only that things that cost money or may affect other users. In business terms there are two most important things for developer: users that bring money. When I say money, I mean in-apps, when I say affect other users, I mean scoreboard, social, competition parts of your game. And you need to keep tiny balance between letting gamers play comfortably and prevent players from stealing your money.
    • Ads. I underestimated role of ads, thought it's annoying and may push your user away. But I was wrong. Firstly, you can integrate ads, so they will become part of your gameplay. Users will want to see some ads just to get some goods instead. And of course, it is a great chance to  get money from users that will never pay :)
    The main purpose of any game is to have a lot of users in game and keep them as long as long as possible, letting them pay for any reason. In that meaning these five features described must be implemented, well-tested as soon as possible.

    And to make this post more concrete, I have some links to assets to share with you to get that features implemented in the easiest way.
    • in-apps: use unibill. It supports app store, google play, windows store out-of-box, including receipts, subscriptions, etc. Must-have!
    • analytics: there's a wonderful and free plugin, called GameAnalytics. It supports events, heat maps, gameplay stats. If you are fan of old-school analytics, you may check out for my wrapper to Flurry plugin in my github repository, I'll push all the code as soon as I find free time for it. 
    • push notification: there are too many good plugins in assets, so I don't know what's the best option here. I'd recommend pushwoosh service, because it provides a lot of features out-of-box, but let me leave this topic open.
    • anticheat: well... there's no common solution as it really depends on what kind of game you are working on. Maybe some kind of playerprefs encryption, in-apps encryption? Did I miss something?
    • ads: there is a bunch of different options: admob, tapjoy, adcolony, applifier. Some of them provide banner and image ads, some of them kind of video or partnership ads. Choose one you like the most.

    вторник, 19 ноября 2013 г.

    Unity Continuous Integration

    And finally Unity Continuous Integration (CI) plugin is on Unity Asset Store! Check it out: https://www.assetstore.unity3d.com/#/content/12695.

    I spent a day setting up Jenkins build machine on Road Smash project and spent one more day to unify builder script and writing documentation, so I can use it on any unity project with any CI framework and any CVS systems, including asset server, git, svn. It works out-of-box, the only thing you need is to call the right method(like BuildAndroid, BuildIOS) from command line(OSX, Windows).

    For now Unity CI supports:

    • iOS build
    • Android builds (including different builds for different textures format, including ATC, ETC, DXT & PVRTC). You can call a method to build 5 different apks, which will support different textures from AndroidManifest.xml and may be uploaded to Google Play as multiple apks.

    I didn't include changing version code to plugin, because every project may have its own policy and I may break it, but it is really easy to add there. I'll write another post about all android building stuff later.

    I'm planning to include unit-testing plugin to CI and make it portable. I'll finish it up as soon as I'll have a free time for it.

    P.S. Unity Technologies rejected my Flurry plugin(full iOS & Android, Windows Phone beta support) for its own internal business reasons. Is anybody interested in it?

    среда, 13 ноября 2013 г.

    Codility prefix problem

    I got a problem from codility site and was asked to solve it. Here's a link on stackoverflow. In two words: assume we have a string S and we need to choose such a prefix P of S, that product of its length and occurrence in S will be maximum.

    For example, S = "abababa" has the following prefixes:
    "a", whose product equals 1 * 4 = 4,
    "ab", whose product equals 2 * 3 = 6,
    "aba", whose product equals 3 * 3 = 9,
    "abab", whose product equals 4 * 2 = 8,
    "ababa", whose product equals 5 * 2 = 10,
    "ababab", whose product equals 6 * 1 = 6,
    "abababa", whose product equals 7 * 1 = 7.
    I like problems like that. It has obvious solution in O(N ** 3) time, elegant and easy to guess if you know z-algorithm O(N ** 2) solution and tricky O(N) solution.

    Ok, first solution is naive brute-force. You choose length from 1 to N, get a prefix of its length and count occurences by brute-force searching. So, choosing length takes O(N) time and brute force takes O(N ** 2) time, totally O(N ** 3).

    Ok, it doesn't really what we want to achieve. Let's think how we can reduce complexity. Anyone who ever learned strings knows that string occurence can be searched in O(N) time, so with a little code we can achieve O(N ** 2) time compexity solution. I used Z-algorithm. Here's the code:



    vector<int> z_function(string &S); //z-function, z[0] = S.length()
    
    int calculate(string &S) {
     vector<int> z = z_function(S);
    
     int ans = 0;
     for (int i = 1; i <= S.length(); ++i) { //iterate on length
      int k = 0; //counter of occurrences
      for (int j = 0; j < S.length(); ++j) 
       if (z[j] >= i) ++k;  
    
      //update answer
      ans = max(ans, i * k);
     }
            return ans;
    }
    

    But it is still not enough though. How we can get rid of nested loop? We can precalc prefix occurences with Z-algo and finding them in constant time. Initially Z-algo returns an array, where z[i] is the length of longest substring starting from S[i], which is also prefix of S. For "abacaba" it will return {7, 0, 1, 0, 3, 0, 1}. And we know that if prefix of S with length i has N occurrences, then all smaller prefixes of S will have at least N occurrences. So, we create an array of occurrences, iterate over z-array, updating new created array and finally iterate over it backwards, updating as explained above.


    vector<int> z_function(string &S); //z-function, z[0] = S.length()
    
    int calculate(string &S) {
     vector<int> z = z_function(S);
    
     int n = S.length(); 
     vector<int> cnt(n + 1);
    
    
     //cnt[i] - count of i-length prefix occurrences of S 
     for (int i = 0; i < n; ++i) 
      ++cnt[z[i]];
    
     //if cnt[i] is in S, cnt[i - 1] will be in S
     int previous = 0;
     for (int i = n; i > 0; --i) {
      cnt[i] += previous;
      previous = cnt[i];
     }
    
     int ans = 0;
     for (int i = 1; i <= S.length(); ++i) { //iterate on length
      int test = cnt[i] * i;
      //update answer
      ans = max(ans, test);
     }
    
     return ans;
    }
    

    We separately count occurrences in O(N) time and find the answer in O(N) time as wanted.

    For me it is the most easiest way to solve this problem. If anyone knows better solution, please kindly share.

    четверг, 31 октября 2013 г.

    Сжатие текстур андроида и Unity3d


    Unity! Я тебя обожаю! Ты из под-коробки поддерживаешь разные типы сжатия в билдах! Не надо руками конвертить, нет больше извратов с AndroidManifest. Поменял опцию и билди! It just automagically works!


    Для любителей CI - поменять значение
    EditorUserBuildSettings.androidBuildSubtarget
    http://docs.unity3d.com/Documentation/ScriptReference/AndroidBuildSubtarget.html

    Для ленивых: разные типы сжатия и девайсы, поддерживающие их:
    DXTS3 texture compression, nonspecific to DXT variant. Supported on devices running Nvidia Tegra2 platform, including Motorala Xoom, Motorola Atrix, Droid Bionic, and others.
    PVRTCPowerVR texture compression. Available in devices running PowerVR SGX530/540 GPU, such as Motorola DROID series; Samsung Galaxy S, Nexus S, and Galaxy Tab; and others.
    ATCATI texture compression. Available on devices running Adreno GPU, including HTC Nexus One,
    Droid Incredible, EVO, and others.
    ETCETC1 texture compression (or RGBA16 for textures with alpha), supported by all devices.

    среда, 23 октября 2013 г.

    Баг шейдера при операции mul

    Натолкнулись на баг в шейдере unity:

    • шейдер ломается частично, а именно: чем ярче пиксель, тем больше он стробит (если оттенок черного - все норм, ярче - становится заметней). То есть шейдер работает, fallback-a не происходит!
    • происходит только при операции умножения матрицы 4x4 на вектор4.
    • воспроизводится только на адрено устройствах. 
    И это при условии, что мы потом отбрасываем последнюю часть и приводим полученный ответ к vector3.
    Называется, поймали эксепшн, который никак нельзя додуматься поймать.



    В общем, у кого стробление пикселей в шейдере, напишите собственный mul (благо, это просто несколько операций dot).

    вторник, 8 октября 2013 г.

    Интересное об app store review

    Кто хоть раз заливал приложение в app store, знает, что нужно пройти  этапы ожидания ревью, самого ревью и ready for sale. Ну так вот, несколько интересных моментов об ожидании в app store и ускорении ожидания (только ios и под каждым словом надо ставить имхо, ибо только мой опыт):
    • Среднее время review - 5 рабочих дней
    • Походу действительно весь штат apple сидит в США, поэтому надо еще рассчитывать их часовой пояс(где-то -7 UTC), их локальное рабочее время. Это важно в случае написания писем.
    • Expedited app review(это запрос на review без ожидания в случае, если там оказался критический баг или вот-вот начнется пиар-кампания) работает магическим образом. Более того, мне кажется, это сильно зависит от настроения отвечающего на запрос. Однажды я нашел некритичный баг после двух часов релиза и получил добро на expedited review. В другом случае я за пять дней до пиар-выставки отправил приложение и получил отказ в досрочном ревью. В общем, сделал следующий вывод: на expedited app review нельзя полагаться.
    • Форма Contact us работает оперативно. Даже если они не отвечают, в течение часа что-то происходит.
    • Статья хабра не сработала. Или я не перескочил минимальный порог приложений для ожидания, или звезды ушли в параллельную вселенную, но это нисколько не повлияло на время ожидания.
    • Если прошло более пяти рабочих дней, то стоит черкнуть письмецо через Contact us. Реально, они начинают проверять! 
    • А теперь самое интересное: если в ожидании ревью висит несколько приложений и написать письмо(а там в форме надо написать id-шник приложения, у которого ты хочешь обновить статус проверки/ожидания проверки), то начинают проверять сначала не то приложение, которое ты упомянул в письме. Я думал, это одноразовый баг, но такое поведение повторялось у меня несколько раз! И до сих пор в ожидании ревью висит приложение, на которое я отправил запрос проверить в первую очередь, хотя остальные давно проверены.

    понедельник, 16 сентября 2013 г.

    Optimize performance in Unity3d: practical notes

    (Russian post goes here)
    I wrote an article in russian about unity performance optimization recently. In this topic I want to share some small, but efficient practical tips with you. Here we go:


    • if you're running out of memory, use proper texture compression. PVRTC is acceptable for iOS, DXT for android. And don't forget about mip-maps. I know, it gonna increase binary size by 30%, but it's worth it. Don't be so modest to limit max size to textures. 
    • If you don't need shadows in renderers (for example, in UI or something), just uncheck "Receive shadows" & "Cast shadows" options. They are always on by default.
    • Don't forget about batching. And yes, static batching allows much more than dynamic, so you don't move object, mark it as static. If you want to combine meshes runtime, use StaticBatchingUtility.Combine.




    • Take into account renderer settings, physics settings of project (Edit->Project settings)
    • Anti-aliasing, texture quality, shadows and vsync have many options. Just experiment with them to gain best performance/quality.
    • Uncheck layer collision checks you don't need to calculate.
    • If you don't really need physics, change fixed timestep.
    • Don't move static objects. 
    • Design your level, so your camera will not take a lot of objects, then camera culling will save a lot of frames for you.
    • Mesh collider checks are very expensive! It's better to use double-check technique: firstly make a capsule or box collider and check with it's collision, then check with mesh collider.
    • Lights. Know the difference between point & directional, use them properly. 
    • Cache  transform, collider, rigidbody. We use CachedMonoBehaviour in our projects and happy with that (P.S. From 4.1 Unity did a lot of work with GetComponent optimization, but CachedMonoBehaviour is still faster).
    You may achieve better results if you know instruments you use. These tips are really easy to use and sometimes they give more performance than it's expected. Use it and know, premature optimization is root of all evil.


    P.S. We are applying all this tips in game named technoball, so please free to try it and tell me what you think about it.
    P.P.S We made a lot of work and prepared an awesome update, stay in touch!

    воскресенье, 8 сентября 2013 г.

    Мысля про скриншоты

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

    1. нажать prtscr
    2. открыть paint(быстрее, чем нажать пуск, набрать mspaint я не умею)
    3. вставить рисунок
    4. сохранить
    5. открыть файлопомойку, залить файл
    6. получить ссылку, поделиться


    Как можно убыстрить этот процесс:

    • Воспользоваться связкой макинтош+дропбокс(кхе-кхе). В маке я делаю в три операции: делаю скриншот сразу в десктоп (cmd+shift+3), кидаю в паблик дропбокс, получаю ссылку из дропбокса
    • Воспользоваться сервисом snag.gy. Смысл таков: нажимаете prtscr, открываете сайт, нажимаете вставить(если нужно, корректируете встроенным редактором), сохраняете, делитесь. Примерно так же быстро, как в маке.
    • Наверняка есть уже готовые сервисы-демоны, которые висят в системе, при нажатии магического шортката делают скриншот, аплодят в файловую помойку и показывают ссылку. Если кто знает о таких, пожалуйста, поведайте! Если нет, скажите, если бы была такая прога, скажите, пользовались бы вы им? 

    четверг, 22 августа 2013 г.

    Facebook left-side menu with storyboards

    Well, facebook-like left-side menus became really trendy UI pattern, so everyone tries to insert it to their projects.
    Functionally it's primitive: you have left-view in background, and there's drag gesture recognizer, which "catches" foreground view and moves it on xz-direction.

    There are lots of open source implementations for ios:
  1. ViewDeck
  2. SASlideMenu
  3. JWSlideMenu
  4. DDMenuController
  5. PKRevealController
  6. ECSlidingViewController
  7. MWFSlideNavigationViewController
  8. MFSideMenu
  9. HHTabListController
  10. MTSlideViewController
  11. SlideViewController
  12. MTStackViewController
  13. MMDrawerController

  14. I'd recommend to use ViewDeck and SASlideMenu, because they are the most functional and easiest to integrate (For android developers it's worth to mention this stackoverflow answer).

    But there was a problem related to storyboards: there's no out-of-box solution to integrate left-side menu with storyboards power! Recently I found really easy to use framework: ViewDeckStoryBoards. Hooray, there's no need to revert back to xibs nightmare! It's based on viewDeck, there's a little code needed(just to overwrite left side & main windows). It supports arc, storyboards. 



    суббота, 29 июня 2013 г.

    Принцип самурая или почему надо радоваться преждевременным ошибкам

    Делаем игру с командой и часто инспектируем код друг друга.
    Натолкнулся вот на это:(ссылка)


    1.   void Awake ()
    2.   {
    3.     saveDirPath = Application.dataPath + "/Levels/";
    4.     
    5.     if (buttonSave != null) {
    6.       UIEventListener.Get (buttonSave.gameObject).onClick += OnButtonSave_Click;
    7.     }
    8.     if (buttonLoad != null) {
    9.       UIEventListener.Get (buttonLoad.gameObject).onClick += OnButtonLoad_Click;
    10.     }
    11.     if (buttonInputDialogOk != null) {
    12.       UIEventListener.Get (buttonInputDialogOk.gameObject).onClick += OnButtonInputDialogOk_Click;
    13.     }
    14.     if (buttonInputDialogCancel != null) {
    15.       UIEventListener.Get (buttonInputDialogCancel.gameObject).onClick += OnButtonInputDialogCancel_Click;
    16.     }
    17.     if (buttonselectLevelDialogClose != null) {
    18.       UIEventListener.Get (buttonselectLevelDialogClose.gameObject).onClick += OnButtonselectLevelDialogClose_Click;
    19.     }
    20.   }
    Коротко: есть кнопки, и при запуске подписываются обработчики. Все хорошо, код абсолютно безопасен. Но он плох. Почему?