Писать в наши дни нативное приложение на Flash — казалось бы, дикая идея. Тем не менее, она имеет право на жизнь. Разработчики игры PixelMogul поделились рассказом о том, как они писали игру для платформы iOS целиком на ActionScript.
В первой части материала мы обсудили базовые проблемы, с которыми сталкивается любой разработчик игр, а также некоторые дизайнерские решения. А сегодня мы переходим к технологическим аспектам разработки приложения.
Выбор подходящего фреймворка, повышение производительности, интерфейс и так далее
Выбор подходящего фреймворка
Выбор игрового движка (фреймворка) отчасти напоминает подготовку к свиданию. В статье «The Game Engine Dating Guide: How to Pick Up an Engine for Single Developers» Стивен Иттерхейм рассматривает важные вопросы, связанные с этой темой. Мы последовали некоторым его рекомендациям, и они действительно очень нам пригодились. Советы Стивена применимы не только к разработке игр и будут полезны при поиске фреймворка любого рода.
- Подбирайте знакомый язык программирования.
Пожалуй, изучить основы языка программирования не так уж и сложно, но нужен немалый опыт, чтобы усвоить все тонкости и детали, позволяющие писать более качественный код, допускать меньше ошибок, добиваться более высокой производительности и надежной безопасности. Поэтому по возможности работайте с тем языком программирования, в котором уже хорошо ориентируетесь. - Подбирайте фреймворк в зависимости от своих нужд, а не от специфики проекта.
Гораздо важнее овладеть имеющимися инструментами, нежели просто получить идеальные инструменты. А овладевают ими только путем постоянного использования. Найдите фреймворк, который достаточно гибок для применения в разнообразных проектах. Как правило, низкоуровневые фреймворки более универсальны, чем полнофункциональные движки. Не гонитесь за изощренными встроенными возможностями, а ищите такой фреймворк, который можно легко расширить, дописав собственные функции. - Документация, обслуживание, сообщество и поддержка.
Прежде чем включить фреймворк в шорт-лист, загляните в документацию. Просмотрите журналы изменений, репозитории, проверьте, насколько активны форумы. Убедитесь, что при работе с данным фреймворком сможете получить адекватную поддержку, соответствующую вашим потребностям и бюджету.
Вероятно, у вас будут и какие-то дополнительные требования. В данном случае, нас интересует фреймворк, позволяющий работать как с iOS, так и с Android.
Наш выбор: STARLING
Фреймворк Starling показался нам идеальным вариантом. Он целиком написан на языке ActionScript и имитирует Flash-архитектуру DisplayList, притом, что отображение всего содержимого осуществляется непосредственно с графического процессора GPU. Это отличная новость для ветеранов ActionScript, которые могут поработать со знакомым API, а в качестве бонуса получить высокопроизводительный рендеринг.
В основе GPU-рендеринга лежит технология Stage3D, вошедшая в состав платформы Flash в 2011 году. Этот механизм гораздо быстрее, чем классический рендеринг с применением DisplayObject, но он использует Adobe’s Graphics Assembly Language (AGAL), низкоуровневый кроссплатформенный язык шейдеров, а не излюбленную архитектуру DisplayList. Как и большинство ActionScript-разработчиков, мы привыкли работать с концепцией DisplayList, во многом поэтому и остановились на Starling.
Поскольку фреймворк Starling обеспечивает весь тот же функционал, что и AGAL, он позволил нам воспользоваться имеющимся опытом ActionScript и работать с архитектурой DisplayList, как мы и привыкли. Но наряду с этим мы получили все преимущества, связанные с рендерингом через GPU. Более того, фреймворк Starling выстроен на основе технологий Adobe Flash и AIR и работает как с iOS, так и с Android (код обеих платформ обертывается в нативное приложение). Наконец, Starting функционирует и в браузере (при помощи специального Flash-плагина).
Правда, удобства не достаются даром. Язык ActionScript не предоставляет разработчику доступа ко всем возможностям, специфичным для конкретных устройств, и библиотекам — все это было бы в нашем распоряжении при работе с нативным SDK. В качестве примеров таких возможностей можно привести функции iOS — регулирование расхода батареи и технология GameCenter. Нативные расширения AIR (ANE) позволяют перекрыть этот пробел. ANE — это пакет, в котором находится нативный код и API для работы с ActionScript. Существует ряд коммерческих и свободных ANE, предназначенных для решения распространенных задач, но вы вполне можете написать такое расширение сами.
Повышение производительности
Оптимизация производительности Flash-содержимого иногда напоминает поиск иголки в стоге сена. Вычленение и идентификация конкретной проблемы может требовать значительного времени и доставлять неудобства.
Adobe Flash Builder (вариант среды разработки Eclipse для ActionScript) содержит профилирующий инструмент, позволяющий заглянуть на уровень нашего собственного кода ActionScript, но все внутренние операции Flash-плеера остаются скрытыми.
В данном случае очень удобен Scout, новый профилирующий инструмент от Adobe. Scout предоставляет подробную информацию о любых аспектах внутреннего функционирования приложения: кадровая частота, графический процессор, потребление памяти графическим процессором, анализ производительности ActionScript, детали рендеринга (DisplayList и GPU), плюс отладочный вывод. Вся информация организована в хронологическом порядке, что позволяет получить общее представление и сосредоточиться на работе с отдельными кадрами или последовательностями кадров.
Оптимизация производительности при помощи Adobe Scout
В такой хронологии каждый кадр представлен в виде вертикального столбца. Горизонтальная красная линия отмечает максимальную длительность выполнения кадра (например, около 17 миллисекунд на кадр при частоте 60 кадров в секунду). Так мы можем быстро идентифицировать наиболее серьезные проблемы с производительностью (наиболее выраженные пиковые значения) и приоритезировать усилия.
Графический процессор отображает кадры один за другим (в данном случае, кадры можно сравнить со слоями в документе Photoshop). Как правило, чем меньше слоев — тем быстрее рендеринг. При задействовании записи по технологии Stage3D позволяет восстановить каждый отдельный кадр и посмотреть, как GPU его отображает, вручную переключаясь с кадра на кадр (то есть, с уровня на уровень). Кроме оптимизации производительности как таковой, пригодится функция «воспроизведения» (replay) при тонкой доработке таких деталей, как учет соударений («попала ли пуля в стену»)?
Для Scout существуют сопутствующие приложения, работающие с устройствами iOS, Android и Kindle. Они позволяют профилировать контент, отображаемый на этих устройствах, дополнительно к контенту, представленному на вашей локальной машине. Подробное введение в работу со Scout дается в статье Тиболта Имберта «Getting Started With Adobe Scout».
Многоязычный интерфейс
Возможность предоставить версию приложения на родном языке пользователя — важнейший фактор, поспособствовавший головокружительному успеху глобального рынка App Store. Кроме очевидных преимуществ, связанных с многоязычной поддержкой, локализация помогает выстроить архитектуру для отделения контента от логики приложения.
Наша команда работает с файлами, написанными обычным текстом. Поэтому мы смогли просто переслать эти файлы нашим переводчикам. Когда нам присылают переведенные материалы, мы просто добавляем их в проект и регистрируем в программе новый язык.
Локализация — распространенная задача, а в API ActionScript даже предоставляется класс ResourceManager, выполняющий задачи по загрузке ресурсов, их синтаксическому анализу и представлению локализованного содержимого. Первую реализацию мы написали менее чем за день, и в большинстве случаев она срабатывала. По каким-то причинам приложение иногда неправильно выбирало локаль и выводило контент на случайно подобранном языке. Другие разработчики, также сталкивавшиеся с подобными проблемами, писали собственные реализации класса ResourceManager.
Конечно, нам было бы удобнее работать со встроенным вариантом класса, но даже после отладки мы не смогли найти проблему спустя почти три дня. Поэтому — вы угадали! — мы написали наш собственный A3-ResourceManager, доступный на GitHub.
Табличные данные
С технической точки зрения наша игра имеет сложный пользовательский интерфейс, управляющий простой базой данных. Многие пользовательские действия требуют получения информации из базы данных или обновления данных. Поэтому мы нуждаемся в надежном сетевом соединении. Язык ActionScript тесно взаимодействует с локальными базами данных SQLite, а эти базы данных функционируют замечательно, если придерживаться нескольких простых правил:
- Применение асинхронного доступа.
На первый взгляд синхронный доступ к базе данных может показаться интересным вариантом, поскольку его сравнительно просто реализовать (не требуется слушателей и обратных вызовов). Но необходимо учитывать, что AIR работает в однопоточном режиме (на момент написания статьи потоки-работники для этой технологии еще не реализованы). Соответственно, при синхронном доступе к базе данных выполнение кода будет приостанавливаться, при этом будет блокироваться даже пользовательский интерфейс. Для любого приложения это настоящий камень преткновения. - Переиспользование утверждений.
В AIR SQL-утверждения подготавливаются (то есть, компилируются) до выполнения. Если текст утверждения (то есть, запрос) не изменяется, то скомпилированное утверждение будет переиспользоваться, благодаря чему повысится скорость выполнения. Параметризованные запросы можно переиспользовать с различными множествами данных, как показано в нижеприведенном коде:
// Если утверждение отсутствует – создаем его if (addStmt == null) { addStmt = new SQLStatement(); addStmt.sqlConnection = conn; // запрос с параметрами addStmt.text = "INSERT INTO tenants (name, income)" + "VALUES (:name, :income)"; } // устанавливаем значения параметров addStmt.parameters[":name"] = "Mark"; addStmt.parameters[":income"] = "4000"; addStmt.execute();
- Использование транзакций вместо единичных утверждений.
Всякий раз при добавлении или обновлении данных вся база данных должна записываться на диск. Как правило, именно на это тратится большая часть времени всей операции. При использовании транзакций все утверждения каждой транзакции выполняются в памяти, и лишь потом файл базы данных записывается на диск. Представьте себе, как экономится время! Обязательно добавляйте систему очередей на ваш уровень абстракции, поскольку SQLite выдаст ошибку, если вы просто начнете новую транзакцию, когда другая транзакция еще выполняется. Здесь пригодился бы отличный вектор или массив. - Не пропускайте имени базы данных или столбцов.
Это небольшая оптимизация, но при явном указании имени базы данных и ее столбцов уменьшается количество работы, которую необходимо сделать во время выполнения.
// НЕ ДЕЛАЙТЕ ТАК SELECT * FROM tenants; // ДЕЛАЙТЕ ТАК SELECT name, income FROM gameDB.ДЕЛАЙТЕ ТАКtenants;
- Получение большого множества результатов частями.
При необходимости немедленного предоставления результатов запроса пользователю будет целесообразно разбить объемное множество результатов на небольшие порции и сначала выдать первую часть информации, пока обработка остальных данных еще не завершена. В приведенном ниже фрагменте кода мы выбираем результаты блоками по 10 строк за раз:
// Если утверждение отсутствует – создаем его if (getTenants == null) { getTenants = new SQLStatement(); getTenants.sqlConnection = conn; getTenants.text = "SELECT name, income FROM gameDB.tenants"; } getTenants.addEventListener(SQLEvent.RESULT, buildTenants); getTenants.execute(10); // возвращает 10 строк (или меньше) function buildTenants(event:SQLEvent):void { var result:SQLResult = getTenants .getResult(); if (result.data != null) { // Перебираем результирующие строки и поочередно обрабатываем их if (!result.complete) { getTenants.next(10); // получаем следующие 10 строк } else { getTenants.removeEventListener(SQLEvent.RESULT, buildTenants); } } }
Нетабличные данные
При разработке игры, как правило, приходится иметь дело с нетабличными данными. Когда пользователь закрывает игру, мы сохраняем его координаты x и y на карте города в области просмотра. Это делается для того, чтобы пользователь, вернувшись к игре, увидел именно ту часть карты, на которой остановился.
В нашем первом прототипе мы сохраняли всю информацию, не относящуюся к базе данных, в текстовых файлах. Такие файлы записывались всякий раз, когда тот или иной объект выходил из употребления, либо приложение выключалось. Мы полагали, что именно такой подход обеспечит нам максимальную гибкость, но он оказался недостаточно надежным. Иногда при неожиданном выключении приложения (например, если поступал входящий вызов), программа запускала задачу записи в файл, но не завершала ее. В результате получались поврежденные файлы с данными.
Нам не удалось воспроизвести такую проблему, поскольку она возникала редко, и решили заменить текстовые файлы на локальные разделяемые объекты (LSO). При работе с LSO учитывайте, что такой объект может содержать лишь примитивные типы данных — например, строку, массив или дату. Размер такого объекта ограничен 100 Кб. Не храните каких-либо объектов (в нашем случае, «жильцов», tenants), а лишь свойства объектов (например, tenant.name и tenant.income) и воссоздавайте объект по его свойствам, если возникнет такая необходимость.
Избегайте издержек, возникающих при работе с атласами текстур
При работе с рендерингом на базе GPU изображения (текстуры) обычно организованы в виде атласов текстур, также называемых «спрайт-листами». При применении атласов текстур в работе с CSS удается уменьшить количество HTTP-запросов. Аналогично, при работе с графическим процессором атласы текстур помогают экономить время, затрачиваемое на загрузку изображений в GPU, и снизить количество вызовов на отрисовку (то есть, количество слоев, которые GPU требуется отобразить для отрисовки кадра).
Поддержка экранов с высоким разрешением (ретины) не составляет никакого труда. Просто загрузите атлас текстур, соответствующий разрешению устройства. Класс AssetManager из фреймворка Starling (в приведенном ниже фрагменте кода представлен как assets) предлагает свойство contentScale, которое можно использовать именно для этой цели.
// Загрузка ресурсов из "atlas1x/" или "atlas2x/" в зависимости от разрешения экрана assets.enqueue( File.applicationDirectory.resolvePath( "assets/atlas" + assets.scaleFactor + "x/", ) );
Если вы работаете с мозаичной графикой, то теряется смысл подготовки отдельных рисунков для экранов с высоким разрешением, поскольку графические файлы не изменятся, а просто увеличатся вдвое (и станут занимать вчетверо большую площадь). Это пустая трата дискового пространства и процессорного времени.
Чтобы обойтись без издержек, мы отделили мозаичные текстуры от текстур пользовательского интерфейса, содержавших сглаживаемую графику. При любых разрешениях используется один и тот же набор мозаичных текстур, но на дисплеях с высоким разрешением он масштабируется во время исполнения, а текстуры пользовательского интерфейса применяются с учетом разрешения (то есть, на устройствах с высоким разрешением задействуется отдельный набор текстур пользовательского интерфейса).
По умолчанию Starling использует билинейную фильтрацию текстур, обеспечивающую плавные переходы между пикселами при масштабировании графики. В большинстве случаев такая технология себя оправдывает, но мозаичные рисунки после такой обработки становятся размытыми. Если отключить сглаживание в классе theImage, получим масштабирование по соседним пикселам (nearest-neighbor scaling) для всех изображений.
mSmoothing = TextureSmoothing.NONE;
Заключение
PixelMogul — наша первая игра, созданная исключительно в домашних условиях. Технологии Adobe AIR и Starling позволили нам применить имеющийся опыт работы с языком ActionScript и платформой Flash и сосредоточиться на разработке. Нам очень помогло быстро растущее и активное сообщество Starling. Если мы решим реализовать другой проект в данной предметной области, то, определенно, сделаем все по тому же образцу, что и в игре PixelMogul.
А что думаете вы? Высказывайтесь, нам интересны ваши мнения!
Дополнение
- PixelMogul, iTunes App Store
- Starling Wiki
Этот мануал — отличное руководство для начинающих работать с фреймворком - Gamua
Этот фреймворк для HTML5 пока находится в разработке, использует те же API, что и Starling. - TexturePacker
Это универсальный инструмент для создания атласов текстур. Его также можно применять при создании спрайт-листов для работы с CSS в вебе. Об этом подробно рассказано в статье «Creating CSS Sprites With TexturePacker.»
Релоцировались? Теперь вы можете комментировать без верификации аккаунта.