Как Ам Ням из Cut the Rope 2 переселялся на Amazon Fire Phone

    Недавно компания Amazon выпустила новый смартфон Fire Phone, и ZeptoLab поступило предложение модифицировать для него игру Cut the Rope 2. Мы добавили туда поддержку нововведений смартфона, и игра вошла в список предустановленных приложений.

    Девайс приехал к нам задолго до официального релиза. Девкит был помещен в специальный короб, защищавший дизайн устройства от любопытных глаз, и, главное, – от объектива фотокамер. Впрочем, все самое интересное заключалось в «начинке» телефона.




    Главная особенность первого смартфона от Amazon – четыре фронтальных камеры, которые следят за глазами пользователя и передают данные об их положении в программы. Это создает ложный 3D-эффект: можно слегка поворачивать изображение на экране взглядом, «оживляя» иконки и другие элементы интерфейса. Особенно сильное впечатление производит просмотр карт: здания приобретают объем, и их можно рассмотреть с любой стороны, просто передвигая взгляд по экрану.

    Другой особенностью телефона являются Hero Icon и Hero Widget. Fire Phone работает на Fire OS, модификации Android, и в нем вместо обычного домашнего экрана присутствуют экраны Carousel и App Grid.



    App Grid – это привычная «сетка» установленных приложений, а Carousel – набор последних использованных программ с горизонтальной прокруткой. Для каждого приложения экран делится на две части: в верхней отображается трехмерная иконка Hero Icon, а в нижней – набор элементов Hero Widget. В виджете содержится набор строк для вывода данных по приложению без необходимости запускать его; эти элементы также могут выполнять действия для быстрого доступа к частям приложения, даже если оно закрыто или свернуто.

    Для Cut the Rope 2 поддержка виджета означает возможность быстрее приступать к игре, заходя сразу в меню выбора уровня, в обход основному меню и одному из двух экранов загрузки. Достижения также можно посмотреть быстрее, ведь необходимость искать путь к этому элементу через меню игры отпадает.

    Для модификации Cut the Rope 2 под Fire Phone нам нужно было сделать трехмерную иконку, научить игру создавать виджет и добавить в нее поддержку команд этого виджета.

    Создание Hero Icon

    В Fire Phone по умолчанию для всех приложений создается трехмерная Hero Icon –
    плоский объект с наложенной текстурой обычной 2D иконки.



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

    От Amazon нам достался toolset, инструкция по созданию иконки в Autodesk Maya и готовый sample.

    В точности повторяя все указанные в инструкции пункты, мы сделали первый вариант иконки:



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

    Мы начали делать иконку параллельно с разработкой виджета, и не имели возможности проверить, как именно она будет смотреться на экране смартфона. После первого же экспорта модели в формат .vbl, сделанный Amazon для HeroIcon, мы выяснили, что альфа-канал отображается некорректно. Поэтому мы перешли ко второму варианту и сделали простую иконку на основе 2D-версии, которая нам очень понравилась.



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

    Во-первых, ошибки при экспорте модели в формат vbl:

    1. [ERROR]: Skeleton: No bind pose specified for node AnimatedNode1 in scene ic_myappname_appsgrid that has node animation, create a «bind» animation with keys on animated objects.

    Решение проблемы нашлось на forums.developer.amazon.com: оказывается, нужно было добавить еще одну пустую анимацию с названием «bind». В официальной инструкции такой информации нет.

    2. [ERROR]: StateMachine: Detected self looping state with transitions that consists of empty sequence(s) resulting in infinite loops.

    Эта ошибка лечится достаточно просто: нужно перейти в Tools Euclid, который вы устанавливаете в Maya согласно инструкции, зайти в редактирование StateMachine, кликнуть правой кнопкой мыши на «State» и выбрать «Remove Self Connection».

    Также было несколько проблем, решения которым мы так и не нашли, как и не смогли определить шаги к их повторению.

    К примеру, наша иконка никак не хотела отображаться лицевой стороной по нужной оси: как бы мы ее ни поворачивали – по оси Y, Z или X – в любом случае на устройстве отображался вид сбоку.

    Поскольку решить проблему в рамках сделанной по инструкции сцены мы не смогли, мы взяли готовые сэмплы от Amazon и импортировали нашу иконку в сцену с иконкой-шаблоном. К нашей радости, экспорт прошел без ошибок, и на девайсе красовалась 3D-иконка Cut the Rope 2 в нужном нам виде. И это несмотря на то, что в sample-сцене не было, например, описанной в инструкции анимации.

    Во-вторых, вылезла вот такая ошибка:

    [ERROR]: MaterialExporter: Unable to open material template «MaterialApplication.app.template»

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

    Разработка команд для виджета

    На этом наша работа над иконкой закончилась, но еще нужно было придумать, что выводить в виджет. Если приложение не устанавливает свой собственный виджет в систему, на его место выводится стандартный список товаров в Amazon, близких по тематике к данному приложению. Для Cut the Rope 2 смартфон выводил в список несколько игр из Amazon Appstore. Заменяя стандартное поведение, мы настроили в виджете отображение игровой информации – количество собранных звездочек, полученных медалей и недавно использованных элементов, вроде телепортов или подсказок. Эти строки виджета также позволяют переходить на карту уровней игры, на экран достижений Amazon GameCircle и в несколько разделов внутриигрового магазина.



    В качестве справочных материалов у нас были три обучающих документа от Amazon по Home Screen, Hero Icon и Hero Widget. Также в нашем распоряжении был SDK для работы с новым устройством и небольшой справочный материал по его установке и настройке.

    С установки SDK для Fire Phone мы и начали. Проблемой оказалось то, что SDK работает только с Android API не выше 17, и нам пришлось вручную понижать версию среды разработки (сделать это автоматизированно не удалось). Для этого потребовалось удалить папки из Android SDK и скачать старые версии компонентов (SDK Tools 22.6.3, SDK Build-tools 19.0.1, API 17 Platform tools) по найденным в интернете ссылкам. После этого установка SDK от Amazon не составила больших проблем.

    Далее надо было добавить строку подключения библиотеки в AndroidManifest:
    <uses-library android:name="com.amazon.device.home" />
    

    Сами элементы виджета создаются из текста программы. После создания, их нужно помещать в списки и группы, а затем инициализировать ими виджет. Таким образом элементы виджета получают стиль, основной, второстепенный и третичный текст, а также иконку и intent.
    final ListEntry listEntry = new ListEntry(context)
                .setContentIntent(heroIntent)
                .setVisualStyle(SHOPPING)
                .setPrimaryText(primaryText)
                .setPrimaryIcon(imageUri);
    listEntry.setSecondaryText(secondaryText);
    listEntry.setTertiaryText(tritaryText);
    ...
    listEntries.add(listEntry);
    group.setListEntries(listEntries);
    groups.add(group);
    …
    mWidget.setGroups(groups);
    

    Интент для виджета создается через специальный класс:
    heroIntent = new HeroWidgetActivityStarterIntent("com.zeptolab.ctr2.CTR2Activity");
    

    Затем нужно получить запрос от виджета. Мы решили сделать код проекта расширяемым для работы с возможными схожими виджетами или интентами. На уровне фреймворка ZFramework, который используется нашими проектами, содержания поступающих в программу интентов отлавливаются в onCreate() и onNewIntent(). Мы используем виртуальный обработчик интентов IntentInterpreter в фреймворке, а на уровне данного проекта – виджета от Amazon – конкретный класс WidgetIntentInterpreter для обработки интентов виджета.

    Нам также надо было учесть, что в отличие от стандартных Android Intents, в случае с виджетом от Amazon в качестве дополнительных данных с интентом можно передавать только строки. Поэтому абстрактный класс разбора событий у нас получил методы перевода между строками и ID событий:
    public abstract class AbstractIntentInterpreter {
        public abstract int processIntent(Intent intent);
        public abstract int getIdForString(String stringData);
        public abstract String getStringForId(int Id);
    }
    

    Он используется примерно так:
    int viewRequested = intentInterpreter.processIntent(intent);
    view.addEvent(viewRequested);
    

    Проектный код с помощью WidgetIntentInterpreter берет сообщение из интента и переводит ее в запрашиваемый ID:
    String extra = intent.getStringExtra(HeroWidgetActivityStarterIntent.EXTRA_HERO_WIDGET_DATA);
    result = getIdForString(extra);
    

    Вторая строка в этом коде преобразует текстовую строку в ID запроса, если полученная в параметрах строка соответствует одному из возможных запросов.

    На уровне фреймворка класс View сохраняет ID запроса в класс EventDispatcher, который предоставляет интерфейс для добавления, считывания ID событий и их стирания:
    public class ZEventDispatcher {
        ...
        public ZEventDispatcher(android.content.Context context) ...
        public void clearEvent() …
        public int getNextEvent(boolean clearAfterRead) …
    }
    

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

    Далее оставалось только считать в проекте ID события, используя JNI. К примеру, вот так:
    int getIntentEvent(bool clearAfterRead)
    {
        JNIEnv* env = JNI::getEnv();
        jclass cls = env->GetObjectClass(JNI::eventDispatcher);
        jmethodID meth = env->GetMethodID(cls, "getNextEvent", "(Z)I");
        jint jEventId = static_cast<jint>(env->CallIntMethod(JNI::eventDispatcher, meth, clearAfterRead));
        env->DeleteLocalRef(cls);
        int result = jEventId;
        return result;
    }
    

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

    Алгоритм обработки запроса виджета при запуске программы примерно следующий:

    1. Событие onCreate Java-класса приложения обрабатывает intent, получая ID запроса;
    2. Полученный ID сохраняется в разборщике событий EventDispatcher;
    3. При создании экрана начальной загрузки ID запроса считывается классом RootView;
    4. Экран загрузки получает в качестве параметра ID запроса и, в зависимости от него, выбирает набор ресурсов для подгрузки;
    5. При завершении экрана загрузки в зависимости от ID запроса показывается определенный экран программы.


    Небольшая сложность возникла у нас с правильным получением интента на разворачивании свернутого приложения. Сначала этот интент ловился методом onResume, однако это событие восстанавливает изначальный intent, которым приложение было исходно запущено. Нам же нужно было получать новый intent при каждом новом разворачивании. Решением стало добавление в приложение свойства

    <activity … 
    android:launchMode="singleTop">
    

    Оно позволило получать новые intents в методе onNewIntent(). В итоге, при разворачивании приложение стало получать свой уникальный набор параметров.

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

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

    Будущие перспективы

    Среди возможных перспектив разработки – улучшение набора команд виджета. Его очень просто поменять в коде, а затем, используя код игры, обновить виджет в системе телефона, когда игра запущена.

    Говоря о вариантах развития виджета, следует также отметить, что после добавления поддержки Amazon Hero Widget в Cut the Rope 2 у нас появился код, который можно будет при желании легко адаптировать под другие виджеты на Android или iOS.

    Это был рассказ о создании модификации Cut the Rope 2 под Amazon Fire Phone. Надеемся, наш опыт будет вам полезен.



    Ну, а что касается экспериментов с новыми технологиями – у нас в ZeptoLab это частая практика, и в связи с этим нам приходится время от времени пересматривать требования для вновь приходящих на технические задачи людей. В частности, у нас обновилось тестовое задание на позицию C++ разработчика, и, чтобы не вводить хабражителей в заблуждение устаревшими, выкатываем его самую свежую версию.

    Необходимо написать простую версию классической игры Asteroids:
    • Ведем космический корабль через астероидное поле, управление на ваше усмотрение;
    • Задача – продержаться максимальное время, избегая астероидов или стреляя по ним. Когда снаряд попадает в астероид, он разлетается на осколки поменьше, а осколки, в свою очередь, полностью уничтожаются при попадании;
    • Астероиды должны представлять собой случайно созданные многоугольники.


    Требования к прототипу:
    • Игра пишется под iOS или Android-платформу (строго);
    • Отрисовка должна быть реализована с помощью OpenGL ES версии 2.0 или выше (можно ограничиться геометрическими фигурами из линий);
    • Если пишете под Android, игра должна быть написана с использованием NDK (С++) – программа должна быть целиком на С++, на Java может быть только обвязка кода;
    • Если пишете под iOS, игра должна быть написана на С++. На Objective-C допускается только обвязка кода;
    • Игра должна быть написана без применения каких-либо сторонних библиотек вроде Cocos2D или GLKit.


    На адрес job@zeptolab.com мы по-прежнему принимаем ваши тестовые задания круглосуточно.
    О том, как мы их проверяем, мы уже ранее писали. А чуть ранее мы немного рассказали про систему обучения разработчиков, которую мы ввели в этом году – и далее будем ее только развивать, не пропустите.

    В ближайшее время надеемся порадовать вас новыми интересностями, происходящими в Zepto-пространстве.

    Всегда ваш,
    ZeptoTeam
    ZeptoLab 48,00
    Компания
    Поделиться публикацией
    Комментарии 3
    • 0
      Использование фронтальных камер в самой игре планируется?
      • +2
        На данный момент – нет. В ранней версии CTR2 была очень похожая возможность, поворот сцены в превдо-3d, и она работала на показаниях акселерометра. Выглядело круто, но через 20 минут от такой игры начинала кружиться голова.
      • +3
        «инструкция по созданию иконки в Autodesk Maya» помоему это похоже на микроскоп и гвозди

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое