ActionScript3 を Haxe に移植できなかった話と、自作ゲームたちの過去と未来について

ActionScript3 を Haxe に移植できなかった話と、自作ゲームたちの過去と未来について

コンコルド効果 - Wikipedia
...コンコルド効果(コンコルドこうか、Concorde effect、Concorde fallacy)は、心理現象の一つである。コンコルドの誤り、コンコルドの誤謬、コンコルド錯誤ともいう。(中略)ある対象への金銭的・精神的・時間的投資をしつづけることが損失につながるとわかっているにもかかわらず、それまでの投資を惜しみ、投資がやめられない状態を指す。(後略)



お久しぶりです、くろば・Uです。

昨年ステま4巻発売と同時に芳文社サイト上でリリースさせていただいたオンブラウザFlashゲーム「すたーらいなー」ですが、ここ数日(正確には、ブランクを何度か挟みつつ10日ほど)何とか JS (HTML5) に移植できないものかと試行錯誤していたわけなんですよね。 Flash くんが親御さんにすら見放されてるものすごい嫌われ具合で、まぁ時代も時代ですよね…という流れ。

その課程で、今まで生JSしか触れていなかったぼくもようやく、巷で話題のいわゆるaltJSに触れてみようという気運が高まり、今回は ActionScript3 (以下,AS3) のプロジェクトを移植しやすいかも?という理由により Haxe + OpenFL を導入、コンパイル環境や Sublime Text 上でエラー箇所をハイライトする python スクリプトを書いたりもして、さあ移植だと意気込み、挫折しました




何故挫折を決めたかというとHaxeとAS3は似たような感じで書けるよ!と聞いてた割には相当部分修正するところが出てきてしまい、あまりの煩雑さに虫唾が走って仕方がなくなったというところと、こういう風に書くのが安全だしプログラマも理解しやすいよね、みたいなHaxeデベロッパーの哲学を無理矢理強制される感じ、そして何より(途中でイカで遊んだ時間も相当あったけど)10日かけてもまだすたーらいなーのプロジェクトではなく、らいなーに食わせている汎用性高めのオレオレライブラリ部分の移植すら完了していないというあまりの先の長さに絶望したこともあります。
というわけで、ただただHaxeと向き合った時間を無為にしたというのもなかなかに不本意なので、ここではもしも AS3 でできたプロジェクトを Haxe に移植したいみたいな方が何かの縁でこのページにいらっしゃった時にも参考になるかもしれない、 Haxe と AS3のどのへんが違って移植はどれぐらい大変なのかを書き殴っておこうかと思います。明日以降の自分がまだ諦めきれずにコードに向き合わない為にもです。


比較的許せる書き換えポイント

関数に可変長引数がない
AS3:
public function foo(...args) {
	trace(args.length);
}
Haxe:
public function foo(args:Array) {
	trace(args.length);
}
Haxe は基本的に型安全を可能な限り守っていこうみたいなポリシーがあるようです。

AS3 の場合、 args は Array 型になります。 AS3 における Array 型というのはどんな型でも入れることができるとてもふわふわしたもの(型安全な配列は Vector.<T> を用いる)ですが、そういうふわインスタンスが簡単に生成されるのは Haxe は嫌いなのでしょう。

Haxe にも型安全な配列として Array<T> が用意されています。可変長で渡したいならどんな変数を渡すのかは決めたほうがいい。ここはまぁ合理的ですね。可変長という概念がそもそもないのでArray<T>::splice の第三引数以降なんてものもありません。

switchにbreakがない
AS3:
switch(bar) {
	case 0:
		trace('bar is 0');
		break;
	case 1:
	case 2:
		trace('bar is 1 or 2');
		break;
}
Haxe:
switch(bar) {
	case 0:
		trace('bar is 0');
	case 1,2:
		trace('bar is 1 or 2');
}
コーディングをミスって意図しないフォールスルーが起きるよりは良い。妥当です。

Function型は存在しない
AS3:
var myFuncVar:Function;
public function foo(a1:String) :void {
	trace("foo is called, first argument is '" + a1 + "'");
}
public function main() {
	myFuncVar = foo;
	myFuncVar('on flash'); // foo is called, first argument is 'on flash'
}
Haxe:
var myFuncVar:String->Void;
public function foo(a1:String) :Void {
	trace("foo is called, first argument is '" + a1 + "'");
}
public function main() {
	myFuncVar = foo;
	myFuncVar('on haxe'); // foo is called, first argument is 'on haxe'
}
AS3 の場合関数は適当に Function 型にぜんぶぶち込んでおくことができましたが、 Haxe はC言語でのそれのように引数1->引数2->...->返値の型を列挙してようやく関数型の変数を定義することができます。
そりゃあてきとうな型の引数やら返り値やらが Function 型まわりに跋扈するよりは、コンパイルエラーで未然に防げるという塩梅ですね。 いちおう Dynamic 型に関数を入れておくこともできますが、呼び出すにはわざわざ Reflect.callMethod(Reflect.field(myFuncVar,'scope'),myFuncVar,args); なんてクソ長いコードを書く必要があります。

ボディブローのように効いてくるポイント

カンマ区切りで複数処理を書くことはできない
AS3:
if (bar) x=0,y=0;
else x=1,y=1;
Haxe:
if (bar) {
	x=0;
	y=0;
} else {
	x=1;
	y=1;
}
コードの見やすさからカンマ区切りで雑な処理を連続させるなというポリシーでしょう。許してよそんぐらい。

for (;;) がない
AS3:
for (var i=0;i<5;i++) {
	// 0,1,2,3,4
}
Haxe:
for (i in 0...5) {
	// 0,1,2,3,4
}
altJS界隈、やたらと for(;;) を敵視してるのは何でなんですか? 確かに for (i=0;i<10;j++) { ... } といったトラブルはありがちだけどさ。 Sublime にこの for loop をスニペット登録してガンガン使ってたぼくみたいな人にはだいぶ敷居が高い。

関数名/変数名に $ は使えない
えっ使いません?
正規表現内でメタ文字でないものをエスケープするとコンパイルエラー
AS3:
var MyReg:RegExp = /\@\&\%\?/;
Haxe:
var MyReg_Error:RegExp = /\@\&\%\?/; // NG
var MyReg:EReg = ~/@&%\?/;// OK
エスケープがあるとまぁコードは汚くなるし、必要ないものを省いたほうがお得だよねって?許してよそんぐらい。

許しがたいポイント

Bool 型以外を if や三項演算子の左とかに単独で入れてはいけない
AS3:
public function foo(a1:int) :void {
	if (a1) trace('true!');
	else trace('false...');
}
public function main() :void {
	var bar:int = 0;
	foo(bar); // false...
	bar = 1;
	foo(bar); // true!
	bar = -5;
	foo(bar); // true!
}
このコードは Haxe では if (a1) のところでコンパイルエラーが起きます。数値型はきちんとvar_float != 0って描かないといけないし、文字列も if(!mystr) mystr='default_string';なんて書けなくて if(mystr==null || mystr=='') mystr='default_string';しないといけない、インスタンスも単独で null 判定はできないので if (my_instance!=null) {}と書きます。許してよそんぐらい。

Int 型は自動的にキャストされない
AS3:
var my_float:Number = 1.1;
var my_int:int = my_float;
trace(my_int); // 1
これまた Haxe ではコンパイルエラーが起きます。 Int 型変数に Float 値を入れるときはちゃんと cast(my_float,Int) とか何かをしないといけません。Int 型に代入しようとしてんのは知ってんだよ自動でキャストしろよお前。

この問題の大変なところは、Int が何かの拍子に簡単に Float になりやすいところで、例えば
Haxe:
public static inline var my_width:Int=16;
public static inline var half_of_my_width_:Int= (my_width/2);
これは my_width/2 が Float として評価されるのでコンパイルエラーです。Int を使うなと言っているのか?

Bool 型以外で logical OR が書けない
AS3:
var a:int=0,
	b:int=1,
	c:String='c';
trace(a||b||c); // 1
trace(c||b||a); // 'c'
Haxeの場合、演算子 || は Bool 型のみが使うことができる表現なのでこの表現はコンパイルエラーです。AS3のこの書き方で便利なところは、例えば

AS3:
var a=function() {return 0;},
	b=function() {return 1;},
	c=function() {return 'c';},
trace(a()||b()||c()); 
という処理の場合は、 b() の時点で true 評価されるので c() は実行されないという点。静的変数が初期化されていれば静的変数を取得、そうでないなら初期化関数を呼ぶ…みたいなのが一瞬で書けて便利だったんです。JavaScriptほかUNIX系言語ではおおよそ使えるし便利なんですよ。何故殺した?

String::replace とか String::slice とか色々ない
なんでさあ!!!!!JavaScriptにもともとついてたメソッドをオミットすんの!!!!????!????

代替機能は用意されているし自分でメソッドを書けばいい、というのももっともですが、上記までで体力を使い果たしていたぼくの心を折るには十分な点だった。




リファクタリングという行為は聞きしに勝る大変さであるということがよくわかりました。
安易に手を出しちゃだめだね。新規にプロジェクトを 作る上では上記のようにとても礼儀正しいコードなので、取り組むのはとてもアリなんじゃないかなとは感じています。

せっかくなので、この場でステまゲームの過去と未来について適当に語りたいと思います。

第1作目:すたーちぇいさー

これはもともと所属していたサークルで制作していたゲームのHPがティザーサイトだった時代に、その時点でできていたドット絵見せるコンテンツを何か置けないかな…と思って挑戦してみたフラゲが元になっています。左右に動かしてジャンプして星を取り、爆弾は避ける、という基本的な所のコードはほとんどいじっておらず、音楽をginkiha氏に発注し、タイトルを作ってドット絵をたまちゃんでリファインして、ステージも1ステージのみだったので適当にレベルデザインし、2週間ぐらいででっちあげました。

第2作目:すたーしゅーたー

2011年あたりでC言語の勉強をしたいと思い立ち、弊サークルでぼくが参加したSTGのプロジェクトのコードを読み解きながら、弾幕部分や敵配置などをまるきり作り直してEXステージみたいな感じで内々にだけ遊ばせた LeiriaEX とかいうどこにも公開されていないゲームが元になっています。
すたーしゅーたーの弾幕部分はステージ1のみほぼ新規での制作で、他の弾幕は上記ゲームのものをAS3に移植するような形になりました。
このときは ActionScript3 の勉強も込みでSTGのガワを作るのもけっこう難航し、1ヶ月強は制作に費やしたような記憶があります。
音楽に関しては、お察しの通りボルテで推しの音屋さん猛烈アタックみたいなアレを決めました。

第3作目:すたーらいなー

2015年の冬あたりから実はこっそりエイプリルフール用の企画〜〜みたいなプロジェクトをこっそり進めてはいたのですが、そちらについてはあまり進捗がふるわず、2016年の6月ぐらいになって「これはアニメの開始にも絶対間に合わないな」と確信し、このプロジェクトのうちのオレオレライブラリ部分(二次元マップの読み込みとか移動とかが書かれていた)でパズルゲームを作ることはできないだろうか?と路線を変更して3ヶ月強でなんとか間に合った代物です。
第4層までの制作を目標にしていますが現在できているのは椎奈が登場する第2層まで。現行版では、2月ぐらいに更新!とか銘打っていた割にはよくわかんねぇ自作ツールにかまけてて制作が進んでいなかったりするので、今月にあやめが登場する第三層を更新することを目指しつつ、このプロジェクトは HTML5 移植の道からは手を引いて、引き続き Flash で作っていこうかなと、思います。



少なくとも Flash が2020年まで完全に死ぬまでは、これら移植することは諦めて AS3 は AS3 のままで行こうかな…と今は甘えた感じに考えています。(気が変わって頑張って移植しようみたいな気分になるかもわからないですし) Adobe AIR はまだまだ生きるようなので、最悪ローカルアプリケーションとしてらいなーその他のステまゲームをリリースすることは(PixelLinerの経験もあって)できそうだな〜とか、あるいはスマホ等モバイルデバイスへの移植のためにコードを書くほうがよほど QOL は高そうだな〜みたいな、そんな展望もありますね。

これからも健やかにゲームを作っていきたい。