サイトトップ

Director Flash 書籍 業務内容 プロフィール

Macromedia Flash非公式テクニカルノート

forループでMovieClipに設定したイベントハンドラメソッドから変数を参照する

ID: FN0601002 Product: Flash

Platform: All
Version: 7.0 and above

forステートメントを使って、複数のインスタンスをまとめて処理する場合の問題です。そのループ処理中で、MovieClipインスタンスにイベントハンドラメソッドを設定するとき、メソッドに設定した(コールバック)関数内からforステートメントのカウンタ変数を参照すると、意図した結果にならないことがあります。

タイムライン上でMovieClipに対してイベントハンドラメソッドを設定する場合には、メソッドの(コールバック)関数内では、this参照とthisなしの「デフォルト参照」が異なります。this参照とデフォルト参照との違いについて、まだ理解が十分でないという方は、先に「Buttonのthis」をお読みください。

1. forステートメントで複数のMovieClipにイベントハンドラメソッドを定義する
つぎのスクリプトは、forステートメントで3つのMovieClipを作成し、それぞれに配列から取出した文字列のインスタンス名を設定します(スクリプト001)。そして、それらのMovieClipインスタンスには、MovieClip.onMouseDownイベントハンドラメソッドを定義しています。

イベントハンドラメソッドに設定した(コールバック)関数には、テスト用のtrace()ステートメントがあるだけです。インスタンス自身のターゲットパスthisと、配列からエレメントをひとつ取出して出力します。配列から取出すエレメントのインデックスには、forステートメントで使用したカウンタ変数iを指定しています。

スクリプト001■複数のMovieClipにイベントハンドラメソッドを定義

// タイムライン: _root
// フレームアクション
var my_array:Array = ["my0_mc", "my1_mc", "my2_mc"];
for (var i:Number = 0; i<3; ++i) {
  var name_str:String = my_array[i];
  var _mc:MovieClip = this.createEmptyMovieClip(name_str, i);
  _mc.onMouseDown = function() {
    trace([this, my_array[i]]); // MovieClipのパスと配列エレメントを出力
  };
}

MovieClip.onMouseDownイベントは、ステージのどこをクリックしても発生します。したがって、ステージ上をクリックすれば、生成した3つのMovieClipインスタンスすべてのMovieClip.onMouseDownイベントハンドラメソッドが呼出されます。そのとき、[出力]結果はどうなると予想しますか?

[出力]パネルには、つぎのように表示されます(図001)。ひとつめのターゲットパスは正しく出力されるものの、ふたつめの配列エレメントがすべてundefinedになっています。これは、なぜでしょう?

図001■スクリプト001の[出力]パネルの表示

_level0.my2_mc,undefined
_level0.my1_mc,undefined
_level0.my0_mc,undefined

my_array[i]の値がすべてundefinedになる

2. forステートメントのカウンタ変数はどこに設定されるか
前述のスクリプト001のtrace()ステートメントに[出力]項目をひとつ追加し、さらにforループの後にもtrace()ステートメントを加えてみましょう(スクリプト002)。

スクリプト002■タイムラインとイベントハンドラ内のカウンタ変数値を確認

> // タイムライン: _root
// フレームアクション
var my_array:Array = ["my0_mc", "my1_mc", "my2_mc"];
for (var i:Number = 0; i<3; ++i) {
  var name_str:String = my_array[i];
  var _mc:MovieClip = this.createEmptyMovieClip(name_str, i);
  _mc.onMouseDown = function() {
    trace([this, my_array[i], i]);
  };
}
trace(i);

[出力]を見るとつぎのように、タイムライン上とイベントハンドラメソッドの関数内のいずれも、カウンタ変数iの値が3になっています(図002)。つまり、3つのインスタンスすべてのイベントハンドラメソッドが、同じタイムライン上のカウンタ変数iを参照しているということです。

図002■スクリプト002の[出力]パネルの表示

3
// ステージ上をクリックすると
_level0.my2_mc,undefined,3
_level0.my1_mc,undefined,3
_level0.my0_mc,undefined,3

カウンタ変数iの値は共通になる

(1) 生成した3つのMovieClipには、それぞれ配列から順に取出したインスタンス名が設定され、それらすべてにMovieClip.onMouseDownイベントハンドラメソッドが正しく定義されています。つまり、この処理についてだけみれば、カウンタ変数iがそれぞれ0、1、2と別個に認識されたということです。

(2) しかし、イベントハンドラメソッドの(コールバック)関数内から参照したカウンタ変数iは、すべて同じ値3になってしまっています。forステートメントはカウンタ変数iが2になるまではループ内の処理を行い、3にカウントアップされると継続条件を外れてループ処理を終了します。つまり、3というのは、ループ処理を終えたカウンタ変数iの値です。

この(1)と(2)の処理結果の違いを、正しく理解する必要があります。

(1)のインスタンス名の設定は、forループの1回の処理でそれぞれ完結します。配列からインデックスiのエレメントを取出し、新たに生成したMovieClipのインスタンス名として設定します。取出されたエレメントの文字列は、インスタンス名として設定されたときには、すでに変数iを参照していません[*1]。

イベントハンドラメソッドも1回の処理でMovieClipへの設定は済み、つぎのループでカウンタ変数の値が変わったからといって、設定済みのターゲットインスタンスがその値に連動して変更されることはありません。

ところが、(2)のイベントハンドラメソッド内のカウンタ変数iは、状況が異なります。変数iは、MovieClip.onMouseDownイベントが発生したとき、(コールバック)関数が呼出されて値が参照されます。このとき変数iにはターゲットがありませんので、デフォルト参照であるスクリプトの記述されたタイムライン上の値を探します

タイムライン上には、forループの処理を終えたカウンタ変数iが値3をもっています。したがって、どのMovieClipインスタンスも、スクリプトの記述場所にあるタイムライン変数iを、共通に参照する結果となったのです。

[*1] 実際には、エレメントの文字列が取出されて、ローカル変数name_strに代入された時点で、変数iとの関係は切れます。文字列は、オブジェクトのような参照のかたちでなく、値として変数に代入されるからです。

3. イベントハンドラメソッドからMovieClipごとに異なる値を参照させる
それでは、イベントハンドラメソッドに設定した(コールバック)関数から、forループの処理で用いられたiの値を参照するには、どうしたらよいでしょう?

スクリプトの記述場所のタイムライン変数iは値が変化しますし、インスタンスにかかわらず共通の参照になってしまいます。したがって、インスタンスごとの値として、MovieClipに個別に設定する必要があるのです

つぎのスクリプトは、各ループ処理中にカウンタ変数iの値を、MovieClipに同名のタイムライン変数iとして設定します(スクリプト003)[*2]。そうすれば、カウンタ変数の値が変わっても、個々のMovieClipに設定された(タイムライン)変数の値はそのまま保持されます。

スクリプト003■MovieClipに値を設定してイベントハンドラ内から参照

// タイムライン: _root
// フレームアクション
var my_array:Array = ["my0_mc", "my1_mc", "my2_mc"];
for (var i:Number = 0; i<3; ++i) {
  var name_str:String = my_array[i];
  var _mc:MovieClip = this.createEmptyMovieClip(name_str, i);
  _mc.i = i;
  _mc.onMouseDown = function() {
    trace([this, my_array[this.i], i, this.i]);
  };
}

MovieClip.onMouseDownイベントハンドラメソッドを定義する直前に、MovieClipインスタンス(ローカル変数_mcに格納)に変数iを設定し、forステートメントのカウンタ変数iの値を代入しています。これで、カウンタ変数iの値がつぎのループで変わっても、インスタンスに設定された(タイムライン)変数iはそのまま保持されます。

MovieClipに定義したイベントハンドラメソッドの(コールバック)関数内から、インスタンスに設定されたタイムライン変数を参照するには、thisが必要になりますthisをつけないデフォルト参照は、スクリプトを記述したタイムラインになってしまうからです。

ステージをクリックすると、[出力]パネルには以下のように表示されます(図003)。this.iは各MovieClipインスタンスに設定されたタイムライン変数を参照するので、それぞれ異なった値になっています。

図003■スクリプト003の[出力]パネルの表示

_level0.my2_mc,my2_mc,3,2
_level0.my1_mc,my1_mc,3,1
_level0.my0_mc,my0_mc,3,0

MovieClipに設定されたタイムライン変数iの値は別個になる

[*2] カウンタ変数iもタイムライン変数です。しかし、カウンタ変数iはスクリプトを記述しているタイムラインの変数であるのに対し、各インスタンスに設定する変数iはそれぞれのMovieClipごとのタイムライン変数になります。

_____

作成者: 野中文雄
作成日: 2006年1月22日


Copyright © 2001-2006 Fumio Nonaka.  All rights reserved.