サイトトップ

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

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

onハンドラを使わずにロールオーバー/ロールアウトを検出する

ID: FN0308001 Product: Flash

Platform: All
Version: MX and Above

1. onイベントハンドラアクションが使えない場合
ボタンシンボルの中にButtonインスタンスを入れ子にして配置すると、ボタンとして正しく動作しないことがあります。Flash MXではMovieClipにも'on'イベントハンドラアクションが使えますので、このような場合にはムービークリップシンボルにButtonインスタンスを入れ子にすることで対処できます。

しかし、大きいボタンの内側に小さいボタンを配置する場合には、意図した結果が得られない場合があります。'on'イベントハンドラアクションは、イベントを排他的に受取ります。つまり、2つのボタンが重なった領域でマウス操作をすると、手前のインスタンスしかイベントを受取れません。また、この仕様と整合性を保つため、大きいボタンの内側(の前面)に配置したボタンにロールオーバーすると、大きいボタンはマウスポインタが領域内にあるにもかかわらず'rollOut'イベントを受取ってしまいます(図1-1。サンプルSWF)。

// MovieClip: 大きいボタン
// MovieClipアクション
on (rollOver) {
  trace("rollOver");
}
on (rollOut) {
  trace("rollOut");
}


【図1-1】別の小さいボタンにロールオーバーすると、大きいボタンには'rollOut'イベントが送られる

この動作は、ムービークリップシンボルにButtonインスタンスを入れ子にした場合にかぎりません。別々のButtonまたは('on'ハンドラを設定した)MovieClipインスタンスをレイヤーに分けて配置しても、重なったインスタンスの手前側がマウスイベントを排他的に受取ります。

この仕様は、たとえばポップアップメニューをつくろうとするときに、問題になります。メニューが表示された状態で、各メニュー項目のボタンは、クリックすると対応した処理が実行されます。他方で、メニューの背景イメージからロールアウトしたときに、メニューを閉じる動作にしたいことがあります(図1-2)。


【図1-2】ポップアップメニューのメニュー項目と背景イメージの配置

背景イメージのButtonあるいはMovieClipインスタンスに'on (rollOut)'ハンドラを設定してメニューを閉じる処理を記述すると、各メニュー項目にロールオーバーしても背景のインスタンスに'rollOut'イベントが発生して、メニューが閉じてしまいます。したがって、この場合の背景イメージには、'on'ハンドラでスクリプトを書くことができないのです。

2. onClipEventハンドラでロールオーバー/ロールアウトを処理する
MovieClipアクションの'onClipEvent'イベントハンドラアクションは、イベントの対象となるインスタンスを特定しません。つまり、イベントが発生すれば、そのイベントを指定した'onClipEvent'ハンドラをもつすべてのインスタンスがそれを受取ります。たとえば、'onClipEvent (mouseDown)'イベントハンドラアクションにスクリプトを記述すると、そのインスタンス上にかぎらずどこをクリックしてもスクリプトは実行されます。

したがって、'onClipEvent'イベントハンドラアクションでロールオーバー/ロールアウトの処理をすれば、インスタンスが重なっていても、背面にあっても問題なく対応できるということになります。しかし、'onClipEvent'ハンドラには、ロールオーバーやロールアウトのイベントがありません。「インスタンスを特定しません」ので、ロールオーバーやロールアウトの対象が定まらず、イベント自体想定できないからです。

ではどうするかというと、'onClipEvent (enterFrame)'で処理します。'enterFrame'イベントは、画面(ステージ)の描画が更新されるたびに発生します。デフォルトなら、12fpsで1秒間に12回ということになります。スクリプトによるアニメーションの処理に用いられることが多いです。この'onClipEvent (enterFrame)'で、マウスポインタの座標がインスタンス上にあるかどうかを判定して、処理を行えばよいのです。

指定した座標がMovieClipインスタンス上にあるかどうかは、'MovieClip.hitTest'メソッドで判定することができます。MovieClip上にマウス座標があるかどうかは、つぎのMovieClipアクションで判定することができます。

// MovieClip: マウスポインタがインスタンス上にあるかどうかを判定
// MovieClipアクション
onClipEvent (enterFrame) {
  if (this.hitTest(_root._xmouse, _root._ymouse, true)) {
    trace("inside");  // テスト用: ポインタがインスタンス上
  } else {
    trace("outside");  // テスト用: ポインタがインスタンス外
  }
}

'MovieClip.hitTest'メソッドのターゲットには、判定の対象となるMovieClipのパスを指定します。このスクリプトでは、対象のMovieClip自身にスクリプトを設定していますので、ターゲットは'this'(自分自身)でよいです。

'MovieClip.hitTest'メソッドは、第1と第2引数に判定したいxy座標をそれぞれ指定します[*1]。座標は、MovieClipインスタンスのパスにかかわらず、つねにステージを基準とします。したがって、マウスポインタの座標なら、_root._xmouseと_root._ymouseを渡します。第3引数は、オプションです。指定しないか、'flase'を渡すと、インスタンスの境界ボックスを基準として重なりを判定します。ですから、MovieClipがどのような形状であっても、矩形で判定されることになります。具体的な形状を元に重なりを判定させたいときには、第3引数に'true'を指定します。

*1 'MovieClip.hitTest'メソッドには、重なりを判定したい相手のMovieClipのパスを指定することもできます。このシンタックスでは、引数はひとつだけになります。この場合の重なりは、つねに互いの境界ボックスの範囲で判定されます。具体的な形状同士の重なりを判定するオプションは、ありません。

'MovieClip.hitTest'メソッドで判定した結果、座標がMovieClipインスタンス上にあれば'true'が、インスタンス外なら'false'が返ります。ですから、'if'アクションのステートメントは座標がMovieClip上にあるときに、'else'アクションのステートメントはMovieClip外にあるときに処理されます。

上記のスクリプトで、一応マウスポインタがMovieClipインスタンス上にあるかどうかの判定はできます。しかし、テスト用に挿入した'trace'ステートメントの結果をみればわかるとおり、毎フレーム(デフォルトなら毎秒12回)、座標がインスタンス上にあるかどうかの結果を出力し続けます。つまり、同じステートメントが繰返し実行され続けてしまうということです。

'on (rollOver)'や'on (rollOut)'イベントハンドラアクションのように、マウスポインタがインスタンスに重なったとき、あるいは外に出たときに1度だけ処理が実行されるようなスクリプトを考えてみることにしましょう。

3. ロールオーバー/ロールアウトの状態の変更を判定する
ロールオーバーからロールアウトに、あるいはその逆に状態が変わったときに1度だけ処理を行うようにするには、前の状態を記録しておく必要があります。こういう場合には、ブール(論理)値の変数に記録するのが通常です。このような処理を「フラグ」による判定と呼ぶこともあります(拙著『オブジェクト指向で考えるActionScript − Flash MXでより進んだWebデザイン作成 −』p.27〜「1.4 条件判定を行う − フラグを使った処理」参照)。変数をbOutとし、マウスポインタがインスタンス上にあったら'false'、インスタンス外なら'true'を設定することにしましょう。

まず、判定の処理手順を考えます。'MovieClip.hitTest'メソッドで、マウスポインタの座標が(1)インスタンス上にあるか、(2)インスタンス外にあるかを調べます。(1)インスタンス上にあった場合は、(3)その前にインスタンス外だった、つまり変数bOutの値が'true'だったら、ロールオーバーしたことになります。ポインタが(2)インスタンス外にあった場合も、同様に(3)その前にインスタンス上にあった、つまり変数bOutが'false'だったら、ロールアウトになる訳です。

この処理手順を実際のMovieClipアクションにすると、以下のようになります。

// MovieClip: ロールオーバー/ロールアウトを検出
// MovieClipアクション
onClipEvent (load) {
  bOut = true;  // [1] 変数の初期値設定
}
onClipEvent (enterFrame) {
  // [2] マウスポインタ座標の重なり判定
  if (this.hitTest(_root._xmouse, _root._ymouse, true)) {
    if (bOut == true) {  // [3] その前にインスタンス外だったかの判定
      trace("rollOver");  // テスト用: ロールオーバー
      bOut = false;  // [4] 変数値の更新
    }
  } else {
    if (bOut == false) {  // [5] その前にインスタンス上だったかの判定
      trace("rollOut");  // テスト用: ロールアウト
      bOut = true;  // [6] 変数値の更新
    }
  }
}

[1] 'onClipEvent (load)'イベントハンドラアクションで、まず変数bOutの初期値を設定します。値を'true'つまりインスタンス外の設定にしたので、このMovieClipが表示されたときマウスポインタがインスタンス外にあった場合にはロールアウトという判定はされません。'MovieClip.hitTest'メソッドの返す値が、変数bOutと同じで、状態の変更がないことになるからです。

[2] 'onClipEvent (enterFrame)'では、前述のとおりまず'MovieClip.hitTest'メソッドで、マウスポインタがインスタンス上にあるかどうかを調べます。

[3] マウスポインタがインスタンス上にあったら、その前の状態を格納した変数bOutの値が'true'かどうかを判定します。この'if'条件が当てはまる(評価が'true')なら、インスタンス外からインスタンス上に状態が変化したので、ロールオーバーの処理を行います。

[4] 状態が変更されたので、新たに現状の'false'(ポインタがインスタンス上)を変数bOutに代入します。

[5] マウスポインタが現在インスタンス上にない場合には、変数bOutの値を調べてその前の状態が'false'かを確かめます。この'if'条件が当てはまれば(評価が'true'なら)、インスタンス上からインスタンス外に状態が変化したので、ロールアウトの処理を行います。

[6] 新たな状態として'true'(ポインタがインスタンス外)を、変数bOutに代入します。

4. スクリプトを整理する
2でご紹介したスクリプトは、もう少し整理して、ステートメント数を減らすことが可能です。最初の判定で、マウスポインタがMovieClipインスタンス上にあるかないかを調べるのではなく、現在のポインタの状態が以前と変わったかどうかを評価するのです。つまり、'MovieClip.hitTest'メソッドの返す値と変数bOutの値が同じかどうかを判定すればよいのです。そして、変化があったら、現在の状態に応じて処理を行います。

// MovieClip: ロールオーバー/ロールアウトを検出
// MovieClipアクション
onClipEvent (load) {
  bOut = true;
}
onClipEvent (enterFrame) {
  // [1] 以前の状態と変化があるかどうかを判定
  if (bOut == this.hitTest(_root._xmouse, _root._ymouse, true)) {
    if (bOut=!bOut) {  // [2] 状態の変数のブール(論理)値を反転して評価
      trace("rollOut");  // テスト用: ロールアウト
      } else {
    trace("rollOver");  // テスト用: ロールオーバー
    }
  }
}

[1] まず、'MovieClip.hitTest'メソッドの戻り値と変数bOutの値が、同じかどうかを評価しています。'MovieClip.hitTest'メソッドは、ポインタが重なっていれば'true'を返します。変数bOutは重なっていなければ、'true'に設定されます。ですから、値が同じということは、状態が変化したことを示します。

[2] ハンドラの第2ステートメントの'if'条件には、少し工夫を加えています。条件に指定した式は、論理式ではなく代入式であることにご注意ください(前掲『オブジェクト指向で考えるActionScript』p.94〜コラム「ifアクションの条件」参照)。'!'演算子は論理否定で、ブール(論理)値を反転します。その値を変数bOutに代入しているので、結局変数値の'true'/'false'が入替わることになります。

'if'条件に代入式を指定すると、代入された値を評価します。つまり、値反転後の変数bOutが'true'か'false'かを調べることになるのです。前の[1]の'if'条件でマウスポインタの重なり状態が変化したときにこの'if'アクションが処理されるので、値反転後の変数bOutは現在の状態を示すことになります。したがって、'if'条件の評価が'true'ならロールアウト、'false'ならロールオーバーの処理を行います。

このスクリプトをベースに作成したポップアップメニューのサンプルSWFを、ご参考までに掲げます。なお、ソースのFLAファイル(Zip圧縮/約7KB)もダウンロードできます(SWFを再生するにはFlash Player 6以降、FLAファイルを開くにはFlash MX以降が必要です)。

なお、2で作成したスクリプトでも、'if'アクションは条件に当てはまるもののみが処理されます。ですから、ステートメントの数が多くても、非効率な処理を行っているということではありません。

_____

作成者: 野中文雄
作成日: 2003年8月11日


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