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


