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