HaxeでFlashのDisplayObjectに、エディタ補完を通したい場合のメモ


Flashオーサリングツールからの素材をHaxeに読み込む場合、その方法の種類と、どのようなクラス構成でIDEに補完指示を出せばいいか自分なりに調べた結果をメモ。
まだ不明点が多いので資料としては不完全なのと、手元の実験ファイルを見ながら、Qiitaに手書きしてるので、コードには細かいミスがあるかもしれない。

flaからの素材をHaxeで動作させるための種類

まず、Flash素材の読み込みには以下の方法がある

swcとしてコンパイル時に埋め込み

Haxeはswcのライブラリとしての埋め込みに対応している。
Flashのクラス出力の特徴として、ライブラリで指定したクラスと、その内部インスタンスまでのパスが補完される。
インスタンスに入れ子になっているパスは補完されない。
また、Haxeでのswc補完に対応しているIDEが少ない(というかHaxeでswcに対応してるIDEは現状あるの?)
haxeにはswcのexternを自動生成するコマンドラインが存在するが、ライブラリでクラス設定されていない入れ子インスタンスは補完されないのと、haxe3になってからは試していない。

詳細:http://haxe.org/manual/swc
(「Haxe は現状, SWCリンクをサポートしていません」という記述があるが、これはHaxe2の話)

swfとしてコンパイル時に埋め込み

Haxeのasset機能を使用してコンパイル引数から指定して埋め込み。
OpenFLを使用している場合は埋め込み方法が違うので注意。
特にOpenFLなどには白玉さんがツールや様々なサンプルを作っているのでそちらを参照。

詳細:http://www.dango-itimi.com/blog/archives/2013/001188.html

swfファイルとして動的ロード

いわゆるLoaderを使用した方式。
自分はAS3でFlashゲームを作る場合、最も基礎ファイルが小さくなり、コンパイル時間が短くなるこの方法を多用している。
また、描画部分のswfパーツが後から切り替え可能なので、プログラマ不在でデザイナが手直しをすることが出来、多人数プロジェクトのテスト時に大いに役立つ。1人の時も、絵を調節する時に、コードエディタに移動してコンパイルし直さなくていいしね。
上記swfコンパイル時埋め込みと容易に切り替えができるので、ブラウザ向けFlashや、テスト時のみはこの方式に切り替える、というのが良いと思われる。
埋め込みとロードが切り替えれる仕組みがOpenFLで上手く出来るのか未検証。出来たらすごく便利なのだけど・・・。
個人的にPCブラウザターゲットの場合は最強だと思っているのだけど、この方式に対応したhxファイル自動生成ツールはまだ存在しない。白玉さんのツールを改造したら出来そうだけど。

画像、スプライトシート、svg、描画コードなどとして動的ロード

Flashで作成したものをそれぞれ、swf形式でないものに分解して素材として扱うという方式。
Toolkit for CreateJSとかがこれだけど、JSだけに最適化されるのと、素材が多数に分割されるため、ロードが長くなるのが欠点だと思う。
HTML5ではバイナリ読み込みが難しいため仕方ないんだろうけど、HTTPリクエストの回数が多くなり、結果的に素材が多いと初期動作は遅くなる。
つーか、HTML5でゲームとか作成するなんて、モバイルブラウザでもそのまま動くようなミニゲームを作る時か、クライアントが「なんか知らんけどHTML5がいい!」って言った時以外必要なくて、PCブラウザはFlashで、モバイルは各種アプリで出力すればいいのであって、HTML5に配慮する必要なんて無いような・・・ブツブツ・・・。
確かFlashCS6にはスプライトシート書き出し機能があったのだけど、FlashCCではどうなったんだっけ?利用すればjsflで独自ツールを作ることは可能だと思われる。
また、スプライトシートを利用することで、OpenFLではGPUに最適化が可能だと思われる。サイズに合わせて動的にスプライトシートを作ったほうが多機種対応にはいいような気もするけど、どうでしょうね。

こちらも白玉さんがCreateJS用のツールを公開しているので参照
http://www.dango-itimi.com/blog/archives/2013/001185.html

DisplayObjectへの、IDE補完

本題。
Haxeではexternやtypedefなど、IDEのコード補完を補助するファイルが作成できる。
externは、埋め込み時など、コンパイル時にそのクラスが存在している場合は機能するが、動的ロードなどでは、対象のクラスが無いとのエラーで、動作できないので、少しやり方が違う。
また、単純なexternでは入れ子のインスタンスに対応できないため、その点も対応する。
下記は基本的にswfターゲットでのやり方で、各種書き出し対象に対してはアレンジが必要のはずなので注意。

埋め込みの場合

ライブラリで、「EmbedSpriteSample」と指定された、Sprite継承のクラスがあり、内部に、innerMCというムービークリップインスタンスが1つ配置されており、その中にはtfというインスタンス名でテキストフィールドが1つ配置されている。innerMCは、ライブラリでクラス定義はされていない、という場合

EmbedSpriteSample.hx
import flash.display.MovieClip;
import flash.text.TextField;
import flash.display.Sprite;
extern class EmbedSpriteSample extends Sprite
{
    public var innerMC:MC_innerMC;
    public function new():Void;
}
// 下位インスタンスの構造指定
extern class MC_innerMC extends MovieClip
{
    public var tf:TextField;
}

これで、ライブラリでクラス定義されていない内部インスタンスまで全てIDE補完が通る。
内部インスタンスのクラス名はとりあえず MC_インスタンス名 としておいた。
実行時の呼び出しは、

Main.hx
class Main 
{
    public static function main():Void
    {
        var display:EmbedSpriteSample = new EmbedSpriteSample();
        flash.Lib.current.addChild(display);
    }
}

このようになる

動的ロードの場合

externは使えないので、対象ライブラリと同名同パッケージの空のクラスを用意する形になる。
名称は「LoadSpriteSample」で、内部構造は同じ場合。
内部インスタンスの使用方法は色々あるが、試した結果だとtypedefでダックタイプ(構造的部分型付け)を利用するのが最もわかりやすく軽量なコードが吐き出されていた。

LoadSpriteSample.hx
import flash.display.MovieClip;
import flash.text.TextField;
import flash.display.Sprite;
class LoadSpriteSample extends Sprite
{
    public var innerMC:MC_innerMC;
    public function new(){ super(); }
}
// 下位インスタンスの構造指定
typedef MC_innerMC =
{ > MovieClip,
    public var tf:TextField;
}

呼び出しは若干特殊だが、下記のようになる(記述が長いのは、FlashのLoaderの挙動のせい)

Main.hx
class Main 
{
    public static function main():Void
    {
        var clazz:Class<Dynamic> = cast(loader.contentLoaderInfo.applicationDomain.getDefinition("LoadSpriteSample"));
        var display:LoadSpriteSample = Type.createInstance(clazz, []);
        flash.Lib.current.addChild(display);
    }
}

煩雑になるし、Loaderを引き回すのも頭悪いのと、文字列指定はタイプミスが怖いので、対応するLoader(applicationDomain)、クラス名を管理するクラスを1つ作って解決するのがいいと思う。
動的ロードは、保持しているLoaderがメモリ上から消えると、削除される恐れがあるので注意。

※追記
白玉さんが、ツールをexternではなく動的ロード用クラス出力に対応してくれるそうです。
入れ子状態のクラス設定無し補完機能には今後の課題とのこと
バッテンラボを要チェック:http://www.dango-itimi.com/blog/