FLASH HAXE GAMING SDK. Их инструменты – теперь наши

Данная статья является вольным переводом статьи FLASH HAXE GAMING SDK, THEIR TOOLS, OUR TOOLS
Примечание: для понимания статьи желательно иметь опыт использования git.

Несколько недель назад Adobe выпустила свое SDK для создания игр на flash (Gaming SDK for flash). Этот набор включает в себя такие известные ActionScript 3 библиотеки с открытым исходным кодом, как Starling, Away3D и Feathers. На http://lib.haxe.org уже есть экстерны для некоторых из них (что уже хорошо). Однако, во время разработки моей последней игры (написанной на Haxe с использованием Starling) оказалось, что этого мне недостаточно. Так как в «лагере» AS3 происходит много чего интересного: github-версия Starling’а день ото дня хорошеет, а пользователи создают для него довольно полезные расширения.
Время – деньги, и нам неохота писать собственные экстерны или переписывать все на Haxe. Не так ли?
Но благодаря отличной поддержке Flash’а в Haxe, мы легко можем сделать такие вещи за секунды, мы можем «хакнуть» AS3-код и использовать результаты в Haxe.
В этом посте я попытаюсь научить новичков следующему:

  • как с помощью Flex SDK скомпилировать .as файлы в a .swf.
  • как использовать эти .swf файлы в haxe (без дополнительной работы)
  • как делать патчи, чтобы заставить Haxe работать с .swf файлами, которые ему «не нравятся»

В конце у нас получится своего рода Adobe Gaming SDK, плюс кое-что еще, но для Haxe. Но здесь, как мне кажется, сам путь более важен, чем цель, к которой он ведет.
Замечание: Если вам неинтересен процесс и вы просто хотите использовать SDK, то я создал несколько репозиториев с конечными результатами на github. Вот репозиторий с тестами: https://github.com/labe-me/haxe-gaming-sdk-test
Замечание: Я не собираюсь поддерживать экстерны для этих библиотек (так как я слишком ленив для этого), поэтому я не буду размещать результаты на haxelib. При желании вы можете сделать это сами. Мне же достаточно имеющихся в haxelib средств работы с git :)

Приступаем.

Чтобы скомпилировать .as файлы нам нужно установить Flex SDK.
Попробуем научиться на примере Starling’а.
(1) достаем исходники:

$ git clone https://github.com/PrimaryFeather/Starling-Framework

Примечание: можно просто скачать их по этой ссылке https://github.com/PrimaryFeather/Starling-Framework/archive/master.zip
(2) Теперь нужно найти корневой каталог библиотеки. Корневой каталог Starling’а находится в папке Starling-Framework/starling/src.
(3) Чтобы создать .swc файл, компилируем все .as файлы. Для этого нам понадобится compc – инструмент, работающий из командной строки и поставляемый вместе с Flex SDK.
compc \
-swf-version 17 \
-source-path Starling-Framework/starling/src \
-include-sources Starling-Framework/starling/src \
-output Starling.swc

Рекомендуется использовать параметр -swf-version, но возможно, что swc будет рабочим, если опустить его.
(4) Извлекаем из полученного .swc файла спрятанный в нем .swf файл.
$ unzip Starling.swc
$ mv library.swf Starling.swf
$ rm catalog.xml

Примечание: Извлечь swf-файл можно также вручную (не используя командную строку) с помощью архиватора, тогда вам нужно будет еще переименовать library.swf в Starling.swf
Просто, не правда ли?
Хорошо, попробуем теперь использовать этот SWF файл. Но мы получим ошибку, которую разрешим далее...
// File StarlingTest.hx
import starling.text.TextField;
import starling.core.Starling;

class StarlingTest extends starling.display.Sprite {
    public function new(){
        super();
        var textField = new TextField(400, 300, "Starling!");
        addChild(textField);
    }

    public static function main(){
        var s = new Starling(StarlingTest, flash.Lib.current.stage);
        s.start();
    }
}

Вот требуемая команда с параметрами для компиляции (заметьте строчку с -swf-lib Starling.swf):
haxe \
-swf-lib Starling.swf \
-swf-version 11 \
-main StarlingTest \
-swf-header 640:480:60:aaaaaa \
-swf test.swf

Примечание: данную команду можно сохранить в текстовом файле с раширением hxml. Тогда компиляцию можно будет запускать открывая данный файл с помощью haxe.exe
А вот и сообщение об ошибке:
Starling.swc@starling.core.RenderSupport:1: character 0 : Same field name can't be use for both static and instance : clear
Starling.swc@starling.core.Starling:1: character 0 : Same field name can'
t be use for both static and instance : context
Starling.swc@starling.core.Starling:1: character 0 : Same field name can't be use for both static and instance : juggler
Starling.swc@starling.core.Starling:1: character 0 : Same field name can'
t be use for both static and instance : contentScaleFactor

Компилятор Haxe сообщает нам, что он не примет класс с такой сигнатурой:
class T {
  static var context : String;
  var context : String:
  public function clear(){}
  public static function clear(){}
}

В Haxe нельзя иметь класс, в котором статические и нестатические члены имеют одинаковые имена. Чтобы разрешить эту и подобные ей ошибки, нужно создать патч вроде этого:
//Starling.patch
-starling.core.RenderSupport.clear
-starling.core.Starling.context
-starling.core.Starling.juggler
-starling.core.Starling.contentScaleFactor

И использовать его следующим образом:
haxe \
-swf-lib Starling.swf \
--macro "patchTypes('Starling.patch')" \
-swf-version 11 \
-main StarlingTest \
-swf-header 640:480:60:aaaaaa \
-swf test.swf

Это заставит компилятор Haxe игнорировать такие статические поля, и мы не сможем использовать их в коде (хотя они будут доступны из экземпляра Starling’а).
Теперь попробуем добавить Feathers в наш набор инструментов, и походу выучим еще кое-что новое.
$ git clone https://github.com/joshtynjala/feathers

Примечание: можно также скачать архив с исходниками отсюда https://github.com/joshtynjala/feathers/archive/master.zip
Найти корневой каталог просто, он находится в корневой папке с исходниками. Но компиляция SWC выдаст нам множество ошибок и предупреждений:
$ compc \
-swf-version 17 \
-source-path feathers/source \
-include-sources feathers/source \
-output Feathers.swc

Warning: Definition starling.display.DisplayObject could not be found.
        import starling.display.DisplayObject;
...

Не нужно паниковать! Для компиляции Feathers просто нужен Starling. Для этого используем параметр --external-library-path+=Starling.swc
$ compc \
-swf-version 17 \
-source-path feathers/source \
-include-sources feathers/source \
--external-library-path+=Starling.swc \
-output Feathers.swc

Заметьте, что мы задали 2 swc: Starling.swc и Feathers.swc.
Извлекаем library.swf и переименовываем его в Feathers.swf
$ unzip Feathers.swc
$ mv library.swf Feathers.swf
$ rm catalog.xml

И вот у нас есть «Перышки». Ну, почти что.
Чтобы использовать Feathers, нам нужен скин Мы можем создать собственный … Однако, об этом позаботились другие, уже создавшие довольно приятные на вид скины… возможно ли их использовать? Давайте выясним! Скачиваем примеры:
$ git clone https://github.com/joshtynjala/feathers-examples

Примечание: или https://github.com/joshtynjala/feathers-examples/archive/master.zip
Пробежавшись глазами по содержимому этого репозитория, видно, что доступно несколько тем. Мне понравилась MetalWorksMobileTheme. Соответствующая папка содержит вложенную папку, в которой лежит файл этой темы feathers/themes/MetalWorksMobileTheme.as file. Догадаетесь, что мы будем делать? Угадали:
$ compc \
-swf-version 17 \
-source-path feathers-examples/MetalWorksMobileTheme/source \
-include-sources feathers-examples/MetalWorksMobileTheme/source \
--external-library-path+=Starling.swc \
--external-library-path+=Feathers.swc \
-output Feathers-MetalWorksMobileTheme.swc

Извлеките library.swf из полученного swc (в следующих версиях Haxe этот шаг будет необязательным, так как появится полноценная поддержка swc. В svn-версии такая фича уже доступна с использованием параметра -swf-lib) и переименуйте его в Feathers-MetalWorksMobileTheme.swf:
$ unzip Feathers-MetalWorksMobileTheme.swc
$ mv library.swf Feathers-MetalWorksMobileTheme.swf
$ rm catalog.xml
Теперь попробуем ис
пользовать Feathers:
// FeathersTest.hx
import starling.core.Starling;
import feathers.themes.MetalWorksMobileTheme;

class FeathersTest extends starling.display.Sprite {
    public function new(){
        super();
        var theme = new MetalWorksMobileTheme(this, false);
        var button = new feathers.controls.Button();
        button.label = "Good old button";
        button.addEventListener(
            starling.events.Event.TRIGGERED,
            function(e){
                trace("POP");
            }
        );
        addChild(button);
    }

    public static function main(){
        var s = new Starling(FeathersTest, flash.Lib.current.stage);
        s.start();
    }
}

И теперь команда для компиляции выглядит посложнее:
$ haxe \
-swf-lib Starling.swf \
--macro "patchTypes('Starling.patch')" \
-swf-version 11 \
-swf-lib Feathers.swf \
-swf-lib Feathers-MetalWorksMobileTheme.swf \
-main FeathersTest \
-swf-header 640:480:60:aaaaaa \
-swf test.swf

Неплохо бы узнать, как haxelib работает, чтобы упростить эти параметры компилятора… но не сейчас.
Что ж, посмотрим, чего же мы еще не получили из Adobe Gaming SDK? Ну конечно, Away3D.
$ git clone https://github.com/away3d/away3d-core-fp11

Примечание: или скачиваем https://github.com/away3d/away3d-core-fp11/archive/master.zip и разархивируем
Корневым каталогом для него является away3d-core-fp11/src/. Тогда двигаем дальше:
$ compc \
-swf-version 17 \
-source-path away3d-core-fp11/src \
-include-sources away3d-core-fp11/src \
-output Away3d.swc

Извлекаем library.swf и переименовываем его в Away3d.swf
$ unzip Away3d.swc
$ mv library.swf Away3d.swf
$ rm catalog.xml

Этот пример выдаст несколько предупреждений, и вы можете поправить их и отправить свой патч команде Away3D, чтобы помочь им улучшить проект. Но не беспокойтесь – все и так заработает :)
// Away3DTest.hx
import flash.display.BitmapData;
import away3d.materials.TextureMaterial;
import away3d.textures.BitmapTexture;

class Away3DTest {
    static var view : away3d.containers.View3D;

    public static function main(){
        view = new away3d.containers.View3D();
        view.camera.x = 300;
        view.camera.z = -600;
        view.camera.y = -600;
        view.camera.lookAt(new flash.geom.Vector3D(0,0,0));
        flash.Lib.current.addChild(view);
        var cubeBmd = new BitmapData(128, 128, false, 0x0);
        cubeBmd.perlinNoise(70, 207, 5, 123, true, true, 70, true);
        var cubeTexture = new BitmapTexture(cubeBmd);
        var cubeMaterial = new TextureMaterial(cubeTexture);
        cubeMaterial.gloss = 20;
        cubeMaterial.ambientColor = 0x808080;
        cubeMaterial.ambient = 1;
        var geom = new away3d.primitives.CubeGeometry(300, 300, 300);
        var cube = new away3d.entities.Mesh(geom, cubeMaterial);
        cube.x = 0;
        cube.y = 150;
        view.scene.addChild(cube);
        flash.Lib.current.addEventListener(
            flash.events.Event.ENTER_FRAME,
            onEnterFrame
        );
    }

    static function onEnterFrame(e){
        view.render();
    }
}

А вот команда для компилятора:
$ haxe \
-main Away3DTest \
-swf-lib Away3d.swf \
-swf-header 640:480:60:aaaaaa \
-swf-version 11 \
-swf test.swf

Вот и все с Adobe Gaming SDK… но нам же хочется большего, нам нужен всемогущий Flash Haxe Gaming SDK :)
Так что давайте добавим вот это расширение для Starling'а, которое может пригодится в дальнейшем:
$ git clone https://github.com/PrimaryFeather/Starling-Extension-Particle-System

Примечание: или можно все так же скачать архив из репозитория и распаковать его
Компилируем:
$ compc \
-swf-version 17 \
-source-path Starling-Extension-Particle-System/src \
-include-sources Starling-Extension-Particle-System/src \
-output Starling-Extension-Particle-System.swc \
--external-library-path+=Starling.swc

Извлекаем swf (для автоматизации, кстати, можно написать простенький скрипт) и переименовываем его в Starling-Extension-Particle-System.swf:
$ unzip Starling-Extension-Particle-System.swc
$ mv library.swf Starling-Extension-Particle-System.swf
$ rm catalog.xml

И снова понадобится патч (статические методы конфликтуют с методами экземпляра, ничего сложного, нужно просто прочитать сообщения компилятора):
//Starling-Extension-Particle-System.patch
-starling.extensions.ColorArgb.fromArgb
-starling.extensions.ColorArgb.fromRgb

Тестируем (вам понадобятся файлы pdesign.pex и pdesign.png, созданные в ParticleDesigner’е):
//ParticlesTest.hx
import starling.core.Starling;
import starling.textures.Texture;
import starling.extensions.PDParticleSystem;

@:bitmap("pdesign.png") class Particle extends flash.display.BitmapData {}
@:file("pdesign.pex") class Config extends flash.utils.ByteArray {}

class ParticlesTest extends starling.display.Sprite {
    public function new(){
        super();
        var config = new flash.xml.XML(new Config().toString());
        var texture = Texture.fromBitmapData(new Particle(0,0));
        var system = new PDParticleSystem(config, texture);
        system.emitterX = flash.Lib.current.stage.stageWidth / 2;
        system.emitterY = flash.Lib.current.stage.stageHeight / 2;
        addChild(system);
        Starling.juggler.add(system);
        system.start();
    }

    public static function main(){
        var s = new Starling(ParticlesTest, flash.Lib.current.stage);
        s.start();
    }
}

Этот тест компилируется с помощью следующей команды:
$ haxe \
-swf-lib Starling.swc \
--macro "patchTypes('Starling.patch')" \
-swf-version 11 \
-swf-lib Starling-Extension-Particle-System.swc \
--macro "patchTypes('Starling-Extension-Particle-System.patch')" \
-main ParticlesTest \
-swf-header 640:480:60:000000 \
-swf test.swf

Вот так.
Давайте теперь наведем порядок в этой куче.
Я создал несколько репозиториев на github’е:

Благодаря поддержке git’а в haxelib эти библиотеки устанавливаются очень просто:

haxelib git away3d <a href="https://github.com/labe-me/haxe-away3d" title="https://github.com/labe-me/haxe-away3d">https://github.com/labe-me/haxe-away3d</a> haxelib
haxelib git starling <a href="https://github.com/labe-me/haxe-starling" title="https://github.com/labe-me/haxe-starling">https://github.com/labe-me/haxe-starling</a> haxelib
haxelib git feathers <a href="https://github.com/labe-me/haxe-feathers" title="https://github.com/labe-me/haxe-feathers">https://github.com/labe-me/haxe-feathers</a> haxelib
haxelib git starling-particle-system <a href="https://github.com/labe-me/haxe-starling-particle-system" title="https://github.com/labe-me/haxe-starling-particle-system">https://github.com/labe-me/haxe-starling-particle-system</a> haxelib

А вот как я создал эти репозитории:
Я создал репозиторий haxe-starling на github’е, и затем:
# clone
git clone git@github.com:labe-me/haxe-starling.git
cd haxe-starling
# link the real AS3 Starling-Framework repository to my repository
git submodule add <a href="https://github.com/PrimaryFeather/Starling-Framework" title="https://github.com/PrimaryFeather/Starling-Framework">https://github.com/PrimaryFeather/Starling-Framework</a> starling
# create a separate haxelib folder
mkdir haxelib

Для автоматизации создания Starling.swf я написал Makefile. Он просто делает то, что мы делали ранее.
Затем в папке haxelib я создал файл патча Starling.patch.
Потом я добавил файл extraParams.hxml. В данном файле хранятся параметры для компилятора, чтобы их не приходилось прописывать каждый раз для каждого проекта. Данные параметры включают использование нашего скомпилированного файл Starling.swf и патча для него, а также задают требуемое значение swf-version 11.
Также в папке haxelib я добавил файл include.nmml, который нужен в том случае, если вы используете библиотеку NME, его назначение такое же, что и у файла extraParams.hxml.
На своей локальной машине я создал маленький тест и задал в нем использование моей локальной версии библиотеки:
$ haxe dev starling `pwd`/haxelib

Для других репозиториев процесс был аналогичен.
Во время своего экспериментирования я нашел два бага, которые могут встретиться и вам (я говорю «могут», так как я использую svn-версию Нaxe):

  • У вас могут возникнуть проблемы, если при компиляции swf файлов с помощью compc вы добавите параметр -debug.
  • Также возникли проблемы с extraParams.hxml – мне пришлось вручную прописывать -swf-lib MyLibrary.swf для случая, когда два файла extraParams.hxml были активны (например, когда одновременно используются Starling и Feathers) (https://github.com/labe-me/haxe-feathers/blob/master/test/Makefile).

Вот и все.
Надеюсь, что этот довольно длинный пост окажется полезным Haxe-сообществу и поможет заполнить пропасть между Haxe и AS3.
И наконец, хотелось бы высказать несколько идей, которые могут сэкономить энергию:
Мне кажется, что написание экстернов – это неблагодарная работа. Я писал их для three.js и для некоторых других библиотек, и мне это не нравилось. Я думаю, что в случае таких платформ как Flash и, может быть, Java и C#, мы не обязаны делать этого. Это должно быть обязанностью компилятора, чтобы мы могли просто добавить параметр -jar-lib, или -cs-lib, также как мы можем сейчас написать -swf-lib.
Кроме того, портирование успешного проекта на Haxe рационально делать, только если вы собираетесь сделать его кросс-платформенным. Если же вы ориентируетесь только на оригинальную платформу, на которой проект был написан, то обновление оригинальной версии практически всегда будет опережать ваш порт. Не обижайтесь, это, всего лишь, совет из личного опыта потери времени.

И счастливого конца света!