この記事のURL

http://www.dango-itimi.com/blog/archives/2013/001174.html


FLASH tips Haxe: CreateJS と Flash API 共通する箇所は interface を作成して記述を簡略化

Haxe にて html5 canvas と Flash コンテンツ並行開発時「似たような API を持つが 利用しているクラスが異なるため、やむを得ず専用のプラットフォーム用クラスファイルを作成し処理を切り分ける」という方法を採ることがあります。

例えば以下のようなクラス切り分けを行います。

親クラス

package shooting.enemy;

class Enemy {

	private var view:View;

	public function new(){

		view = new View();
	}
}

Flash用クラス

package shooting.enemy;

import flash.display.DisplayObjectContainer;

class EnemyForFlash extends Enemy{

	public function new(layer:DisplayObjectContainer){

		super();
		layer.addChild(view);
	}
}

javascript(CreateJS)用クラス

package shooting.enemy;

import createjs.easeljs.Container;

class EnemyForJS extends Enemy{

	public function new(layer:Container){

		super();
		layer.addChild(view);
	}
}

View クラスは Flash To Haxe Converter で作成された、flash.display.MovieClip あるいは createjs.easeljs.MovieClip を継承した extern クラスです。

上記 EnemyForFlash, EnemyForJS コンストラクタ内では同じ内容の処理を行なっていますが、layer インスタンスのクラスが異なるため、EnemyForFlash, EnemyForJS と各プラットフォーム用クラスに切り分けて記述しています。

Flash の DisplayObjectContainer クラスと CreateJS の Container クラスの役割は同じようなもので、持ち合わせているプロパティやメソッド名はとても似ています。addChild の引数には DisplayObject という名前のクラスを追加する、という点まで一緒です。

ここで、DisplayObjectContainer クラスと Container クラスを同じクラスとして扱う事ができれば、EnemyForFlash, EnemyForJS クラスを用意する事はなくなるのではないかと考えたところ、以下の方法を編み出しましたので順に記述します。

interface を作成

flash.display.DisplayObjectContainer と createjs.easeljs.Container クラス両方に共有のメソッド addChild を持つことを示すための interface IDisplayObjectContainer を作成します。

package sample.display;

#if js
import createjs.easeljs.DisplayObject;

#elseif flash
import flash.display.DisplayObject;

#end

interface IDisplayObjectContainer {

	public function addChild(child:DisplayObject):DisplayObject;
}

上記 interface は、IntelliJ IDEA のエディタ上では「DisplayObject が見つかりません」といったエラー表示がされてしまいます。これはエディタ側で DisplayObject が何であるのか判定できていないだけで Haxe のコンパイルは問題なく通ります。

プラットフォームデフォルトクラスの継承と IDisplayObjectContainer を interface に持つ独自クラスの作成

flash.display.DisplayObjectContainer と createjs.easeljs.Container それぞれを親に持つクラスを作成し、更に IDisplayObjectContainer を implements します。

Flash には DisplayObjectContainer の子クラスとして Sprite と MovieClip クラスがありますが、今回は Sprite クラスを継承したクラスの作成を行います。

package sample.display.as3;

class Sprite extends flash.display.Sprite, implements IDisplayObjectContainer{
}
package sample.display.createjs;

class Container extends createjs.easeljs.Container, implements IDisplayObjectContainer {
}

拡張したクラスを利用

上記必要な拡張クラス制作が完了したら、Enemy クラスは以下のように記述できます。

package shooting.enemy;

import sample.display.IDisplayObjectContainer;

class Enemy {

	private var view:View;

	public function new(layer:IDisplayObjectContainer){

		view = new View();
		layer.addChild(view);
	}
}

EnemyForJS, EnemyForFlash クラスの制作は必要ありません。呼び出し側で、拡張した Sprite or Container クラスインスタンスどちらかを指定すればよしとなります。

Flash 用 呼び出し側記述例

var layer = new sample.display.as3.Sprite();
new Enemy(layer);

CreateJS 用 呼び出し側記述例

var layer = new sample.display.createjs.Container();
new Enemy(layer);

課題

addChild のみの例では結構良い見た目で記述できている感じがしますが、例えば removeChild を IDisplayObjectContainer に持たせる場合 以下の一行を追加します。

public function removeChild(child:DisplayObject):#if js Bool #elseif flash DisplayObject #end;

CreateJS の場合は 戻り値が Bool であるのに対し、Flash の場合 戻り値は DisplayObject です。Haxe の条件付きコンパイル記述で切り分ける必要があります。

Haxe の条件付きコンパイル記述で処理切り分けを行うのであれば、わざわざ interface を用意せず Enemy クラス内に CreateJS と Flash 用処理の全てを記述してしまう、という方法もあります。しかし条件付きコンパイル記述で囲まれた箇所は、エディタ IntelliJ IDEA でリファクタリング対象にならない、というデメリットがあります。interface 内のみの 条件付きコンパイル記述指定で済むのであれば、プログラミングの負担は軽減され良しと考えます。

参考)条件付きコンパイル
http://haxe.org/ref/conditionals?lang=jp

[ FLASH ] [ tips ] 投稿者 siratama : 2013年04月05日 16:52

トラックバック

http://www.dango-itimi.com/blog/mt-tb.cgi/1134

コメント

以下コメントを書き込むだけでは、管理人には通知が行われません。通知を行いたい場合、管理人の書き込みに「返信」を押してコメントをしていただくか、あるいは Google+, Twitter へご連絡ください。




[EDIT]