пятница, 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, хрен они там смогут им воспользоваться. При более тщательной ревизии всех вещей оказалось, что эти уроды сперли купленный вчера торт! Спереть ноутбук и торт вместо того, чтобы спереть дорогостоящую технику? У меня нет слов.