カテゴリー別アーカイブ: AS3.0

【CreateJS】AS3からHTML5 Canvasに書き出す場合のJavaScript(その3)2017年版

【MacOS Seria+Adobe Animate CC 2017.5リリース】
swfで作ったコンテンツをHTML5 Canvasに書き出すお勉強のその3です。

その2から随分間が空きましたが(笑)、ちょうど以前開発したAS3コンテンツをまとめてCanvas化する仕事があって、思い出しつつ調べつつ作業したので、2017年10月の時点でどのように変換したかをまとめておきます。

前提とするコンテンツは下記のようなものです。

・Flash IDE(いわゆる純正のFlashアプリケーション)で作成されたもの。
・スクリプトを外部ファイル化していない(関数などは1フレーム目にまとめて定義)。
・タイムラインとActionScriptを併用している(定義した関数や変数をフレームで呼び出すようなもの)

ちょっとしたインタラクションとアニメーションを絡めたようなコンテンツは、わざわざクラスファイルを外部化するのも面倒だし、こういう作り方してる人も多いんじゃないかと思います。FlashDevelopやFlash Bulderなんかでガリガリ作ったものは、その筋の人たちにお任せします(笑)。

0.FlaファイルをCanvas形式に変換する。

コマンド>他のドキュメント形式に変換 を選択します。

ドキュメントタイプコンバーターで「HTML5 Canvas」を選択してファイル名、保存場所を指定してOKを押します。

ドキュメントタイプコンバーターのポップアップ

※この時、全てのActionScriptはコメント化されます。「this.stop();」などそのまま使えるものもコメント化されるので要注意。

1.型は全て削除する(:uint や as MovieClipなど)。

JavaScriptにはないので当然ですね。アクションパネルなどで一括置換が漏れがなくてオススメです。

2.タイムラインのどこからでも使いたいものは、アクション>グローバル>スクリプトに書く(のグローバル化)

1フレーム目に関数(function)を書いて、別のフレーム(例えば10フレーム目)で呼び出すのは、ASではよくやりますが、これがそのままではJavascriptで動きません。その場合は、アクションパネルでアクション>グローバル>スクリプトを開いて、ココに書きます。ココに書かれた変数や関数はグローバル化されるので、this参照なしでどこからでも呼び出せます。

アクションパネルの左側から、グローバル>スクリプトを選択します。

・スクリプト例(定義:グローバル>スクリプト)

//---アニメフラグ
var flg_anime = true;

//ボタン:切替
function switchBtn(flg)
{
	if (flg)
	{
		//アニメーション開始;
		startAnimation();
	}
}

・スクリプト例(呼び出し;フレームスクリプト)

//ボタン:初期化
switchBtn(flg_anime);

※ flg_anime, switchBtn()共にグローバルなので、this等の修飾なしで使用できる。

グローバル>スクリプトの関数で、ステージ上に配置されたMovieClipやボタン、ステージのタイムラインなどを使用する場合は、exportRootで参照します。

・スクリプト例(定義:グローバル>スクリプト)

//アニメーション開始
function startAnimation()
{
	exportRoot.gotoAndPlay("f_anime");
}

3.MovieClip内の関数

タイムラインが1フレームしかないもので、MovieClip内で完結する変数、関数は、そのままthis等の参照なしでフレームに書いてOK。
・そのMovieClip内に閉じたものは、function xxx(){} で定義したものをthis.xxx()で参照すればよい。

・スクリプト例(MovieClip;フレームスクリプト)

//何か例を入れる

但しMovieClipの外部から参照する変数、関数はプロパティにする必要があります。
・this.xxx = function(){} な書き方。

//何か例を入れる

4.プロパティパネルに書いたインスタンス名は参照出来ない。

ボタンやMovieClipでインスタンス名で参照したいときは、明示的にnameプロパティに文字列を設定する必要があります。

・スクリプト例(定義:グローバル>スクリプト)

//ボタン:初期化
function inithBtn()
{
	exportRoot.btn_up_year.name = "btn_up_year";
	exportRoot.btn_up_month.name = "btn_up_month";
	exportRoot.btn_up_day.name = "btn_up_day";
}

※MovieClipなら、MovieClip内のフレームで、this.name = “xxx” でも定義できる。

5.円形グラデーションやフィルターなどは使えない。

・HTML5 Canvasに書き出すのに制限があります。ライブラリ上でビットマップに変換しておくか、場合によってはビットマップで作り直す必要があります。

6.MouseEvent.MOUSE_UP >> pressup

・ここは “mouseup” じゃないので要注意。

処理 AS JavaScript
クリック MouseEvent.CLICK click
マウスオーバー MouseEvent.MOUSE_OVER mouseover
マウスアウト MouseEvent.MOUSE_OUT mouseout
マウスダウン MouseEvent.MOUSE_DOWN mousedown
マウスアップ MouseEvent.MOUSE_UP pressup
ロールアウト MouseEvent.ROLL_OUT rollout

7.Movieclip内の関数でのthis参照

関数内でのthisを参照すると、ページのルートであるWindowを指します(詳しくは、別記事の「4.thisの参照」を参照のこと)。
MovieClip内の関数で、相対的にパスを指定したいときは、関数外で変数にthisを入れておき、それを使って参照します。

・スクリプト例(定義:MovieClip内のフレームスクリプト)

//関数内での参照用にthisを変数化
var my_mc = this;

//初期化
this.init = function ()
{	
	my_mc.open_cap.visible = false;
	my_mc.close_cap.visible = false;

}

8.MovieClipボタンのカーソル対応

“buttonMode” は”cursor”に変更します。

処理 AS JavaScript
指カーソルにする MovieClip.buttonMode = true; MovieClip.cursor = “pointer“;
デフォルトカーソルにする MovieClip.buttonMode = false; MovieClip.cursor = “default“;

9.TFLは使えない。

・強制的に普通のテキストに変換されるので、注意が必要です。

10.Dateオブジェクトの扱いに注意

ActionScriptにもJanaScriptにも同じようにDateオブジェクトがありますが、年月日時分秒を取得する際に、ActionScriptはプロパティで、JavaScriptはfunctionのため、微妙に違うので要注意。

処理 AS JavaScript
Dateオブジェクト生成 var date:Date = new Date(); var date = new Date();
年(4桁) date.fullYear date.getFullYear()
date.month date.getMonth()
date.date date.getDate()
date.hours date.getHours()
date.minutes date.getMinutes()
date.seconds date.getSeconds()

11.MovieClipのwidth,height

MovieClip.nominalBoundsで取得可能です。

・スクリプト例(定義:MovieClip内のフレームスクリプト)

this.tft07.nominalBounds.width
this.tft07.nominalBounds.height

【参考】
・X-LABO: Toolkit for CreateJS & Haxe : シンボルの初期表示サイズ・位置情報取得 nominalBounds
・Animate CCで書きだしたCreateJSのグラフィックスの色をプログラム上から変更する – Qiita

createjs – How to change the width of a movieclip in flash html5 canvas? – Stack Overflow

12.MovieClipをリストアップする

多くのMovieClipを配置して制御する場合、ひとつずつに名前をつけたりするのも大変なので、MovieClipだけを配列化して使う場合があります。

・ActioScript例

function initBit():void
{
	array_bit = [];
	
	for (var i:uint=0; i<this.numChildren; i++)
	{
		var ds:DisplayObject = this.getChildAt(i);

		if (ds is MovieClip)
		{
			array_bit.push(ds);
			ds.visible = false;
		}
	}

}

この「is MovieClip」がjavaScriptにないので、どうしようかな?と考えて、MovieClipにはtimelineが存在するので、それを代替にしてMovieClipのみをリストアップすることにしました。まあ気づけばたいした話ではないんですが。

・JavaScript例

his.initBit = function ()
{
	array_bit = [];
	
	for (var i=0; i<this.numChildren; i++)
	{
		var ds = this.getChildAt(i);

		if (!!ds.timeline)
		{
			array_bit.push(ds);
			ds.visible = false;
		}
	}
}

だいたいこんなところだと思いますが、抜けがあったらまた更新します。

以前書いたブログもよければご参考に。

【CreateJS】AS3からHTML5 Canvasに書き出す場合のJavaScript(その1) | AS blind side
【CreateJS】AS3からHTML5 Canvasに書き出す場合のJavaScript(その2) | AS blind side

【追記】
早速、12.を追記しました(2017.10.11)。

【CreateJS】AS3からHTML5 Canvasに書き出す場合のJavaScript(その2)

swfで作ったコンテンツをHTML5 Canvasに書き出すお勉強のその2です。

今回は座標制御について。

青いボタン(btn_start)を押すとスロット(slot_mc0)が回転して、ランダムな位置で停止するというサンプルです。スロットが回転中はボタンを押せないようにしています。これをCanvasで動くように書き換えます(赤字はインスタンス名)。swfサンプルはこちら

screen_test02_02

スロット(slot_mc0)のMovieClipの構造はこんな感じです。4つの絵の入ったMovieClipが縦に2つ(img_mc0,img_mc1)並んでいて、全体をマスクしています。マスクの範囲から出てしまったら、もう一方のMovieClipの上に来るような仕組みです。(当然ながら、MovieClipはx=0で揃えてあります)。

で、ASはこんな感じ。まずはルートのタイムラインに書いてあるスクリプト。JSに変換する前提なので、型指定はしていません。
【AS:ルートのタイムライン】

var timer_msg;

//startボタン:使用可
useStartBtn();

function onOver_start(event){
	var mc = event.currentTarget;
	mc.alpha = 0.7;
}

function onOut_start(event){
	var mc = event.currentTarget;
	mc.alpha = 1;
}

function onClick_start(event){
	//startボタン:使用不可
	nouseStartBtn();

	slot_mc0.setRandom();
	slot_mc0.startTimer_spin(1000*(0+1),3);
	slot_mc0.startSlot();
}

function stopSlot(mc){
	//startボタン:使用不可
	useStartBtn();
}

function useStartBtn(){
	btn_start.addEventListener(MouseEvent.ROLL_OVER,onOver_start);
	btn_start.addEventListener(MouseEvent.ROLL_OUT,onOut_start);
	btn_start.addEventListener(MouseEvent.CLICK,onClick_start);
	btn_start.buttonMode = true;

	btn_start.alpha = 1;
}

function nouseStartBtn(){
	btn_start.removeEventListener(MouseEvent.ROLL_OVER,onOver_start);
	btn_start.removeEventListener(MouseEvent.ROLL_OUT,onOut_start);
	btn_start.removeEventListener(MouseEvent.CLICK,onClick_start);
	btn_start.buttonMode = false;	

	btn_start.alpha = 0.3;
}

スロット自体の処理は、AS版ではスロット(slot_mc0)MovieClipのタイムラインに書いています。
【AS:slot_mc0のタイムライン】

var array_pos = [0,110,220,330,440];
var speed_max = 30;
var flg_end = false;
var flg_stop = false;
var flg_remove = false;
var posNo = 0;
var imgNo = 0;
var speed;
var timer_spin;
var pos_stop;

//開始位置をランダムにする
function setRandom():void{
	var pos_y = xRandomInt(0,img_mc0.height);

	img_mc0.y = pos_y;
	img_mc1.y = img_mc0.y-img_mc1.height;

	//はみ出しチェック
	ckeckFrameOut();

	//スピード:初期化
	speed = speed_max;

	//停止フラグ:初期化
	flg_stop = false;
	flg_end = false;
	flg_remove = false;
	posNo = 0;
}

//回転
function spin(event){
	img_mc0.y += speed;
	img_mc1.y += speed;

	if (flg_end){
		checkEndPoint();
	}
	if (flg_stop){
		checkStopPoint();
	}

	ckeckFrameOut();

	if (flg_remove){
		flg_remove = false;
		//停止時の位置ずれ補正
		img_mc0.y = pos_stop;
		if (img_mc0.y < 0){
			img_mc1.y = img_mc0.y + img_mc0.height;
		} else {
			img_mc1.y = img_mc0.y - img_mc0.height;
		}

		if (img_mc1.y == 0){
			img_mc0.y = img_mc1.y - img_mc0.height;
		}

		this.removeEventListener(Event.ENTER_FRAME,spin);
		var parentObj = this.parent;
		parentObj.stopSlot(this);
	}
}

//停止地点チェック
function checkEndPoint(){
	var i;
	var dis_mim = img_mc0.height;
	var flg_check = false;

	//---スロットをランダムにする
	for (i=0;i<array_pos.length;i++){
		if (img_mc0.y >= 0 && img_mc0.y <= array_pos[i]){
			flg_check = true;
			break;
		} else if (img_mc0.y < 0 && img_mc0.y <= array_pos[i]*-1){
			flg_check = true;
			break;
		} else {
			flg_check = false;
		}
	}

	if (flg_check){
		var dis = Math.abs(array_pos[i] - Math.abs(img_mc0.y));
		if (dis < dis_mim){
			dis_mim = dis;
			posNo = i;
		}
	}

	var fugo = (img_mc0.y < 0) ? -1 :1;
	pos_stop = array_pos[posNo] * fugo;

	//絵柄番号:判定(img_mc0.yが+値の時は、img_mc1の位置が当たりになる)
	imgNo = (pos_stop < 0) ? posNo :(array_pos.length-1) - posNo;
	imgNo = (imgNo == 4) ? 0:imgNo;

	flg_end = false;

	var amari = dis_mim % speed;

	if (amari > 0){
		img_mc0.y += amari;
	}

	flg_stop = true;
}

//停止監視
function checkStopPoint(){
	if (pos_stop == img_mc0.y){
		flg_stop = false;
		flg_remove = true;
	} else {
		var margin = Math.abs(Math.abs(pos_stop) - Math.abs(img_mc0.y));
		if (margin < speed){
			speed = margin;
		}
	}
}

//はみ出しチェック
function ckeckFrameOut(){
	if (img_mc0.y >= img_mc0.height){
		img_mc0.y = img_mc1.y - img_mc0.height;
	} else if (img_mc1.y >= img_mc1.height){
		img_mc1.y = img_mc0.y - img_mc1.height;
	}
}

//ランダムな整数値を取得
function xRandomInt (nMin,nMax) {
	// nMinからnMaxまでのランダムな整数を返す
	var nRandomInt = Math.floor(Math.random()*(nMax-nMin+1))+nMin;
	return nRandomInt;
} 

//Timer開始
function startTimer_spin(interval,count){
	timer_spin = new Timer(interval,count);
	timer_spin.addEventListener(TimerEvent.TIMER,onCheckTimer_spin);
	timer_spin.addEventListener(TimerEvent.TIMER_COMPLETE,onEndTimer_spin);
	timer_spin.start();
}

function startSlot(){
	this.addEventListener(Event.ENTER_FRAME,spin);
}

function onCheckTimer_spin(event){
	var timerObj = event.currentTarget;
	var remain_count = timerObj.repeatCount - timerObj.currentCount;
	if (remain_count <= 3){
		remain_count = (remain_count == 0) ? 1 :remain_count;
		speed = Math.round(speed / remain_count);
	}
}

//Timer停止
function onEndTimer_spin(event){
	//終了フラグ:on
	flg_end = true;
}

そして、これをCanvasで書き出すために、JavaScriptで書き直したものが下記です。こちらも同じくFlash CCのルートのタイムラインに書いています。Canvasでパブリッシュしたサンプルはこちら
【Canvas版JavaScript】

//インスタンスへのMouseOver関連イベントはコレが必須。
stage.enableMouseOver();

var h_img_mc = this.slot_mc0.img_mc1.y - this.slot_mc0.img_mc0.y;

var speed_max = 30;
var array_pos = [0,110,220,330,440];

//startボタン:使用可
useStartBtn(this);

function onClick_start(event){
	//startボタン:使用不可
	nouseStartBtn(event.currentTarget.parent);

	var mc = event.currentTarget;
	var slot = mc.parent.slot_mc0;

	slot.img_mc0.height = h_img_mc;
	slot.img_mc1.height = h_img_mc;

	setRandom(slot);
	slot.startTime = createjs.Ticker.getTime(true);
	startSlot(slot);
	startTimer_spin(slot,1000*(0+1),3);

}

function onOver_start(event){
	var mc = event.currentTarget;
	mc.alpha = 0.7;
}

function onOut_start(event){
	var mc = event.currentTarget;
	mc.alpha = 1;
}

//開始位置をランダムにする
function setRandom(mc){
	var pos_y = xRandomInt(0,mc.img_mc0.height);

	mc.img_mc0.y = pos_y;
	mc.img_mc1.y = mc.img_mc0.y-mc.img_mc1.height;

	//はみ出しチェック
	ckeckFrameOut(mc);

	//スピード:初期化
	mc.speed = speed_max;

	//停止フラグ:初期化
	mc.flg_stop = false;
	mc.flg_end = false;
	mc.flg_remove = false;
	mc.posNo = 0;
	mc.remain_count = 0;

}

//はみ出しチェック
function ckeckFrameOut(mc){
	if (mc.img_mc0.y >= mc.img_mc0.height){
		mc.img_mc0.y = mc.img_mc1.y - mc.img_mc0.height;
	} else if (mc.img_mc1.y >= mc.img_mc1.height){
		mc.img_mc1.y = mc.img_mc0.y - mc.img_mc1.height;
	}
}

//ランダムな整数値を取得
function xRandomInt (nMin,nMax) {
	// nMinからnMaxまでのランダムな整数を返す
	var nRandomInt = Math.floor(Math.random()*(nMax-nMin+1))+nMin;
	return nRandomInt;
} 

//Timer開始
function startTimer_spin(mc,interval,count){
	mc.remain_count = count;
	mc.addEventListener("tick",onCheckTimer_spin);
}

function startSlot(mc){
	mc.addEventListener("tick",spin);
}

function onCheckTimer_spin(event){
	var mc = event.currentTarget;
	var wCount = Math.floor((createjs.Ticker.getTime(true) - mc.startTime)/1000);

	var count = 3 -  wCount;

	if (mc.remain_count == count){
		return;
	} else {
		mc.remain_count = count;

		if (mc.remain_count <= 3){
			if (mc.remain_count == 0){
				mc.removeEventListener("tick",onCheckTimer_spin);
				onEndTimer_spin(mc);
			} else {
				mc.speed = Math.round(mc.speed / mc.remain_count);
			}
		}
	}
}

//回転
function spin(event){
	var mc = event.currentTarget;

	mc.img_mc0.y += mc.speed;
	mc.img_mc1.y += mc.speed;

	if (mc.flg_end){
		checkEndPoint(mc);
	}
	if (mc.flg_stop){
		checkStopPoint(mc);
	}

	ckeckFrameOut(mc);

	if (mc.flg_remove){
		mc.lg_remove = false;

		//停止時の位置ずれ補正
		mc.img_mc0.y = mc.pos_stop;
		if (mc.img_mc0.y < 0){
			mc.img_mc1.y = mc.img_mc0.y + mc.img_mc0.height;
		} else {
			mc.img_mc1.y = mc.img_mc0.y - mc.img_mc0.height;
		}

		if (mc.img_mc1.y == 0){
			mc.img_mc0.y = mc.img_mc1.y - mc.img_mc0.height;
		}

		mc.removeEventListener("tick",spin);
		stopSlot(mc);
	}
}

//停止監視
function checkStopPoint(mc){
	if (mc.pos_stop == mc.img_mc0.y){
		mc.flg_stop = false;
		mc.flg_remove = true;
	} else {

		var margin = Math.abs(Math.abs(mc.pos_stop) - Math.abs(mc.img_mc0.y));
		if (margin < mc.speed){
			mc.speed = margin;
		}
	}
}

//
function onEndTimer_spin(mc){
	//終了フラグ:on
	mc.flg_end = true;

}

//停止地点チェック
function checkEndPoint(mc){
	var i;
	var dis_mim = mc.img_mc0.height;
	//var posNo = 0;
	var flg_check = false;

	//---スロットをランダムにする
	for (i=0;i<array_pos.length;i++){
		if (mc.img_mc0.y >= 0 && mc.img_mc0.y <= array_pos[i]){
			flg_check = true;
			break;
		} else if (mc.img_mc0.y < 0 && mc.img_mc0.y <= array_pos[i]*-1){
			flg_check = true;
			break;
		} else {
			flg_check = false;
		}
	}

	if (flg_check){

		var dis = Math.abs(array_pos[i] - Math.abs(mc.img_mc0.y));
		if (dis < dis_mim){
			dis_mim = dis;
			posNo = i;
		}
	}

	var fugo = (mc.img_mc0.y < 0) ? -1 :1;
	mc.pos_stop = array_pos[posNo] * fugo;

	//絵柄番号:判定(img_mc0.yが+値の時は、img_mc1の位置が当たりになる)
	imgNo = (mc.pos_stop < 0) ? posNo :(array_pos.length-1) - posNo;
	imgNo = (imgNo == 4) ? 0:imgNo;

	mc.flg_end = false;

	var amari = dis_mim % mc.speed;

	if (amari > 0){
		mc.img_mc0.y += amari;
	}

	mc.flg_stop = true;
}

function stopSlot(mc){
	//startボタン:使用可
	useStartBtn(mc.parent);
}

function useStartBtn(target){
	target.btn_start.addEventListener("mouseover", onOver_start);
	target.btn_start.addEventListener("mouseout", onOut_start);
	target.btn_start.addEventListener("click",onClick_start);
	target.btn_start.cursor = "pointer";

	target.btn_start.alpha = 1;
}

function nouseStartBtn(target){
	target.btn_start.removeEventListener("mouseover", onOver_start);
	target.btn_start.removeEventListener("mouseout", onOut_start);
	target.btn_start.removeEventListener("click",onClick_start);
	target.btn_start.cursor = "normal";

	target.btn_start.alpha = 0.3;
}

AS版ではスロット(slot_mc0)MovieClipの中に書かれていたスクリプトも、Canvas版ではルートのタイムラインにまとめてあります。MovieClipの中にスクリプトを書かない方がいいと聞いたのでと、JSだとやはり1ファイルにまとまっていたほうがわかりやすいので。

※その為Canvas版では、【slot_mc0のタイムライン】の冒頭(1〜10行目)で書かれている変数も、固定値のもの(1〜2行目)はルートに書き、その他(3〜10行目)はスロット(slot_mc0)のプロパティにしてあります。また関数には引数でMovieClipを渡すようにしてあります。

で、ここからが本題のCanvas版用にJavaScriptで書き換える時の注意点。

1.オブジェクトの基準点が違う

まず気をつけないといけないのは、Cavnasに描き出されたにMovieClipの座標は、Flash CC上での座標(+)ではなくて中心点(○)であるということ。これを知らないで座標を取得すると訳のわからない位置が表示されてハマります。なので必ずオブジェクトの座標と中心点が重なるようにしておくこと。

【参考】
・Chitose Web: Toolkit for CreateJS 座標がずれる時

2.オブジェクトのwidth,heightが取れない。

そしてもう1つ。DisplayObject.getBounds()という一見オブジェクトのサイズを返してくれそうなメソッドがありますが、これが役に立たないということ。そもそも作られた目的が違うようです。詳しくは下記を参照。

【参考】
・FN1312005 | EaselJS 0.7.1: ColorFilterクラスでインスタンスの塗りのグラデーションを反転させる | HTML5 : テクニカルノート(FumioNonaka.com)
※「03 Shapeオブジェクトに矩形領域を定める ー DisplayObject.getBounds()とDisplayObject.setBound」を参照

またDisplayObjectにwidth,heigitのプロパティがありません。スロット(slot_mc0)をループさせるために中のMovieClipの高さが必要なので、ここではimg_mc0とimg_mc1のy座標の差を高さとして算出し(【Canvas版JavaScript】19〜20行目)、img_mc0とimg_mc1に「height」というプロパティ名で渡しています(【Canvas版JavaScript】4行目)。

※ img_mc0の高さはわかっているので固定値で変数にすることも可能ですが、絵柄を増やして高さが変更になることも考慮して計算で出しています。

3.Timerの使い方

AS版ではスロット(slot_mc0)の回転をEvent.ENTER_FRAMEでやって、Timerを一定間隔で動かして回転速度と停止のコントロールをしていたのですが、EaselJSで該当するTickerは静的クラスでインスタンス化できないので複数作れません。

・EaselJS v0.8.0 API Documentation : Ticker

なので、回転開始時間をcreatejs.Ticker.getTime()で取得してスロットのプロパティに設定し(【Canvas版JavaScript】23行目)、経過時間をチェックして擬似的にインターバル処理を行っています(onCheckTimer_spin/【Canvas版JavaScript】87〜107行目)。

【参考】
・JavaScript – CreateJS(EaselJS)でタイマを管理するTickerクラスの使い方 – Qiita

※JSのTimerを使えばいいのでは?という指摘を受けましたが、書いてる時点ではCreateJSの中のメソッドしか頭になく気がつきませんでした(笑)。そちらの方が、TimerとENTER_FRAMEにそれぞれ対応していていいかもしれません。

4.thisの参照

スタートボタンの制御を関数化していますが(useStartBtn/nouseStartBtn)、ここでも問題が。AS版のようにボタンを「this.btn_start」で参照すると、オブジェクトがないよと怒られます。

[Error] TypeError: undefined is not an object (evaluating 'this.btn_start.addEventListener')

console.log(“this >> “+this)で調べてみると、ルートで参照した場合は・・・

this >> [MovieClip (name=null)]

となり、functionの中で参照した場合は・・・

this >> [object Window]

と返ってきます。

FlashCCが書き出すHTMLを見てみてると・・・

<script>
var canvas, stage, exportRoot;

function init() {
	canvas = document.getElementById("canvas");
	images = images||{};

	var loader = new createjs.LoadQueue(false);
	loader.addEventListener("fileload", handleFileLoad);
	loader.addEventListener("complete", handleComplete);
	loader.loadManifest(lib.properties.manifest);
}

function handleFileLoad(evt) {
	if (evt.item.type == "image") { images[evt.item.id] = evt.result; }
}

function handleComplete() {
	exportRoot = new lib.test02_html5();

	stage = new createjs.Stage(canvas);
	stage.addChild(exportRoot);
	stage.update();

	createjs.Ticker.setFPS(lib.properties.fps);
	createjs.Ticker.addEventListener("tick", stage);
}
</script>

となっていて、Flashからの要素をexportRootにラップしてstageにaddChildしているので、ルートで参照したときのthisの値([MovieClip (name=null)])は、exportRootを指しています。

一方、functionの中で呼び出したものはページのルートであるWindowを指すようです。

ここではカンタンにルートから呼び出す関数にはthisを渡して参照するようにしました(【Canvas版JavaScript】10行目、218〜225行目)。

※他の方法としては、直接exportRootを参照に使うか、stageの中から参照することも可能です。

exportRoot.btn_start.addEventListener("mouseover", onOver_start);
stage.getChildAt(0).btn_start.addEventListener("mouseover", onOver_start);

またはJSのFunction.bind()を使って、thisの参照を書き換えた変数を生成して呼び出す方法もあります。

var func = useStartBtn.bind(this.btn_start);
func(this);

【参考】
・FN1303001 | CreateJSで関数にスコープを定める ー proxy()関数 | HTML5 : テクニカルノート
・FN1303002 – 関数に任意のthis参照を定める – HTML5 : テクニカルノート
・JavaScript の超便利なメソッド bind で this を制御する | Foreignkey, Inc.

5.まとめ

処理 AS JavaScript
オブジェクトの座標(x,y) 座標(+) 中心点(○)
オブジェクトのサイズ(width,height) width,height プロパティなし
ルートのthis参照 root exportRoot
functionのthis参照 root Window
繰り返し処理 Event.ENTER_FRAME Ticker
タイマー Timer Timer(setTimeout/setInterval)

上記の説明は、私の環境のFlash CCで書き出される下記のバージョンのCreateJSを前提としています。

<script src="http://code.createjs.com/easeljs-0.7.1.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.5.1.min.js"></script>
<script src="http://code.createjs.com/movieclip-0.7.1.min.js"></script>
<script src="test01_html5.js"></script>

【CreateJS】AS3からHTML5 Canvasに書き出す場合のJavaScript(その1)

今更ですが文字通り、swfで作ったコンテンツをHTML5 Canvasに書き出すお勉強です。

Flash Professional CCでは「コマンド」>「AS3 ドキュメント形式から HTML5 Canvas に変換」でCanvas用のファイルに変換できます。基本的なことはこちらから。

・Flash Professional CC:今すぐ試してほしい! 新しいHTML5書き出しADC Plus

これは前から知ってるわけですが、「じゃあJS部分はどう書くか?」ってざっとググっても変換用サンプルに書かれているJSはたいていルートでの「gotoAndPlay」レベル。で、もうちょっと実際に使う形でASに書いたものをどう書き換えればいいのか?というのを少しずつまとめていきます。

screen_test01

 

とりあえず簡単なサンプルで、win_mcというMovieClip(グレーの部分)にbtn(白の部分)というMovieClipが入っている。win_mcは10フレームで、btnをクリックすると1コマずつ切り替わるというもの。swfサンプルはこちら

で、ASはこんな感じ。ルートのタイムラインに書いています。JSに変換する前提なので、型指定はしていません。

win_mc.gotoAndStop(1);

win_mc.btn.buttonMode = true;
win_mc.btn.addEventListener(MouseEvent.CLICK,onClickBtn);

function onClickBtn(event){
	var target = event.currentTarget;
	var num_max = target.parent.totalFrames;
	var num_current = target.parent.currentFrame;
	var num_next = (num_current + 1) > num_max ? 1 : (num_current + 1);
	target.parent.gotoAndStop(num_next);
}

で、これをCanvasで書き出すために、JavaScriptで書き直したものが下記です。こちらも同じくFlash CCのルートのタイムラインに書いています。Canvasでパブリッシュしたサンプルはこちら

//インスタンスへのMouseOver関連イベントはコレが必須。
stage.enableMouseOver();

this.win_mc.gotoAndStop(0);

this.win_mc.btn.cursor = "pointer";
//win_mc.btn.buttonMode = true;
this.win_mc.btn.addEventListener("click",onClickBtn);

function onClickBtn(event){
	var target = event.currentTarget;
	//var num_max = target.parent.totalFrames;
	var num_max = target.parent.timeline.duration;
	var num_current = target.parent.currentFrame;
	//var num_next = (num_current + 1) > num_max ? 1 : (num_current + 1);
	var num_next = (num_current + 1) > (num_max - 1) ? 0 : (num_current + 1);
	console.log("num_current:"+num_current+"/num_next:"+num_next+"/num_max:"+num_max);
	target.parent.gotoAndStop(num_next);
}

JavaScriptの方は書き換えた箇所に、元のASをコメントにして残してあるのでわかると思いますが、まとめると違いは下記の通り。

処理 AS JavaScript
カーソル変更 buttonMode = true cursor = “pointer”
総フレーム数取得 target.parent.totalFrames target.parent.timeline.duration
フレームの数え方 フレームカウントが1から フレームカウントが0から

で、ここが落とし穴だったんですけど、予めStage.enableMouseOver()メソッドを呼出しておかないと、マウスポインタとインスタンスの重なりについてのイベントが起こらないということ。これがないとcursor = “pointer”を設定してもカーソルは変わらない。また・・・

this.win_mc.btn.addEventListener("mouseover", mouseOverHandler);
this.win_mc.btn.addEventListener("mouseout", mouseOutHandler);

のようなマウスイベントも取れません。

こちらの記事が参考になりました。やはり困ったときは野中さん(笑)。

F-site | EaselJSでインスタンスにロールオーバーしたとき指差しカーソルにする

ちなみに私の環境のFlash CCでは、下記のバージョンのCreateJSが使われています。

<script src="http://code.createjs.com/easeljs-0.7.1.min.js"></script>
<script src="http://code.createjs.com/tweenjs-0.5.1.min.js"></script>
<script src="http://code.createjs.com/movieclip-0.7.1.min.js"></script>
<script src="test01_html5.js"></script>

パブリッシュしてもローカルにライブラリがないと思ったら、ネット上のライブラリを参照してるんですね。これも今回初めて気づきました。

【追記その1】2015.3.30
野中さんから指摘を受けましたが、総フレーム数を取得する「target.parent.timeline.duration」は、MovieClipが持っている値ではなくて、「target.parent.timeline」でTweenJSのTimelineクラスを参照して、そのプロパティであるdurationを参照している、ということのようです。

・TweenJS v0.6.0 API Documentation : Timeline > Properties > duration
・EaselJS v0.8.0 API Documentation : MovieClip > Properties > timeline

【追記その2】2015.3.30
パブリッシュするときにCreateJSのライブラリをホスト(ネット上のライブラリ)にするか、ローカル書きだしにするかは「パブリッシュ設定」で変更できます。また書き出しに使用されるバージョンは、Flash CC側で固定になっているそうです。こちらも野中さんからご教授いただきました。

・FN1312001 | Flash Professional CC 13.1のHTML5 CanvasドキュメントとCreateJS | HTML5 : テクニカルノート(FumioNonaka.com)

Flash CS6/CCでコードヒントが表示されない

今日仕事の話をしていて、ウチのFlash IDEではコードヒントが出てないことに気づいた。というか、かなり前から出たり出なかったりだったので、ほとんどヒントなしで書いていた。昔はなかったし(笑)。
ただ「ウチだけ?」みたいな状況が気になったのと、カスタムクラスを使う必要があってないと不便だな…というのがあったので、調べてみました。
※CCだとアクションパネルの下部に「コードヒントキャッシュを構築できません」とビックリマークアイコン、CS6だとビックリマークアイコンだけが表示されています。
※どちらもコードヒントのキャッシュを削除では直りませんでした。
【Flash CS6】
‘/Users//Library/Preferences/Flash CS5 Preferences’ をテキストエディタで開いて、
<Editor_Code_Hints>1</Editor_Code_Hints>の値を1 >> 1000 に変更してOK。
【Flash CC】
同じように修正しようと思って、’/Users//Library/Preferences/Adobe/Flash/13.0/Application.xml’ に書いてある…
 
<key>Editor Code Hints</key>
<int type=’signed’ size=’32’>1</int>
の数値をいじっても直らず。再インストールしかないかなあと思ったものの、下記を参考にActionScript3.0設定に追加してあったライブラリパス、ソースパスを全て削除したら、無事表示されました。
結局のところ、ファイル毎(fla,xfl)ではなくて、Flash IDEの方に何でもライブラリパスを通すという昔からの癖が問題だったようです。たぶんCS6もライブラリパスを消せば問題なく動くはず。
意外なところではまりました。参考サイトに感謝。

Box2D 2.0.0のサンプルを2.1aで動かすための注意点

EaselJSのお勉強でBox2DWebを触ったので、その先を進めようとしたら意外とサンプルがない。JS版も基本的にBox2DFlashAS3と同じらしいんで、サンプルを漁ってみたもののどれも古くてそのままでは動かない。なんとか動くようにできたけど、色々変わってる点もがあり、それについて書かれた日本語ページを見つけられなかったので、今後のためにメモ。

※ Box2DFlashAS3 2.0.0 を 2.1a で動かすための変更点です。
まず、古いサンプルだとパブリッシュ設定がFlash Player9 になってる場合が多いけど、これだとこんなエラーが出ます。
行 8711046: 型が見つからないか、コンパイル時定数ではありません : b2Fixture。
これはFlash Player 10以上にすることで解決します(Flash Player11.4+Flash CS6で確認)。
次に Box2D.Dynamics.b2world クラスの b2wolrd メソッドの引き数が変わっています。
(2.0.0)world = new b2World(worldAABB, gravity, true);
(2.1a )world = new b2World(gravity, true);
それから Box2D.Collision.Shapes 配下のクラスが変わっています。
(2.0.0)b2CircleDef >> (2.1a)b2CircleShape
(2.0.0)b2PolygonDef >> (2.1a)b2PolygonShape
そして Box2D.Dynamics.b2world クラスの bodyを作る為のメソッドが変わっています。
(2.0.0)var floor:b2Body = world.CreateStaticBody(floorBodyDef);
(2.1a var floor:b2Body = world.CreateBody(floorBodyDef);
2.0.0では、作るbodyの種類毎に world.CreateStaticBody、world.CreateDynamicBody とメソッドがあったようですが、2.1a では1つになっています。で、staticとかdynamicの指定は、Box2D.Dynamics.b2BodyDef で設定するようになっています。
2.1a 
var bodyDef:b2BodyDef = new b2BodyDef();
bodyDef.type = b2Body.b2_dynamicBody;
また 2.0.0 では Box2D.Collision.Shapes に指定していた密度、摩擦係数、弾性(density,friction,restitution)は、2.1a では Box2D.Dynamics.b2FixtureDef に指定します。
(2.0.0)
var wheelShape:b2CircleDef = new b2CircleDef();
wheelShape.radius = 0.15;
wheelShape.density = 1;

(2.1a 
var wheelfixtureDef:b2FixtureDef = new b2FixtureDef();
wheelfixtureDef.shape = wheelShape;
wheelfixtureDef.density = 1;
よくサンプルで使われている Box2D.Dynamics.b2DebugDraw もメソッドが大幅に変わっています。「m_…」が「Set…」に変更されています。
(2.0.0)
debugDraw.m_sprite = this;
debugDraw.m_drawScale = DRAW_SCALE;
debugDraw.m_fillAlpha = 0.3;
debugDraw.m_lineThickness = 1;
debugDraw.m_drawFlags = b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit;

(2.1a 
debugDraw.SetSprite(this);
debugDraw.SetDrawScale(DRAW_SCALE); 
debugDraw.SetFillAlpha(0.3);
debugDraw.SetLineThickness(1)
debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit | b2DebugDraw.e_pairBit);
最後に、物理シミュレーションを更新するための world.step() は、引数が2つから3つに変更されています。
(2.0.0)world.Step(1 / 24, 10);
(2.1a )world.Step(1 / 24, 10, 10);
そして Box2D.Dynamics.b2DebugDraw を使う際は、world.Step() を呼び出したあとに・・・
world.DrawDebugData();
が、必要なようです。
とりあえずココまでの対処で、2.0.0 のサンプルを 2.1a で動かすことができました。まだ理解し切れてるとはいえないので、誤り等ありましたらご指摘下さい。下記サンプルを書き換えて動作確認しました(MaxOSX10.7.6+Flash CS6+Box2DWeb 2.1a)。感謝。
【追記】2013.7.21 東京ひよこの会 第5回で、この内容を発表した際に見つけた誤りを修正しました。
(誤)(2.0.0)b2CircleDef >> (2.1a)b2PolygonShape
(正)(2.0.0)b2PolygonDef >> (2.1a)b2PolygonShape
 

AIR for iOSでCameraUIを使ったアプリが落ちる件について

Flash Bulider4.6でCameraUIクラスを使ったiOSアプリのテストをしていたんですが、何度かカメラを呼び出して(CameraUIはiOSのカメラを呼び出す仕様)いると、何度目かで必ずアプリが落ちる…という現象が発生しました。

ネットで探したサンプルコード(Adobe AIR for iOSによるiPhone/iPadアプリケーション開発第4回 カメラとカメラロール | クラスメソッド開発ブログ)に手を加えてテストしていたので、「どこか自分で書き足したところがおかしいんだろうな」と色々テストするも解決せず。元のサンプルコードに戻した状態でも状況は変わらず。
で、いろいろググってみたら、こんな記事を発見。
この記事によると「Flex SDK4.6に問題があって、iOS上ではカメラを使うアプリは使えない」とのこと。がーん…。
ちょうどFlash Builder4.7がリリースされたタイミングでもあるので、一縷の望みをかけて、Flash Builder4.7+Apache Flex4.8+AIR3.4の環境を構築してテストしてみました。
結果は問題なく動作しました。Flex 4.8ならOKなようです。しかしこの問題、いろいろググった割に関連記事が1つしかないとか…。AbodeのFAQとかに載っていてもよさそうですが。

QuickBox2Dの衝突判定とビット単位の論理積

今更ながら周りの影響で、QucikBox2Dをいじってます。@alumican_net さんが書かれたブログがわかりやすいので拝見してますが、『 QuickBox2DやBox2DFlashAS3での衝突判定をグループ分け 』という記事で以下のような記述がありました。
自分と何かが衝突するかどうかは、自分のmaskBitsと相手のcategoryBitsの論理積をとって計算できる。結果が0なら衝突しない、0以外なら衝突する。
普段あまり使わないので「論理積ってなに?」って状態だったんで調べてみると、大重美幸さんのActionScript3.0入門ノートにわかりやすい解説がありました。
ビット単位の論理和では値を2進数に換算して各ビットごとに比較し、どちらかが1ならば1にします。たとえば、6(2進数で110)と5(2進数で101)のビット単位の論理和は7(111)になります。同様にビット単位の論理積では両方が1のときに1にし一方が0ならば0にします。6と5のビット単位の論理積は4(100)になります。
2進数における桁のシフトは2のn階乗で変化します。次のサンプルで使われているrgbToHex()はRGBの各色の値から0xFFFFFFの形式の16進数に変換する関数です。

なるほど!ということで、実際に計算してみました。


maskBits:0x0007(0111) &  categoryBits:0x0004(0100) >> 4(0100):衝突する

maskBits:0x0007(0111) &  categoryBits:0x0008(1000) >> 0(0000):衝突しない

maskBits:0x0007(0111) &  categoryBits:0x0002(0010) >> 2(0010):衝突する

maskBits:0x0007(0111) &  categoryBits: 0x00020010) >> 2(0010):衝突する


maskBits:0x0007(0111) &  categoryBits:0x0008(1000) >> 0(0000):衝突しない

maskBits:0x0007(0111) &  categoryBits: 0x00040100) >> 4(0100):衝突する

・■

maskBits:0x0001(0001) &  categoryBits: 0x00020010) >> 00000):衝突しない



maskBits: 0x00010001) &  categoryBits: 0x00040100) >> 0(0000):衝突しない

maskBits: 0x00010001) &  categoryBits: 0x00081000 >> 0(0000):衝突しない

ふむふむ。確かに先の引用通り「結果(論理積)が0なら衝突しない、0以外なら衝突する。」になっています。

そこでを categoryBits:0x0001, maskBits:0x0003 に変更してみました。すると・・・

maskBits:0x0003(0011) &  categoryBits:0x0004(0100) >> 0(0000):衝突しない


maskBits:0x0007(0111) &  categoryBits: 0x00010001) >> 1(0001):衝突する


と、基準を変えて計算すると違う結果になってしましました。
そこで色々調べてみたら、QucikBox2Dのユーザーマニュアルに次のように書いてありました。

Here is the rule for a collision to occur:

uint16 catA = fixtureA.filter.categoryBits;

uint16 maskA = fixtureA.filter.maskBits;

uint16 catB = fixtureB.filter.categoryBits;

uint16 maskB = fixtureB.filter.maskBits;

 

if ((catA & maskB) != 0 && (catB & maskA) != 0)

{

  // fixtures can collide

}

つまりAとBを比較する時に「それぞれを基準に計算した結果が、どちらも0でない場合に衝突する」ということなんんですね。上に書いたの場合とも合致しますね。納得。
以下のサイトを参考にしました。感謝。

NetStreamで使用するflvの相対位置

NetStreamを使ってるswfをHTMLと別階層に配置することになって、flvが読めなくなって混乱。改めてヘルプを見ると、flvHTMLでなくswfからの相対パスになるってことなのね。以下引用。

ローカルファイルの再生

メディアファイルの場所。引数には、String、URLRequest.url プロパティ、またはこのどちらかを参照する変数を使用できます。アプリケーションセキュリティサンドボックス外にある Flash Player および AIR コンテンツの場合、SWF ファイルまたはサブディレクトリと同じディレクトリに保存されているローカルビデオファイルを再生できます。ただし、上位レベルのディレクトリに移動することはできません。

アプリケーションセキュリティサンドボックス内の AIR コンテンツの場合、メディアファイルに指定するパスは、SWF ファイルのディレクトリに相対します。ただし、SWF ファイルのディレクトリより上位に移動することはできません。AIR でパスを相対パスとして処理するため、完全なパスは指定しないでください。

「参照するパスの起点はHTML」って考えが染みついていると、普段HTMLとswfは同階層に置くことが多いので、ちょっと混乱しました(笑)。ヘルプの該当箇所はこちらから。

XMLから取り出したエレメントの親への参照

XMLをいじっていて不思議な現象が。下記のようなXMLを定義します。

var xml:XML =
<itemlist>
<itemGroup name=”X”>
<itemGroup name=”XX”>
<itemGroup name=”XXX”>
<item name=”XXX001″ />
<item name=”XXX002″ />
<item name=”XXX003″ />
<item name=”XXX004″ />
<item name=”XXX005″ />
</itemGroup>
</itemGroup>
<item name=”X-001″ />
<item name=”X-002″ />
</itemGroup>
</itemlist>;
この中のitemエレメントで@name=”XX001″のデータの親を参照すると、下記のようになります。これは問題なし。
trace(xml..item.(@name==”XXX001″).parent());
—-
<itemGroup name=”XXX”>
  <item name=”XXX001″/>
  <item name=”XXX002″/>
  <item name=”XXX003″/>
  <item name=”XXX004″/>
  <item name=”XXX005″/>
</itemGroup>
次に同じ条件でitemエレメントを検索して、それを別XMLの子として追加します。
var xmlList:XMLList = xml..item.(@name==”XXX001″);
var xml2:XML = new XML(“<top></top>”)
for each (var item:XML in xmlList) {
xml2.appendChild(item);
}

xml2に対して、itemエレメントで@name=”XX001″のデータの親を参照すると、下記のようになります。これも問題なし。

trace(xml2..item.(@name==”XXX001″).parent());
—-
<top>
  <item name=”XXX001″/>
</top>

この後に、元々のXML(xml)に対して、同じく親を参照すると・・・

trace(xml..item.(@name==”XXX001″).parent());
—-
<top>
  <item name=”XXX001″/>
</top>
と、別XMLの親を参照してしまいます。該当エレメントを別XMLに追加してしまうと、元XMLの該当エレメントの参照も変わってしまうようなのです。
これってどういうことなんでしょうね???もっとも別XMLにしたいなら、XML.copy()で複製を作れって話ではあるんですけけど、ちょっと納得いかない感じです。