SpineをStarling1.7で使う:AssetManager経由の初期化に苦労する の巻


前回の投稿 SpineをStarling1.7で使う:2種類のTextureAtlasに注意 の巻 の続きです。 この投稿では、2種類のテクスチャアトラスのうち、Spine形式のデータを読み込んで初期化する流れを追います。
デモページは前回と同じやつです。

公式のデモではデータファイル3つをソースにEmbedして埋め込んでいますが、

  • raptor.json… キャラクター構造+モーションデータ
  • raptor.atras … Spine形式のテクスチャアトラス設定テキストデータ

  • raptor.png … テクスチャアトラスの画像データ

通常はデータファイルをソース外から動的に読み込むことが多いと思います。それぞれURLLoaderLoaderで読み込みSkeleton初期化する事は可能ですが、面倒なので、starling.utils.AssetManagerを使ってファイルを読み込んでみます。

AssetManager利用の実際のコード

コード例を交えながら、サクサク進めます。

sampple.as
// インスタンスを作ってファイル登録してロード
var assetManager:AssetManager = new AssetManager();
assetManager.enqueue("raptor.png");
assetManager.enqueue("raptor.atlas");
assetManager.enqueue("raptor.json");
ssetManager.loadQueue(function (ratio:Number):void {
    if (ratio == 1.0) {
      createRaptor();
    }
});

↑ ここはいつも通りのコードです。

sample.as
// ロード済みのファイルの取り出し
function createRaptor():void {
    var texture:Texture = _assetManager.getTexture("raptor");
    var json:Object = _assetManager.getObject("raptor");
    var atlasData:Object = _assetManager.getByteArray("raptor");

↑ pngはTextureとして、jsonはObjectとして、atrasデータのテキストは(getString的なメソッドがないため)ByteArrayとして取り出します。

GoblinsExample.as
[Embed(source = "/goblins-mesh.atlas", mimeType = "application/octet-stream")]
    static public const GoblinsAtlas:Class;

↑ Spine公式のデモでもapplication/octet-stream(なんかバイナリデータ)としてEmbedしています。これで大丈夫です。

sample.as
// atlasDataとテクスチャからアトラスを作成して
var spineAtlas:Atlas = new Atlas(atlasData, new StarlingTextureLoader(texture));
// アトラスを元にAttachmentLoaderとやらを作って
var attachmentLoader:AttachmentLoader = new AtlasAttachmentLoader(spineAtlas);
// AttachmentLoaderを元にSkeletonJsonクラスを作って(Jsonそのものではないです)
var json:SkeletonJson = new SkeletonJson(attachmentLoader);
// SkeletonJsonに読み込んだjsonを解釈させて
var skeletonData:SkeletonData = json.readSkeletonData(json);
// skeletonDataからSkeletonAnimationを(やっと)作ります
var raptor:SkeletonAnimation = new SkeletonAnimation(skeletonData, true);
addChild(raptor);

↑ めんどくさい手続きで画面に表示できるSkeletonAnimation(=DisplayObject)を作ります。ほぼ公式のサンプルのままのコードです。お決まり処理になるので、関数かなにかにまとめましょう。。継承ツリーは、SkeletonAnimation extends SkeletonSprite extends DisplayObject と、なっています。

面倒臭いながらも、SkeletonAnimationを生成できたので、これでOK!、と言いたいのですが、実は上記のコードにはエラーがあり、コンパイルは通るものの実行するとランタイムエラーになります。問題は下記の部分です。new StarlingTextureLoader()がエラーとなります。

error.as
var spineAtlas:Atlas = new Atlas(atlasData, new StarlingTextureLoader(texture));

引数の型としてはObject型なので隠蔽されますが、StarlingTextureLoaderはコンスタラクタの引数にBitmapかBitmapDataかBitmap、もしくは(アトラス画像が複数枚の場合)パスをキー名、BitmapかBitmapDataをValueに持つObjectを取ります。Texture型は引数にとりません。

sample.as
//`A Bitmap or BitmapData for an atlas that has only one page, or for a multi page atlas an object where the key is the image path and the value is the Bitmap or BitmapData.`
{
  "hoge1.png":hoge1BitmapData,
  "hoge2.png":hoge2BitmapData
}

と、いう事でraptor.pngをBitmap型としてStarlingTextureLoaderに引き渡したいのですが、、

AssetManagerからBitmapDataの参照は取れない

のです。AssetManagerは画像を読み込むと、その画像を元にすみやかにTextureインスタンスを生成し、画像すなわちBitmapDataを破棄してしまいます。使用メモリを少なくするための処理なのですが、(Spineを使う場合にに限らず)Bitmapを利用したい場合に困ってしまいます。AssetManagerを改造してBitmapを保持するようにしている人もいるようですが、自分はコアなライブラリであるStaling側に変更を極力加えたくないので、下記のような解決策を取りました。

専用のTextureLoaderを作ってしまおう

Textureインスタンスを引数に取るharayoki.spine.starling.MyStarlingTextureLoaderなるものを作りました。implements TextureLoaderです。クラス名はそのうち変えそうですが。。ソースはここにあります。中身はStarlingTextureLoaderをベースに改造して作られたものです)

sample.as
new MyStarlingTextureLoader(texture);
//もしくは
new MyStarlingTextureLoader({path1:texture1, path2:texture2});

↑ このようにインスタンス化が可能です。こちらのクラスを使って書き出したのが、前回と今回のデモページとなります。とりあえず、問題なく動いていますね。やあ、よかった、よかった。

まとめ

今回は専用TextureLoaderを作って問題を解決しましたが、Starling形式のatlasXMLを読み込んでSpineを使う場合はAssetManagerとの相性は抜群です。今回の投稿はSpine形式のatlasデータテキストと画像をAssetManagerで読み込んで使いたい場合のノウハウとなります。自分でパッキングまで行う人には必要ないノウハウですね。でも、他の作業者からデータをもらうような場合って、Spine形式のデータをもらうことになるほうが多いと思うんですよね。

そういうニーズがありそうなので公式でもTextureを引数にできるTextureLoaderを作ってよ、という要望をSpineのフォーラムに投げておきました。そのうち実装されると良いなあ。。
http://ja.esotericsoftware.com/forum/Wish-StarlingTextureLoader-can-be-instantiated-with-starling-6412