学生さんに質問されたので調べてみた。
質問の内容はFlashの事だったが。

ステージ上に人間の顔があり、
目と鼻と口がフレーム毎に入れ替わるムービーがある。
それらのパーツを個別に操作(stop()だったりPlay()だったり)できるようにしたい。

といった内容だった。

現状、ボタンはパーツ分(ストップ用とスタート用)用意しているがどれも同じ挙動になってしまう(どのスタートボタンを押してもすべてのパーツがPlay()してしまい、どのストップボタンを押してもすべてのパーツがストップしてしまう)との事だった。

flaファイルを見てみると、メインのタイムラインしか無く(5フレーム分)それぞれのスタートボタン、ストップボタンには

on (press) {stop();

}

もしくは

on (press) {play();

}

と書いてあるだけだった。
なるほどこれじゃあ無理だわなあ。
という訳でこの場合の解決策は

メインのタイムラインは1フレームでいい。
そして各パーツをシンボル化した後、メインステージに配置(このシンボルは独自のタイムラインを持っている)。
インスタンス名を付けた後にそれぞれ該当するボタンに対して

on (press) {
  インスタンス名.stop();
}

などと記述すればいい。 このファイルに関してはとりあえず問題は解決した。

ただこのファイルはたまたまActionScript2.0のファイルだったからこの書き方でも良かったが今後はおそらく消えていくスタイルだろう。
というのも、ActionScript3.0になるとそもそもムービークリップやボタンインスタンスにはスクリプトを記述する事が出来なくなった。
タイムラインか外部にクラスファイルを用意してオブジェクト指向で・・というのが今後のコーディングスタイルらしい。
なので上の例をもしAS3で書くと

eye.addEventListener(MouseEvent.CLICK, func);
nose.addEventListener(MouseEvent.CLICK, func);
mouth.addEventListener(MouseEvent.CLICK, func);

function func(evt:MouseEvent) {
	evt.target.stop();
}

と、こうなる。
ここではボタンを作成するのが面倒だったのとムービークリップをトグルボタンとして使いたかったので各ムービークリップにたいしてイベントを追加。
それぞれのパーツをクリックした時にそれらの動きが止まるようになっている。
ここで、またまた問題が。
このままだと動きを止める事は出来るがもう一度動かしたい時にはそのアクションを書く場所が無い。
もちろんボタンを作成して

インスタンス名.stop();

と記述する事は出来るがここではムービークリップ自体をトグルボタンとして使いたいのでボタンを作成しては意味が無い。
一つ考えられる方法としてはグローバルな変数を用意してそれをフラグとして利用する方法だ。
AS2の書き方なら

onClipEvent (load) {
	var flag = true;
}
on (release) {
	if (flag) {
		this.stop();
	} else {
		this.play();
	}
	flag = !flag;
}

ここでは変数flagをフラグとして使っている。
こんなコードを各ムービークリップに直接書き。
そのムービークリップをステージ上でコピー。
コピー後インスタンスの入れ替え。

とする事で実現できただろう。
しかしAS3ではムービークリップに直接書く事は出来ない。
となるとタイムラインに書く事になるんだが、グローバル変数を使うとものすごく面倒くさい事になる。
まず、ここでは目、鼻、口と3つのパーツがあるのでそれぞれにフラグが必要になる。
3つでも大変なのに今後例えば耳、髪型などなど増えるたびにそれらの変数も考えなければならない。
さらにaddEventListener関数に(第二引数として)渡しているコールバック関数もこのグローバル変数を参照させなければいけないので3つ分用意しなければいけないという事になってしまう。
単純にムービークリップが一つだけだったとしたら
AS3の書き方で

var flag = true;
nose.addEventListener(MouseEvent.CLICK, func);

function func(evt:MouseEvent) {
	if (flag) {
		evt.target.stop();
	} else {
		evt.target.play();
	}
	flag = !flag;
}

こう書けば目的は達成される。
ただ、パーツが2つ3つと増えていくと増えるたびにこの組み合わせを書かなくてはならない。
じゃあAS2の方が良かったのか、というと全然そうでもない。
AS2の場合は各ムービークリップに書けるので変数名等はそのまま使えて、例えばthisのように抽象的な書き方をしていれば全く同じ内容で動かす事は出来る。
ただしスクリプトをいろんな所に書けるという事はそれだけ分散する可能性がありちょっとした修正を加えたい時には今度は書いた箇所を捜すのが大変になってしまう。

ではでは、どうすればいいか?

これはクロージャを使う事であっさりと解決する。
具体的なコードは

eye.addEventListener(MouseEvent.CLICK, func());
nose.addEventListener(MouseEvent.CLICK, func());
mouth.addEventListener(MouseEvent.CLICK, func());

function func() {
	var flag = true;
	return function(evt:MouseEvent) {
		if (flag) {
			evt.target.stop();
		} else {
			evt.target.play();
		}
		flag = !flag;
	}
}

これだけだ。
通常addEventListener関数の第二引数は関数名であり関数の呼び出しではない。
なのに上のコードはfunc()とカッコを付ける事でfunc関数を呼び出した結果を引数としている。
これは関数を返す関数、よく高階関数といわれるものだ。
それにより無名の関数オブジェクトがaddEventListener関数に渡されオブジェクトをクリックした時にその無名関数が実行される。
さらにfunc関数を呼び出した時に1度だけflag変数がtrueで初期化される。
その後オブジェクトをクリックするたびに無名関数の中でそのflagが反転されるという訳だ。
このflagという変数は各オブジェクトごとに独立しているので他のパーツには全く影響ない。
これだと今後、耳や髪型などを追加しても

ear.addEventListener(MouseEvent.CLICK, func());
hair.addEventListener(MouseEvent.CLICK, func());

とするだけでいい。
こんな時クロージャは非常に強力だ。
これが使えないPHPは、ああ、なんて悲しい。

カテゴリ:

トラックバック(0)

トラックバックURL: http://blog.beanz-net.jp/beanz_mtos/mt-tb.cgi/36

コメントする