サイトトップ

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

Adobe Flash CS3 Professional ActionScript 3.0

□04 MovieClipの座標の操作

04-01 マウス座標を調べる
本章のお題は、マウスポインタの後を追いかけるMovieClipインスタンスのスクリプトです。段取りとしては、(1)マウスポインタの座標を調べ、(2)その位置にMovieClipインスタンスの座標を設定すればよいでしょう。これを、(3)毎フレームの処理で行えば、目的のアニメーションができあがります。シンプルな内容ですが、前提知識や関連情報についても、この機会に詳しく解説していきます。

継承されるプロパティとメソッド
まず、MovieClipインスタンスの位置座標を指定するプロパティは、xyでした。しかし、ここで改めて、[ヘルプ]の[ActionScript 3.0コンポーネントリファレンスガイド]を確認してみましょう。

[MovieClipクラス]の[プロパティ]を見ても、デフォルトではxyというプロパティが一覧表にありません(図04-001)。rotationプロパティも掲載されていませんし、妙に数が少ないようです。実は、表の上部に「継承されるブロパティを表示」というボタンがあります。これをクリックすると一覧表が拡張され、たくさんのプロパティが表示されるようになります。

図04-001■[ヘルプ]パネルに表示された[MovieClipクラス]の[プロパティ]一覧表

「継承されるブロパティを表示」のボタンをクリックすると、たくさんのプロパティが一覧表示される。

拡張された一覧表には、もちろんxyプロパティが掲載されています(図04-002)。では、最初から一覧に含まれていたプロパティと、拡張表示されたプロパティは何が違うのでしょうか。それは一覧表の右の欄に示された「定義元」のクラスです。

図04-002■[MovieClipクラス]の[プロパティ]一覧表に拡張表示されたxとy

「定義元」にDisplayObjectと記載されている。

最初に表示されていたのは、「定義元」がMovieClipのものだけです(図04-001)。これはつまり、これらのプロパティがMovieClipクラスに定義されていることを意味します。ところが、xyプロパティの「定義元」は、DisplayObjectと記載されています。ですから、これらのプロパティはMovieClipクラスでなく、DisplayObjectという別クラスで定義されているということになります。

しかし、すでに前の章でスクリプトを作成して試したとおり、xyプロパティはMovieClipインスタンスで操作することができました。これを可能にする仕組みが、「継承」なのです。MovieClipクラスは、DisplayObjectクラスを「継承」しています。すると、DisplayObjectクラスのプロパティやメソッドが、MovieClipクラス自身に定義されたものと同じように、MovieClipインスタンスからアクセできるようになるのです。

自作PCを組立てようとするとき、すべての部品をひとつひとつ買い集めるのではなく、「べアボーン」と呼ばれるキットを利用することがあります。べアボーンキットには、どのようなPCにも最低限必要なマザーボードやケース、電源がワンセットになっています。そこに、自分の選んだCPUを挿し、必要な容量のメモリやディスクを積み、その他のボードやドライブを組込んで、オリジナルのPCをつくる訳です。

継承というのは、このべアボーンのように他のクラスをベースとして利用し、それに独自のプロパティやメソッドを追加定義することにより新しいクラスを作成する仕組みです。ベースとなるクラスのプロパティやメソッドは、当然新しいクラスで使用することができます。この基本となるクラスを「スーパークラス」、それをベースに追加・拡張したクラスは「サブクラス」と呼ばれます。

04-001
スーパークラスをベースに、プロパティやメソッドをつけ加えてサブクラスをつくるのが「継承」。


Word 04-001■継承
継承」とは、オブジェクト指向プログラミングで、予め定義されているクラスをもとにして、追加・拡張した新しいクラスを定義するとことです。ベースとなるクラスを「スーパークラス」(super class)または「基本クラス」、新たに定義したクラスを「サブクラス」(sub class)あるいは「派生クラス」と呼びます。

スーバークラスのプロパティやメソッドは、すべてサブクラスに受継がれます。したがって、サブクラスは追加・拡張するプロパティやメソッドの定義をつけ加えればよい、というのが継承の特長です。

MovieClipは、DisplayObjectをスーパークラスとする、サブクラスなのです。もっとも、MovieClipクラスは、DisplayObjectクラスを直接継承している訳ではありません。ActiolnScript 3.0の継承は、何段階もの階層をもっています。ヘルプの[ActionScript 3.0コンポーネントリファレンスガイド]で[MovieClipクラス]の解説の最初に、MovieClipクラスまでの継承のつながりが記載されています(図04-003)。

図04-003■[MovieClipクラス]の解説冒頭に示された「継承」

ObjectクラスからMovieClipクラスまで、6段階の継承が行われている。

DisplayObjectクラスを直接継承するのは、InteractiveObjectクラスです。つぎに、そのサブクラスとしてDisplayObjectContainerクラスがあり、それをさらにSpriteクラスが継承します。そして、SpriteクラスがMovieClipクラスの直接のスーパークラスになります。

Tips 04-001■DisplayObjectクラスのサブクラス
DisplayObjectクラスは、その名のとおり、ステージ上に表示するインスタンス(display object)に必要なプロパティやメソッドを定義しています。そうしたインスタンスには、当然座標(xy)や角度(rotation)のプロパティが必要になります。

ですから、DisplayObjectクラスを継承するサブクラスは、ステージ上に表示するインスタンスを作成するクラスになります。たとえば、ボタンシンボルのインスタンスを扱うSimpleButtonクラスや、TextFieldインスタンスをつくるTextFieldクラスなどが挙げられます。

DisplayObjectクラスにも、EventDispatcherというスーパークラスがあります。EventDispatcherはイベントを扱うクラスで、たとえばaddEventListener()メソッドは、このクラスに定義されています。そして、EventDispatcherクラスは、Objectクラスを継承します。このObjectは、ActionScript 3.0のすべてのクラスのスーパークラスになります。

MovieClipクラスは、これら6つのスーパークラスのすべてのプロパティやメソッドを利用することができます。なお今後は、プロパティやメソッドを引用して説明する際に、その定義されているクラスを示して、DisplayObject.xDisplayObject.y、あるいはEventDispatcher.addEventListener()のように表記することがあります。

マウスポインタの座標を調べる
つぎは、マウスポインタの座標を調べる方法です。これはやはりDisplayObjectクラスにプロパティが定められています。[ActionScrpt 3.0コンポーネントリファレンスガイド]を確認してみましょう。マウスポインタのx座標を調べるプロパティDisplayObject.mouseXのシンタックスと説明は、つぎのとおりです。

mouseX:Number [read-only]

マウス位置のx座標を示します(ピクセル単位)。

[read-only]といのは「読取り専用」、つまり調べることはできても設定はできないということです。Flashコンテンツでは、マウスポインタの位置を勝手に動かすことはできません。説明はきわめて簡素です。マウスポインタの座標を、ピクセル単位で返すということです。一見、疑問の余地はないように見えます。それでは、早速スクリプトを書いてみましょう。

当初考えた段取りの3点は、一応判明しました。(1)マウスポインタの座標は、DisplayObject.mouseXおよびDisplayObject.mouseYプロパティで調べられます。(2)MovieClipインスタンスの座標は、DisplayObject.xおよびDisplayObject.yプロパティで設定できることはすでにわかっていました。(3)毎フレームのアニメーションは、EventDispatcher.addEventListener()メソッドを使って、Event.ENTER_FRAMEイベントにリスナー関数を登録すれば処理できます。

いきなりxy座標両方の処理を書くのでなく、まずはx座標だけのスクリプトを作成してみましょう。つまり、マウスポインタの水平方向の動きに追随するアニメーションのスクリプトです。すると、スクリプトは以下のようなフレームアクションでよさそうに思えます。メインタイムラインにMovieClipインスタンスを置き、そのシンボル内の第1フレームアクションとして設定するものとします。

// MovieClip: マウスポインタに追随させるインスタンス
addEventListener(Event.ENTER_FRAME, xFollowMouse);
function xFollowMouse(eventObject:Event):void {
  x = mouseX;
}
図04-004■マウスポインタに追随させるMovieClipのフレームアクション

DisplayObject.mouseXプロパティの値を、DisplayObject.xプロパティに設定する。

マウスポインタのx座標値をMovieClipインスタンスのx座標に設定している訳ですから、これでマウスポインタの水平方向の動きに追随しそうに思えます。しかし、[ムービープレビュー]でアニメーションを確認すると、点滅したような動きになるはずです。マウスを動かすと点滅する位置が変わるので、ポインタの動きに反応はしているようだということが推測できます。では、なぜMovieClipインスタンスの水平位置が、マウスポインタのx座標に一致しないのでしょうか。


04-02 プロパティの天動説と地動説
DisplayObject.mouseXDisplayObject.xも、ともにx座標値を示すプロパティです。しかし、問題はそれらが、どこから見た座標値かということです。

まず、DisplayObject.mouseXは、DisplayObjectクラスのプロパティであることをもう一度思い起こしましょう。DisplayObjectクラスのプロパティだということは、インスタンスによって値が異なることを意味します。つまり、ステージのような特定の座標空間を基準にするのでなく、各インスタンスから座標を測るということです。具体的には、インスタンスの基準点を原点とした座標を返します。

他方、DisplayObject.xプロパティについては、自分を中心にして自分の位置を決めることはできません。ですから、自分の配置された親のタイムラインを基準に、座標を測ることになります。つまり、親タイムラインの基準点を原点とした座標を返します。

いってみれば、DisplayObject.mouseXは自分中心の天動説プロパティで、DisplayObject.xは自分の属する座標空間を基準にした地動説プロパティだということになります。基準が異なりますので、一方のプロパティ値を他方のプロパティに設定すれば、ちぐはぐな結果を招きます。

04-002
mouseXは、自分を宇宙の中心と考える天動説。xは、自分は宇宙の一員だとする地動説。

たとえば、メインタイムラインに配置されたインスタンスのDisplayObject.xプロパティ値が100であれば、ステージ左端から100ピクセルの位置に存在します。そして、マウスポインタがステージ左端から50ピクセルの位置にあった場合、DisplayObject.mouseXプロパティはインスタンスから見た座標ですから、その値は-50だということになります(図04-005)。

図04-005■DisplayObject.xプロパティとDisplayObject.mouseXプロパティの値

DisplayObject.xプロパティは配置されたタイムラインを、DisplayObject.mouseXプロパティはインスタンスを基準に値を定める。

前記フレームアクション(図04-004)で、Event.ENTER_FRAMEが発生し、DisplayObject.mouseXプロパティの値-50をインスタンスのDisplayObject.xプロパティに設定すると、ステージ左端から外に50ピクセルの位置に移動してしまいます。そして、マウスポインタが動かなかったとすると、インスタンスから見たDisplayObject.mouseXプロパティの値は、今度は100になります。したがって、つぎの描画更新時には、またステージ左端から100ピクセルの位置に戻ります。この繰返しが、点滅の動作になったのです。

Tips 04-002■動作の確認は小分けにする
マウスポインタのxy座標に追随するインスタンスのスクリプトを記述するのに、今回まずx座標の処理だけを先に試しました。作成しようとしている処理をいきなりまとめて記述すると、問題が生じやすくなり、また発生した現象の解析にも時間と手間がかかってしまいます。

処理が小分けにできるときは、そのひとつひとつを段階的に確認しながら進めることが、トラブルを未然に防ぎ、また生じた問題を早く解決するコツです。


04-03 親タイムラインから眺める − 地動説
マウスポインタの座標に正しくインスタンスを追随させるには、天動説か地動説かどちらかに基準を合わせる必要があります。まず、考え方がわかりやすい地動説で、スクリプトを修正してみましょう。DisplayObject.xは、地動説プロパティですので、このまま変えません。

前記スクリプト(図04-004)でDisplayObject.mouseXプロパティにターゲットを省略したので、スクリプトを記述したMovieClipインスタンスが参照されました。このターゲットを明記して、親タイムラインを指定すればよいでしょう。インスタンスは、メインタイムラインに配置されています。オーサリング時に配置したMovieClipインスタンスからメインタイムラインを参照するのは、DisplayObject.rootプロパティです。

DisplayObject.mouseXプロパティのターゲットをDisplayObject.rootプロパティで指定したのが、以下のフレームアクションです(スクリプト04-001)。[ムービープレビュー]で試すと、MovieClipインスタンスがマウスポインタの水平方向の動きに追随します(図03-006)。

スクリプト04-001■MovieClipインスタンスをマウスポインタの水平方向の動きに追随させるフレームアクション

// MovieClip: マウスポインタに追随させるインスタンス
addEventListener(Event.ENTER_FRAME, xFollowMouse);
function xFollowMouse(eventObject:Event):void {
  x = root.mouseX;
}


図04-006■マウスポインタの水平方向の動きに追随するMovieClipインスタンス

DisplayObject.mouseXプロパティのターゲットに、DisplayObject.rootプロパティを指定。

AS1&2 Note 04-001■rootは_rootとは違う
ActionScript 2.0/1.0では、_rootプロパティがつねにメインタイムラインを参照しました。たとえば、MovieClipインスタンスをターゲットにして外部SWFをロードすると、読込まれたSWF内から参照した_rootプロパティの値は、読込んだSWFのメインタイムラインでした。

しかし、ActionScript 3.0では、外部SWFをロードしたときも、その内部から参照したDisplayObject.rootプロパティの値は、つねに読込まれたSWF自身のメインタイムラインになり、単独で再生した場合と変わりません。ActionScript 3.0のDisplayObject.rootプロパティは、2.0/1.0の_rootプロパティと必ずしも同じでないことに注意しましょう。

DisplayObject.rootをターゲットとして参照するのは、汎用性の面では欠点があります。たとえば、マウスポインタに追随するMovieClipインスタンスを、もうひとつ別のMovieClipシンボルに入れ子にしてメインタイムラインに配置した場合です。

すると、DisplayObject.xプロパティの座標の基準は、入れ子の親のMovieClipインスタンスになります。ところが、DisplayObject.mouseXプロパティのターゲットにはDisplayObject.rootプロパティを指定している訳ですから、座標の基準はつねにメインタイムラインになります。基準の異なる座標を同一のものとして扱えば、またちぐはぐな動きになります。

ターゲットとしてつねに親タイムラインを参照できれば、この問題は解決します。親のインスタンスを参照するプロパティは、DisplayObject.parentです。前記フレームアクション(スクリプト04-001)のリスナー関数の処理内で、DisplayObject.mouseXプロパティのターゲットをDisplayObject.parentに替え、さらにy座標の処理について同様のステートメントを加えたのが、つぎのスクリプト04-002です。

スクリプト04-002■MovieClipインスタンスをマウスポインタの動きに追随させるフレームアクション − 地動説

// MovieClip: マウスポインタに追随させるインスタンス
addEventListener(Event.ENTER_FRAME, xFollowMouse);
function xFollowMouse(eventObject:Event):void {
  x = parent.mouseX;
  y = parent.mouseY;
}

[ムービープレビュー]を実行すると、マウスポインタの動きにMovieClipインスタンスが追随します(図04-007)。

図04-007■マウスポインタのxy座標の動きに追随するMovieClipインスタンス

DisplayObject.mouseXプロパティのターゲットにDisplayObject.parentプロパティを指定し、y座標の処理も追加。

Tips 04-003■ステージ外のマウスポインタ座標
ActionScriptでは、ステージ外にマウスポインタが移動したことは認識されません。DisplayObject.mouseX/DisplayObject.mouseXプロパティについては、ステージ外に出る直前の座標値が保持されたまま、その値は変わらなくなります。

したがって、前記スクリプト04-002では、ステージ外に出る直前の位置にMovieClipインスタンスが停止したまま、マウスポインタをステージ内に戻すまで、動かなくなります(図04-008)。この動作は[ムービープレビュー]の場合も、HTMLドキュメントにSWFを埋込んだ場合も同じです。

図04-008■MovieClipインスタンスはマウスポインタがステージ外に出る直前の位置で停止

DisplayObject.mouseX/DisplayObject.mouseYプロパティは、マウスポインタがステージ外に出る直前の値を保持したままになる。

04-04 差を埋める − 天動説
前記スクリプト04-002と同じ処理を、天動説の基準で行うこともできます。まずは、スクリプトを先にご紹介しましょう(スクリプト04-003)。ふたつの事項につき、2箇所ずつ、計4箇所修正しています。

スクリプト04-003■MovieClipインスタンスをマウスポインタの動きに追随させるフレームアクション − 天動説

// MovieClip: マウスポインタに追随させるインスタンス
addEventListener(Event.ENTER_FRAME, xFollowMouse);
function xFollowMouse(eventObject:Event):void {
  x += mouseX;
  y += mouseY;
}

まず第1は、関数xFollowMouse()本体の2行のステートメントに記述した、DisplayObject.mouseX/DisplayObject.mouseYプロパティのターゲットです。天動説を基準にするので、ターゲットを省略して、MovieClipインスタンス自身を参照するように変更しました。このままでは点滅したような動作になることは、04-01「マウス座標を調べる」で作成した、水平座標の処理を行うスクリプト(図04-004)により確かめました。

そこで第2に、通常の代入演算子=を、加算後代入演算子+=に修正しています。これで[ムービープレビュー]を確認すると、前記スクリプト04-002と同じように、マウスポインタの動きにMovieClipインスタンスが追随します。これは、なぜでしょう?

マラソンで、先頭走者に追いつきたいと思ったとき、地動説の考え方はこうなります。まず、先頭走者が今この瞬間走っている、スタート地点からの位置を確かめます。それが何キロメートルの位置だと知ったら、瞬時にその同じ場所に移動できれば追いつきます。

しかし、通常ランナーは追いつきたい相手のスタート地点からの位置ではなく、自分のどれくらい前を走っているのかということを考えるでしょう。それが何百メートルあるいは何キロメートル先だとわかったら、その遅れを取戻そうとします。つまり、相手の位置までに足りない距離を、加えて埋め合わせてやればよいのです。これが天動説の考え方になります。

プロパティ += 自分から見た相手との差
図04-009■自分から見た相手との差を埋め合わせれば追いつく

MovieClipインスタンスのDisplayObject.mouseXの値が100なら、その値を加えて右に100ピクセル動けば位置が一致する。

自分の方が相手より先にいる場合には、相手の位置が負の値になりますので、やはりその値を加えれば、相手の場所に追いつきます。追いついてしまうと、相手の位置との差がなくなりますので、0を足し込むことになり、そのままその場にとどまります。

04-003
相手に追いつくには、遅れを取戻す。つまり、自分と相手との差を埋め合わせればよい。


04-05 イーズアウトの公式
目的の位置まで一気に移動するのでなく、徐々に減速しながら到達するというアニメーションをよく見かけます。Flash使いの好きな、イーズアウトという動きです。前記スクリプト04-003に修正を加えて、マウスポインタの位置にイーズアウトしながら追随するようにしてみましょう。

Word 04-002■イージング
「イージング」とは、アニメーションの動きを徐々に加速したり、減速したりすることです。立ち上がりに加速するのを「イーズイン」、減速しながら止まるのを「イーズアウト」と呼びます。

今回も、まずスクリプトを先にご紹介します。数値の変数nDeceleration("deceleration"は「減速」の意)を、新たに追加しています。

スクリプト04-004■MovieClipインスタンスをマウスポインタに向けてイーズインさせるフレームアクション − 天動説

// MovieClip: マウスポインタに追随させるインスタンス
var nDeceleration:Number = 0.2;
addEventListener(Event.ENTER_FRAME, xFollowMouse);
function xFollowMouse(eventObject:Event):void {
  x += mouseX*nDeceleration;
  y += mouseY*nDeceleration;
}

変数nDecelerationは、減速率を決める小数値で、0から1の間の値を取ります。この変数値を、関数xFollowMouse()本体でインスタンスのxy座標を設定する代入式右辺の、DisplayObject.mouseXDisplayObject.mouseYプロパティの値にそれぞれ乗じています。

変数nDecelerationの値が1であれば前記スクリプト04-003と同じ動作で、MovieClipインスタンスは遅れずにぴったりとマウスポインタに追随します。0なら動きません。値が0に近いほどゆっくりと後を追い、1に近ければ素早く追いつきます。

余談ですが、昔筆者の隣の家に、男の子の兄弟がいました。兄弟というと、大抵は下の子が要領はよいようです。あるとき、弟が茶の間のお菓子を見て、台所のお母さんに「お菓子食べてもいい?」と尋ねたそうです。ふたり兄弟ですから、「半分ずつよ」とお母さんは答えました。しかし、しばらくするとまた、「お菓子食べてもいい?」と聞くのです。「半分だからね」と念を押すと、またすぐ「お菓子食べてもいい?」と繰返します。

あまりしつこいので、お母さんが茶の間に行くと、お菓子はほぼなくなっていました。どうやら「半分よ」とお母さんの答えが返ってくるたびに、残ったお菓子を半分ずつ食べてしまっていたようです。

これは、減速率0.5(=1/2)の場合の処理と同じです。毎回、つまり毎フレーム、半分(1/2)だけ目的地に近づきます。目的地に近づくにしたがって、移動する1/2の距離は小さくなります。すなわち、減速していくということです。そして、速度を落としながらも、目的地にかぎりなく近づいていきます。

このイーズアウトの処理を公式風に表現すると、つぎのとおりです。

プロパティ += 自分から見た相手との差*減速率

ただし、0 < 減速率 < 1とする。

Tips 04-004■かぎりなく近づくということ
イーズアウトの公式で処理を繰返した場合、数学的には相手との差は0にはなりません。しかし実際には、マウスポインタの座標はピクセル値で整数、MovieClipインスタンスの座標値は小数点以下2桁程度の精度ですので、小さい端数は丸められて最終的には差が0になります。


Tips 04-005■減速率
相手との差という計算結果が、0のような一定値にかぎりなく近づくことを、「収束」するといいます。減速率を負の値にすると、相手との差が0に収束せず、インスタンスはマウスポインタの位置から離れていってしまいます。

しかし、1以上2未満の減速率に対しては、相手との差が0に収束します。減速率を1より大きくすると、インスタンスがマウスポインタの位置を通り過ぎるアニメーションになります。しかし、2より小さい値にすれば、行き過ぎる距離が次第に小さくなり、インスタンスの座標値はマウスポインタの座標値に収束します。したがって、インスタンスをマウスポインタに、ゴムやバネで結びつけたような動きになります。


Maniac! 04-001■イーズアウトと等比数列
アニメーションの開始時に目的の座標までの距離を1とした場合、減速率が0.2であれば、毎フレーム更新時に目的座標に向けて近づく移動距離は、つぎのようになります。

第1回目のフレーム更新での移動は0.2で、残りの距離は0.8。第2回目のフレーム更新時は、移動が前フレームの残り0.8の2割で0.2×0.8となり、残る距離は0.8の8割で0.82です。同様に第3回目のフレーム更新時には、移動が0.2×0.82で、残る距離は0.83になります。

つまり、前の回の移動距離に0.8を掛合わせると、次回の移動距離になっています。このような関係にある数値の並びを「等比数列」といい、第n回目のフレーム更新時の移動距離は0.2×0.8n-1になります。さらに一般化して、減速率をdとすれば、第n回目の移動距離は、d×(1-d)n-1と表すことができます。

各回の移動を足し合わせた合計の距離は、等比数列の和として考えることができます。等比数列の和は、特定の条件の下において、足し合わせる回数nをかぎりなく増やしたとき、一定の値に収束することが知られています。減速率dが0より大きく、1より小さいというのは、その条件に当てはまります。そして、収束する値はこの場合、目的の座標までの距離である1になります。

等比数列やイーズアウトの処理との関係については、数学編Math 01「数列の基礎」で解説しています。

前記スクリプト04-004は、地動説の基準でも記述することができます。地動説の場合、マウスポインタの座標DisplayObject.mouseX/DisplayObject.mouseYプロパティは、親のインスタンスに対するDisplayObject.parentプロパティの参照をターゲットにすることになります。そのうえでマウスポインタの座標を自分から見た値とするには、自分のインスタンスの座標を差引けばよいでしょう。

先頭走者のスタート地点からの位置がわかったら、同じように自分のスタート地点からの位置を調べてその差を取れば、自分から見た相手までの距離になるからです。したがって、フレームアクションに定義した関数xFollowMouse()を、つぎのスクリプト04-005のように書替えても動作は変わりません。

スクリプト04-005■MovieClipインスタンスをマウスポインタに向けてイーズインさせるフレームアクション − 地動説

// MovieClip: マウスポインタに追随させるインスタンス
var nDeceleration:Number = 0.2;
addEventListener(Event.ENTER_FRAME, xFollowMouse);
function xFollowMouse(eventObject:Event):void {
  x += (parent.mouseX-x)*nDeceleration;
  y += (parent.mouseY-y)*nDeceleration;
}

よって、イーズアウトの公式は、つぎのように書替えることもできます。

プロパティ += (相手の値 - 自分の値)*減速率

ただし、0 < 減速率 < 1とする。

もっとも、今回の処理では天動説プロパティDisplayObject.mouseX/DisplayObject.mouseYを使って、自分から見た相手の値との差が直接取得できるので、あえて地動説の処理にする必要はありません。わざわざ差を計算しなければならないのは、目的の値が予め決まっているような場合です。たとえば、ステージ中央にイーズアウトのアニメーションをさせるといった例が考えられます。

ステージ中央の座標は、直接取得できません。Stageクラスで幅と高さのStage.stageWidthStage.stageHeightのプロパティ値を調べて、中心の座標を計算する必要があります。ただし、Stageクラスに直接アクセスして、インスタンスを作成することはできません。

new Stage();

new演算子でコンストラクタを呼出してStageインスタンスを作成しようとすれば、つぎのようなエラーが[出力]されます。

ArgumentError: Error #2012: Stage クラスをインスタンス化することはできません。

Maniac! 04-001-2■StageクラスのインスタンスとSingleton
Stageクラスのインスタンスは、Flash Playerにひとつだけ自動的に生成されます。そして、それに追加して、複数のインスタンスをつくることはできません。

このように、インスタンスがひとつしか生成されないことを保障するクラスの設計手法(デザインパターン)は、Singleton(Singletonパターン)と呼ばれます。

[*筆者用参考] Wikipedia「Singletonパターン」、「Singletonパターン」、akihiro kamijo「コンストラクタ(とSingleton)」。

DisplayObjectおよびそのサブクラス(たとえばMovieClip)のインスタンスは、DisplayObject.stageプロパティをもちます。このDisplayObject.stageプロパティがStageインスタンスを参照しますので、そこからStageクラスのプロパティを取得します。

そうすると、ステージ中央の座標を計算したうえで、MovieClipインスタンスをその位置に向けてイーズアウトさせるフレームアクションは、以下のようになります(スクリプト04-006)。MovieClipインスタンスは、メインタイムラインの端(中央以外)に配置し、スクリプトはこのMovieClipシンボルのフレームアクションとして設定します。

スクリプト04-006■MovieClipインスタンスをステージ中央に向けてイーズインさせるフレームアクション

// MovieClip: ステージ中央に移動させるインスタンス
var nStageCenterX:Number = stage.stageWidth/2;
var nStageCenterY:Number = stage.stageHeight/2;
var nDeceleration:Number = 0.2;
addEventListener(Event.ENTER_FRAME, xFollowMouse);
function xFollowMouse(eventObject:Event):void {
  x += (nStageCenterX-x)*nDeceleration;
  y += (nStageCenterY-y)*nDeceleration;
}

まず、スクリプトの初期設定で、Stage.stageWidthStage.stageHeightプロパティを使って、それぞれの半分の長さつまりステージ中央の座標を計算しています。MovieClipインスタンスはメインタイムラインに配置されていますので、インスタンスのDisplayObject.x/DisplayObject.yプロパティにこれらの座標値を設定すれば、ステージ中央に配置できます。

つぎに、Event.ENTER_FRAMEイベントに設定したリスナー関数xFollowMouse()内の処理で、上記のイーズアウトの公式にしたがい、目指すステージ中央の座標から自分の座標値を引いた値に減速率を乗じて、DisplayObject.x/DisplayObject.yプロパティに設定しています。

[ムービープレビュー]で確かめると、MovieClipインスタンスはイーズアウトしながら、ステージ中央に移動します。

Tips 04-006■イベントリスナーは削除しないのか
MovieClipインスタンスがステージ中央に達したら、それ以上リスナー関数を呼出しても、実際上の動きはなくなります。それでもなお、リスナー関数の処理を続けるのは無駄です。つまり、イベントリスナーは削除した方がよいということになります。

もっともそのためには第1に、MovieClipインスタンスの位置がステージ中央に達したか(厳密には十分に近づいたか)どうかという、条件判定の処理が必要です。また第2に、リスナー関数をイベントリスナーから削除するEventDispatcher.removeEventListener()メソッドについて学ばなければなりません。

これらの事項は後の章で学習しますので、この段階では問題点の指摘にとどめます。

イーズアウトの処理について、ふたつの計算方法をご紹介しました(表04-001)。ただいずれも、相手と自分の差を取り、減速率を掛合わせて、プロパティの現在値に加算するという意味では同じ内容といえます。ひとことでいうなら、「差を割引いて足し込む」ということになるでしょう。

表04-001■イーズアウトの公式
目指すプロパティ値に減速しながら到達する
プロパティ += 自分から見た相手との差*減速率

または、

プロパティ += (相手の値 - 自分の値)*減速率

ただし、0 < 減速率 < 1とする。


04-003
イーズアウトの処理は、差を割引いて足し込めばいい。


04-06 プロパティを設定する関数
前節のスクリプト04-004で作成したマウスポインタに向かってイーズアウトするMovieClipをタイムラインに複数配置して、減速率の変数nDecelerationの値をインスタンスによって少しずつ変えると、マウスポインタの後を連凧のように連なって追いかけるアニメーションができそうです(図04-010)。それでは、変数値はどのように設定したらよいでしょう。

図04-010■マウスポインタに向かってイーズアウトする複数のインスタンスに異なる減速率の値を設定する

マウスポインタを追いかける速さに差が生じて、連凧のようなアニメーションになる

04-005
連凧は、少しずつ遅れてついてくる

インスタンスの変数値をどのように設定するか
まず、フレームアクションでvar宣言した変数nDecelerationに、代入する数値そのものを変えることはできません。MovieClipシンボルのフレームアクションは、シンボル内に描画したイメージと同じく、シンボルのインスタンスに共有されます。したがって、変数に設定した値は、すべてのインスタンスに共通になってしまいます。

そうすると、MovieClipインスタンスを配置した親のタイムラインにフレームアクションを記述して、各インスタンスの変数値を個別に変えればよいでしょう。ただし、今回は変数に値を直接代入する方法は取りません。理由はふたつあります。

第1に、フレームアクションの実行される順序が問題です。フレームアクションは、親から子の順で処理されます。すると、親タイムラインのフレームアクションで変数値を設定しても、子のフレームアクションでvar宣言時に初期値を与えていますので、その値で上書きされてしまいます。

たとえば、以下のスクリプトは、メインタイムラインの第1フレームアクションから、MovieClipインスタンスmy_mcの(Number型)変数testに数値1を設定します。しかし、そのMoiveClipシンボル内の第1フレームアクションで、Number型変数testをvar宣言するとともに、初期値0を代入しています。すると、フレームアクションは、MovieClipインスタンスのスクリプトがメインタイムラインよりも後に処理されますので、変数testの値は1から0に変更されてしまうことになります。

// メインタイムライン
// 第1フレームアクション
// MovieClipインスタンスmy_mcを配置
my_mc.test = 1;
trace(my_mc.test);
// MovieClip: インスタンスmy_mc
// 第1フレームアクション
var test:Number = 0;
trace(test);
// [出力]パネルの表示
1
0

[出力]結果を見ると、メインタイムラインで設定した変数値1がまずtrace()関数で表示され、つぎにMoiveClipインスタンスmy_mcで代入した初期値0に書替えられたことがわかります。

変数に値を直接代入しない理由の第2は、修正や拡張に対する柔軟性が損なわれるためです。

たとえば、各MovieClipインスタンスの減速率の値だけでなく、アルファ値も変えることになるかもしれません。その場合、減速率やアルファの値は、試行錯誤しながら調整する可能性が高いですし、後から変更することも十分あり得ます。それらの調整や変更をMovieClipインスタンスの外部から行うのは、煩雑なだけでなく、MovieClipシンボル内にフレームアクションを記述してパーツ化した意味が失われます。

さらに、MovieClipシンボルに設定したスクリプトを拡張する際には、変数に設定しうる値をチェックしたり、設定のタイミングを管理するなどして、誤動作(バグ)が生じないような工夫を加えることが少なくありません。インスタンスの外部から直接変数の値を変更すると、このような処理が無視される結果となります。

以上の理由から、パーツ化を推し進め、スクリプトの信頼性と整合性を高めるためには、変数をインスタンスの外部から直接変更するのは望ましくありません。そこで、MovieClipシンボル内のフレームアクションに、変数設定用の関数を定義した方がよいということになるのです。

変数値を設定する関数の定義
それでは、MovieClipシンボルの第1フレームアクションに関数を定義して、減速率nDecelerationの変数値を設定します。後で関数の処理を拡張して、減速率以外の変数も加え、その初期設定も行う可能性があります。そこで、関数名はxInitialize()とします。

関数の定義に慣れるコツは、まず呼出し方から考えることでした。タイムラインにマウスポインタを追いかけるMovieClipインスタンスmy_mcを置いたとすると、タイムラインのフレームアクションからインスタンスmy_mcに対して、減速率の値たとえば0.2をつぎのように設定することにします。

my_mc.xInitialize(0.2);

今のところは、関数xInitialize()の処理は、変数nDecelerationに引数の値を代入する1ステートメントだけの内容です。練習問題としても、ごく簡単でしょう。MovieClipシンボルの第1フレームアクション(スクリプト04-004)に追加する関数xInitialize()は、つぎのように定義されます。

function xInitialize(nValue:Number):void {
   nDeceleration = nValue;
}

しかし、ほかにもうひとつ、フレームアクション中に修正しなければならない点があります。それは、var宣言した変数nDecelerationの初期値設定です。もとのスクリプト04-004では、var宣言と同時に、変数nDecelerationには0.2が代入されています。

var nDeceleration:Number = 0.2;

変数値を関数xInitialize()の呼出しにより設定したとしても、フレームアクションの処理は親から子の順番であることは変わりありません。すると、メインタイムラインからの関数xInitialize()を呼出して変数値を設定しても、つぎにMovieClipインスタンスのフレームアクションが実行されれば初期値0.2で上書きされてしまいます。

それを避けるには、フレームアクションでは変数nDecelerationのvar宣言と型指定のみにとどめる必要があります。その修正も加えたのが、つぎのスクリプト04-007です。

スクリプト04-007■減速率設定の関数を追加したMovieClipのフレームアクション
// MovieClip: マウスポインタに追随させるインスタンス
var nDeceleration:Number;
addEventListener(Event.ENTER_FRAME, xFollowMouse);
function xFollowMouse(eventObject:Event):void {
  x += mouseX*nDeceleration;
  y += mouseY*nDeceleration;
}
function xInitialize(nValue:Number):void {
  nDeceleration = nValue;
}

たとえば、メインタイムラインにマウスポインタに追随するMovieClipインスタンスを3つ配置し、それぞれにmy0_mc、my1_mc、my2_mcというインスタンス名をつけたとします。すると、メインタイムラインのフレームアクションに以下のステートメントを記述すれば、3つのMovieClipインスタンスが異なった減速率により、少しずつ追いかけるスピードを変えてマウスポインタに連なります。[ムービープレビュー]で、確認してみましょう。

my0_mc.xInitialize(0.6);
my1_mc.xInitialize(0.4);
my2_mc.xInitialize(0.2);

Tips 04-007■関数の初期化時期
関数xInitialize()は、MovieClipのフレームアクションで定義されています。その関数を、MovieClipのフレームアクションより先に処理されるメインタイムラインから、なぜ呼出すことができるのでしょうか。

それは関数の定義が、フレームアクションの処理に先立って初期化され、呼出しの準備が整っているからです。詳しくは、後述Column 04「変数宣言と関数定義の初期化時期」をご参照ください。

アニメーションを開始する関数の定義
これで意図した動作が得られたものの、前のスクリプト04-004と比べて、ひとつ大きく仕様が変わることになりました。それは、上記メインタイムラインのフレームアクションを削除してみればわかります。[ムービープレビュー]を実行すると、すべてのMovieClipインスタンスがステージからいなくなります。

これは、Number型の変数nDecelerationに初期値が設定されていないため、デフォルト値としてNaN(Word 02-003「NaN」)が与えられてしまうからです。NaNにどのような数値演算を施しても、結果はNaNになります。そして、NaNを数値のプロパティに設定すると、予想外の値、DisplayObject.xDisplayObject.yではステージ外の座標値として扱われてしまうのです(02-03「関数(function)を定義する」「秒針が最初に一瞬ずれる訳 − NaN(非数) −」参照)。

Tips 04-008■DisplayObject.xとDisplayObject.yプロパティにNaNを設定した結果
DisplayObject.xDisplayObject.yプロパティにNaNを設定したとき、予想外のどのような値になるかは、簡単に実験してみればわかります。タイムラインにMovieClipインスタンスmy_mcを配置して、以下のフレームアクションを記述したら、[ムービープレビュー]で確かめてみましょう。

var n:Number;
trace(n); // 出力: NaN
my_mc.x = n;
trace(my_mc.x); // 出力: -107374182.4

つまり、新たな仕様というのは、MovieClipインスタンスに対して必ず関数xInitialize()を呼出し、減速率の初期値を設定しなければならないということです。しかし、逆に考えると、MovieClipをタイムラインに置いただけで直ちにアニメーションが始まるのでなく、好きなときにスタートできるという意味でもあります。

ただ、いきなりステージ上から消えてしまうのは問題です。それだけでなく、Event.ENTER_FRAMEイベントにリスナー関数が登録されていますので、アニメーションを始める必要がなくても、関数が繰返し呼出されてしまいます。

そこで、アニメーションを開始する関数xStart()を定義し、そこでxInitialize()を呼出して変数の初期値を設定するとともに、イベントリスナーの登録を行うこととします。そうすると、MovieClipインスタンスには、初期状態ではEvent.ENTER_FRAMEイベントへのリスナー関数は登録されておらず、関数xStart()の呼出しを待ってアニメーションが始まることになります。

関数xStart()の処理は、前述のとおり、ふたつのステートメントから構成されます。第1は、EventDispatcher.addEventListener()メソッドを呼出して、Event.ENTER_FRAMEイベントにリスナー関数xFollowMouse()を登録します。第2は、xInitialize()を呼出して減速率の変数nDecelerationに初期値を与えます。この関数xStart()を定義して加えたのが、つぎのスクリプト04-008です。

スクリプト04-008■アニメーション開始の関数を追加したMovieClipのフレームアクション

// MovieClip: マウスポインタに追随させるインスタンス
var nDeceleration:Number;
function xStart(nValue:Number):void {
  addEventListener(Event.ENTER_FRAME, xFollowMouse);
  xInitialize(nValue);
}

function xFollowMouse(eventObject:Event):void {
  x += mouseX*nDeceleration;
  y += mouseY*nDeceleration;
  // trace(this, name, nDeceleration); // リスナー関数の呼出し確認用
}
function xInitialize(nValue:Number):void {
  nDeceleration = nValue;
}

このマウスポインタに追随するMovieClipインスタンスを配置したタイムラインのフレームアクションからは、前のスクリプト04-007のときの関数xInitialize()に替えて、xStart()を呼出すことになります。ですから、減速率の初期値は、関数xStart()の引数として渡します。xStart()の関数本体{}内では、その値をそのまま引数にして関数xInitialize()を呼出しています。

メインタイムラインにMovieClipインスタンスmy0_mc、my1_mc、my2_mcを配置したすると、メインタイムラインのフレームアクションにたとえば以下のようなステートメントを記述することになります。

my0_mc.xStart(0.6);
my1_mc.xStart(0.4);
my2_mc.xStart(0.2);

MovieClipインスタンスに対して関数xStart()を呼出さなければ、そのインスタンスのEvent.ENTER_FRAMEイベントにはリスナー関数が登録されません。前述スクリプト04-008の関数xFollowMouse()内には、それを確認するためのtrace()関数をステートメントとして加えています。

ただし、trace()ステートメントはコメントアウトしてありますので、確認するにはコメント行区切り記号//を削除します。[ムービープレビュー]を行うと、関数xStart()を呼出したMovieClipインスタンスの情報が、Event.ENTER_FRAMEイベントの発生するたびに、trace()関数により[出力]パネルに表示されます(図04-011)。関数xFollowMouse()内のステートメントで、trace()関数に指定した引数は3つあります。

図04-011■trace()ステートメントを有効にして[ムービープレビュー]で確認する

[出力]パネルに、シンボルを示す表記とインスタンス名と変数nDecelerationの値が、各インスタンスごとに表示される。

第1は、関数xFollowMouse()が呼出されているターゲットのインスタンスを参照するthisです。タイムラインに配置した3つのMovieClipインスタンスが、それぞれのxStart()関数を呼出されてアニメーション開始しますので、[出力]パネルに情報を表示されるインスタンスも3つあります。しかし、[出力]パネルにおける表記は、どれも同じ[[object Pen_1]となっています。

"object"の後に示される識別子は、シンボル名をもとに自動的に生成されるようです。したがって、インスタンスが異なっても、シンボルが共通なら同じ表記になってしまいます。

Tips 04-009■[出力]されるインスタンスの表記
インスタンスの参照をtrace()関数で[出力]パネルに表示すると、原則としてそのクラスが[object クラス名]のかたちで示されます。

たとえば、ボタンシンボルのインスタンスは、SimpleButtonクラスに属します。したがって、そのインスタンスをtrace()関数の引数として渡せば、つぎのように[出力]されます。

[object SimpleButton]

また、MovieClipインスタンスの場合も、そのシンボル内にフレームアクションが記述されていなければ、つぎのようにクラス名のMovieClipが[出力]されます。

[object MovieClip]

MovieClipシンボルにフレームアクションが記述されている場合には、クラス名ではなくシンボル名をもとに、[object シンボル名_数字]といった表記になるようです。


AS1&2 Note 04-002■インスタンスのパスは[出力]されない
ActionScript 2.0/1.0では、MovieClipやButton、TextFieldなど、ステージ上に描画されるインスタンスには、trace()関数に引数として渡すとそのパスを表示するものがありました。たとえば、メインタイムラインに配置されたMovieClipインスタンスmy_mcであれば、つぎのようなパスが[出力]パネルに表示されます。

_level0.my_mc

他方で、多くのクラスのインスタンスは、単に[object Object]とだけ[出力]され、具体的な情報に乏しかったといえます。

しかし、ActionScript 3.0では、原則としてクラス名が[出力]されるように統一されました。たとえば、以下のフレームアクションを実行した場合、ActionScript 2.0/1.0では[object Object]とのみ表示されるのに対して、3.0では[object Sound]とクラスが明示されます。

var _sound:Sound = new Sound();
trace(_sound);

Maniac! 04-002■MovieClipインスタンスの文字列表現
trace()関数にインスタンスを引数として渡したときに[出力]されるのは、そのインスタンスの文字列表現です(Maniac! 03-002「trace()関数はインスタンスのtoString()メソッドを呼出す」参照)。ActionScript 3.0では、前記Tips 04-009で述べたとおり、クラス名を示すのが原則です。

MovieClipシンボルには、後に解説するとおり、ActionScript 3.0で定義したクラスを設定することができます。さらには、クラスを定義していなくても、クラス名を設定するだけで、そのシンボルを最小限の範囲でクラスのアセット(素材)として扱うことができます。

MovieClipシンボルにクラスを設定しようとすると、デフォルトではシンボル名がクラス名として暫定表示されます(図04-012)。そのまま確定すれば、インスタンスのtrace()関数による[出力]つまり文字列表現として、シンボル名がクラス名として示されることになります。

図04-012■[シンボルプロパティ]ダイアログボックスでMovieClipシンボルにクラスを設定する

MovieClipシンボルにクラスを設定しようとすると、[クラス]にはシンボルの[名前]が暫定的に表示される。

MovieClipシンボルにフレームアクションが記述されている場合に、インスタンスの文字列表現([出力]の表記)として、クラス名でなくシンボル名にもとづいた名前が自動的に生成される(Tips 04-009)というのは、この[シンボルプロパティ]の仕様と軌を一にしていると思われます。

ただし、このときシンボル名に日本語(2バイト文字)が含まれている場合には、[object Timeline_1]といった、まったく異なる表記になります。これは、日本語(2バイト文字)が識別子として不適切なため、変換されることによるのでしょう。

そこで、trace()関数の第2の引数として、DisplayObject.nameプロパティを指定しました。このプロパティは、MovieClipインスタンス名を文字列で返します。各インスタンスには異なった識別子を設定してあますので、この値はMovieClipインスタンスごとに違います。これで、3つのMovieClipインスタンスの[出力]を、区別することができます。

trace()関数に指定した第3の引数は、変数nDecelerationです。この変数には、MovieClipインスタンスごとに、異なった減速率が値として設定されているはずです。値が意図どおりに正しく設定されているかを、[出力]された結果により確認することができます。前掲図04-011の[出力]パネルの表示は、意図どおりの結果を示しています。

つぎに、メインタイムラインに記述した、各MovieClipインスタンスのxStart()関数を呼出すフレームアクションを削除(もしくはコメントアウト)すると、[出力]はまったくされなくなります。よって、各MovieClipインスタンスのEvent.ENTER_FRAMEイベントにリスナー関数は登録されておらず、アニメーションを処理する関数xFollowMouse()も呼出されていないことが確認できます。


04-07 パラメータを加える
前節で作成したスクリプト04-008のMovieClipインスタンスのアニメーションに、もう少し変化を与えてみましょう。MovieClipインスタンスごとに、アルファの値を少しずつ変えることにします。この処理は初期設定として、関数xInitialize()に加えます。

パラメータとして固定値を設定
複数のMovieClipインスタンスのうち、マウスポインタに遅れて追随するものほどアルファ値を下げる(透明度を上げる)ことにします。DisplayObject.alphaプロパティは、完全な透明が0、完全な不透明を1とする小数値を取ります。デフォルト値は1です。

[プロパティ]インスペクタでインスタンスの[カラー]を設定する場合(図04-013)と異なり、アルファ値はパーセンテージ(%)でなく、小数値で指定することに注意しましょう。

図04-013■[プロパティ]インスペクタの[カラー]設定

[アルファ]は0から100までのパーセンテージ(%)で指定する。

AS1&2 Note 04-003■プロパティ値はパーセンテージでなく小数値
ActionScript 2.0/1.0では、MovieClip._alphaプロパティの値は、[プロパティ]インスタペクタの設定と同じく、パーセンテージ(%)で指定しました。ActionScript 3.0では、比率を表すプロパティ値の指定には、基本的にパーセンテージ(%)でなく、小数値が用いられます。

ほかにもたとえば、ActionScript 2.0/1.0では、幅や高さのスケール(拡大縮小率)であるMovieClip._xscaleMovieClip._yscaleプロパティがパーセンテージ(%)で指定されました。これに対応するActionScript 3.0のDisplayObject.scaleXDisplayObject.scaleYプロパティは、実寸(100%)を1とする小数値で設定します。


Maniac! 04-003■DisplayObject.alphaプロパティの内部処理
第1に、DisplayObject.alphaプロパティの値は、内部的には256階調で処理されているようです。

たとえば、DisplayObject.alphaプロパティに0.1を代入すると、実際には0.09765625という半端な値が設定されます。しかし、1/256(=0.00390625)を単位と考えれば、256階調の丁度25(25/256)に当たります(表04-002)。1/256を単位とした端数(1/256未満の数値)は、実際に設定されるプロパティ値からは切捨てられます。

表04-002■0.1刻みで値を設定した場合の実際のDisplayObject.alphaプロパティ値と256階調換算値
設定値
DisplayObject.alphaプロパティ値
256階調換算値
0 0 0
0.1 0.09765625 25
0.2 0.19921875 51
0.3 0.296875 76
0.4 0.3984375 102
0.5 0.5 128
0.6 0.59765625 153
0.7 0.69921875 179
0.8 0.796875 204
0.9 0.8984375 230
1 1 256

この点で、ひとつ注意すべきことがあります。1/256未満の値、たとえば0.003をDisplayObject.alphaプロパティに加算しても、端数として切捨てられるため、プロパティ値は変化しません。つまり、1/256未満の値は、加算し続ける処理を行ったとしても、アルファがまったく変わらないという結果になります。

第2に、DisplayObject.alphaプロパティに、0から1までの範囲外の値を設定することは一応は可能です。もちろん、表示上1以上の値は1と同じ完全な不透明で、0以下の値は完全な透明になって0と変わりません。

しかし、その値は±128の範囲で管理されているようです。DisplayObject.alphaプロパティに128を代入すると、-128が設定され、完全な透明になってしまいます。また、-129は127に変換され、完全な不透明に設定されます。したがって、やはり設定値は、0から1までの数値に限定するのが安心でしょう。

スクリプト04-008が設定されたMovieClipインスタンスのアニメーションは、低い減速率が設定されたインスタンスほど、マウスポインタに遅れて追随することになります。したがって、減速率の変数nDecelerationの値が小さいインスタンスほど低いDisplayObject.alphaプロパティ値を設定すれば、意図どおりに遅いMovieClipインスタンスほどアルファ値が下がることになります。

先ほどマウスポインタに追随するMovieClipインスタンスを3つ置いて試したときは、減速率としてそれぞれ0.6と0.4、0.2を設定しました。スクリプト04-008に追加する処理では、ポインタに一番速く近づく減速率0.6のインスタンスを、アルファ値が1になるようにしたいと思います。そこで、減速率に0.4を加えて、MovieClipインスタンスのDisplayObject.alphaプロパティを設定することにします(スクリプト04-009)。

スクリプト04-009■アルファ値の変更を追加したMovieClipのフレームアクション

// MovieClip: マウスポインタに追随させるインスタンス
var nDeceleration:Number;
function xStart(nValue:Number):void {
  addEventListener(Event.ENTER_FRAME, xFollowMouse);
  xInitialize(nValue);
}

function xFollowMouse(eventObject:Event):void {
  x += mouseX*nDeceleration;
  y += mouseY*nDeceleration;
}
function xInitialize(nValue:Number):void {
  nDeceleration = nValue;
  alpha = nValue+0.4; // アルファ値の変更を追加
}

メインタイムラインのフレームアクションに前回と同じく以下のステートメントを記述して、[ムービープレビュー]を確認すると、マウスポインタに遅れて追随するMovieClipインスタンスほど低いアルファが設定されます(図04-014)。

my0_mc.xStart(0.6);
my1_mc.xStart(0.4);
my2_mc.xStart(0.2);
図04-014■アルファ値の設定を加えたマウスポインタに追随するMovieClipインスタンス

マウスポインタに遅れて追随するMovieClipインスタンスほど、アルファが低い。

このアルファの処理(スクリプト04-009)に特に問題はないものの、少し不満が残ります。ひとつは、MovieClipインスタンスに設定する減速率を、限定していることです。マウスポインタに一番速く追いつくインスタンスの減速率を0.6として、アルファ値を調整する値の0.4を決めました。つまり、この減速率が0.8になったり、0.5に変わったりすれば、0.4という調整値をいちいち書替えなければなりません。

もうひとつは、MovieClipインスタンスごとに、アルファ値が固定していることです。これは好みの問題ではあります。しかし、せっかくのインタラクティブなアニメーションですから、マウスポインタの動きに応じて、インスタンスごとのアルファも変化させてみたいと思います。

変化するパラメータ値を設定
アニメーションに対してアルファ値をどのように変化させるかは、プロジェクトであれば仕様、個人のコンテンツなら趣味によります。比較的単純なのは、変化するパラメータを選び、その値に比例や反比例させることです。その他、物理法則の方程式を利用したり、三角関数のsin・cosカーブを使うなど、複雑な式も考えられます。

もっとも、インスタンスの(座標や角度などの)動きと比べると、アルファは細かな変化までわかりません。そこで、パラメータとしてはMovieClipインスタンスとマウスポインタとの距離を測り、アルファはその値に反比例させてみます。

まず、2点間の距離は「三平方の定理」(後述数学編Math 02「三平方(ピタゴラス)の定理」参照)によって求めることができます。2点をそれぞれ(x1, y1)と(x2, y2)とすれば、その間の距離lは、つぎの式で表されます。

【2点(x1, y1)と(x2, y2)との距離l】
l = √(x1-x2)2+(y1-y2)2

04-003
2点間の距離は「三平方の定理」で、√x座標の差2+y座標の差2。面倒な計算はActionScriptにやらせるので、ラクチン。

もっとも、今回計算するのは、MovieClipインスタンス(の基準点)とマウスポインタの座標との距離です。MovieClipインスタンスをターゲットとすれば、基準点は座標(0, 0)です。したがって、インスタンス(の基準点)の座標(0, 0)からマウスポインタの座標までの距離nDistanceは、DisplayObject.mouseXDisplayObject.mouseYプロパティの値をそのまま用いて、つぎの式で求められます。

var nDistance:Number = Math.sqrt(Math.pow(mouseX, 2)+Math.pow(mouseY, 2));

Mathクラスの新しいメソッドが、ふたつ使われています。第1のMath.sqrt()メソッドは、引数に渡した数値の平方根(square root)を返します。なお、引数は0以上の数値でなければならず、負の数を渡すとNaNが返ります。

Maniac! 04-004■平方根と実数
平方根」とは、2乗して与えられた数になる新たな数をいいます。実数の範囲で扱う場合は、平方根は0以上の実数に対して定義されます。実数を2乗すれば、必ず0以上の実数になるからです。

なお、数の範囲を複素数まで拡げると、負の数の平方根を定義することができます。ただ、Mathクラスのプロパティやメソッドは、実数を扱います。

第2のメソッドは、累乗あるいはべき乗(power)を計算するMath.pow()です。引数はふたつあり、aのn乗、anを得るには、つぎのように指定します。

var nAnswer:Number = Math.pow(a, n);

Maniac! 04-005■累乗(べき乗)の底と指数
aをn乗したanについて、累乗される数aを「底(てい)」(base)、累乗の数nを「指数」(exponent)と呼びます。

マウスポインタまでの距離を計算して、距離に反比例した値をアルファに与える処理は、毎フレームのアニメーションとして行います。そこで、前掲スクリプト04-009の関数xFollowMouse()に、つぎのような修正を加えました(スクリプト04-010)。

スクリプト04-010■アルファ値をマウスポインタからの距離に反比例させたフレームアクション

// MovieClip: マウスポインタに追随させるインスタンス
var nDeceleration:Number;
var nRange:Number = 40;
function xStart(nValue:Number):void {
  addEventListener(Event.ENTER_FRAME, xFollowMouse);
  xInitialize(nValue);
}
function xFollowMouse(eventObject:Event):void {
  var nX:Number = mouseX;
  var nY:Number = mouseY;
  x += nX*nDeceleration;
  y += nY*nDeceleration;
  var nDistance:Number = Math.sqrt(Math.pow(nX, 2)+Math.pow(nY, 2));   // マウスポインタまでの距離を計算
  alpha = nRange/(nDistance+1);   // アルファをマウスポインタまでの距離に反比例
}
function xInitialize(nValue:Number):void {
  nDeceleration = nValue;
}

第1に、インスタンス(の基準点)からマウスポインタまでの距離を求める式については、すでに説明しました。ただし、マウスポインタの座標値であるDisplayObject.mouseXDisplayObject.mouseYプロパティの値は、予めローカル変数(nXとnY)に格納しています。これは、ローカル変数の値を取得する方が、インスタンスのプロパティにアクセスするより、スピードが速いからです。

もちろん、インスタンスのプロパティへのアクセスが1度きりであれば、ローカル変数に代入する意味はありません。しかし、その値を同じ関数内の複数のステートメントで使う必要がある場合は、ローカル変数に代入すると、アクセススピードを稼ぐことができます。また、ターゲットのインスタンスをドット(.)で指定しているときには、ローカル変数を使うことで記述を短くすることもできます。

上記スクリプト04-010では、マウスポインタの座標値は、インスタンスの座標の移動と今回追加したマウスポインタまでの距離の計算の2箇所で使うことになったため、ローカル変数に代入することにしました。

04-007
関数内で2回以上使うプロパティの値は、ローカル変数に入れた方がお得。

第2に、マウスポインタまでの距離に反比例してアルファを設定するステートメントに、説明していない項がふたつ加えられています。アルファを単にマウスポインタまでの距離に反比例させるなら、一見つぎのステートメントでよさそうに思われます。

alpha = 1/nDistance;

しかし、まず代入式右辺の分子を1にすると、変数nDistanceの値つまりマウスポインタまでの距離が1ピクセルまで近づかないと、インスタンスが完全な不透明となる1(100%)になりません。10ピクセル離れてしまえば、アルファ値は0.1(10%)になってしまいます。

そこで分子に設定した変数nRangeは、var宣言で初期値として40が与えられています。したがって、マウスポインタまで40ピクセルの距離まで近づけば、インスタンスは完全に不透明なアルファ値1になります。

つぎに、代入式右辺の分母である変数nDistanceに加えた数値1が問題となります。これは、ともかくもこの+1を取除いてみると、必要性は直ちにわかります。+1を除き分母を変数nDistanceのみにして[ムービープレビュー]を試すと、マウスを止めてMovieClipインスタンスがポインタの位置に達したときに消えてしまいます(図04-015)。

図04-015■DisplayObject.alphaに値を設定する右辺の分母に1加算しない場合

マウスを止めれば、MovieClipインスタンスがポインタに追いつくと同時に消えてしまう。

問題は、MovieClipインスタンスがマウス座標に追いついて距離が0になったときです。0による割り算は数学では定義されていませんし、ActionScriptでも正の無限大を意味するInfinityという特殊な値になります。この値は、算術演算の対象としたり、数値として扱うのに適さない場合が少なくありません。実際、DisplayObject.alphaプロパティに代入すると、0が設定されてしまいます。

変数nDistanceは、マウスポインタまでの距離を表しますので、必ず0以上の数値になります。したがって、0による割り算を避けるには、正の値を何か加えればよいでしょう。そこで、スクリプト04-010のDisplayObject.alphaプロパティへの代入式右辺は、分母に1を足してある訳です。

04-008
0で割り算してはダメ。


Word 04-003■Infinity
Infinityは、正の無限大を表す特殊な値です。ヘルプの[ActionScript 3.0コンポーネントリファレンスガイド]には、以下のようなあっさりとした解説があるのみです(図04-016)。

図04-016■ヘルプ[ActionScript 3.0コンポーネントリファレンスガイド]のInfinity定数の解説

Number.POSITIVE_INFINITY定数の解説は、もう少し詳しい。

そこで、Number.POSITIVE_INFINITY定数の項を見ると、つぎのようにもう少し説明が加えられています。

正の無限大を表す IEEE-754値を指定します。このプロパティの値は、Infinity定数の値と同じです。

正の無限大は、数学演算または関数が表現可能な正の値の超える値を返すときに使われる特別な数値です。

データはNumber型に属するものの、数値としての処理を行った場合に、通常の数値とは異なる結果を返すことが少なくありません。以下のテスト用スクリプトでわかるように、Infinityに正の数値を加えても、掛合わせても、結果はInfinityのままです。また、正の数値をInfinityで割ると、0が返されます。

trace(Infinity);   // 出力: Infinity
trace(Infinity+1);   // 出力: Infinity
trace(Infinity*2);   // 出力: Infinity
trace(1/Infinity);   // 出力: 0

ただし、正負の符号はもちます。したがって、負の無限大は-Infinityで表されます。

trace(Infinity*-1);   // 出力: -Infinity

[ムービープレビュー]で試すと、マウスポインタからの距離が離れるほどアルファが下がり、近づくにつれて不透明になります(図04-017)。

図04-017■マウスポインタからの距離にアルファが反比例する

マウスポインタから離れるほどアルファが下がり、近づくにつれて不透明になる。

Maniac! 04-006■InfinityやNaNをプロパティに設定すると
定数InfinityNaNは、データはNumber型ではあるものの、通常の数値とは異なる特殊な値です。これらの値をインスタンスのプロパティに設定すると、予期しない結果になることが少なくありません。たとえば、座標や回転角度、アルファのプロパティに設定した結果は、下表04-003のとおりです。なお参考までに、併せてActionScript 2.0/1.0の結果も示しました。

表04-003■0.1刻みで値を設定した場合の実際のDisplayObject.alphaプロパティ値と256階調換算値
プロパティ
代入した値
結果として設定された値
表示結果
ActionScript 3.0
DisplayObject.x Infinity -107374182.4 ステージ外に配置
NaN
DisplayObject.rotation Infinity NaN 反時計回りに少し回転した角度(02-02「変数を使う」図02-014参照)
NaN
DisplayObject. Infinity 0 非表示
NaN
ActionScript 2.0/1.0
MovieClip._x Infinity -107374182.4 ステージ外に配置
NaN 値変わらず 変化なし
MovieClip._rotation Infinity NaN 反時計回りに少し回転した角度
NaN 0(値変わらず) 変化なし
MovieClip._alpha Infinity 0 非表示
NaN 100(値変わらず) 変化なし

上表04-003の具体的な結果そのものより、InfinityNaNをプロパティに設定すると予期しない結果になるということが重要です。表の内容も、試してみたところ得られた結果であって、仕様としてドキュメント等に明記された(「ドキュメント化」されたと表現されます)ものではありません。実際、ActionScript 2.0で試した結果は、Flash Professional 8とは一部異なりました(Flash CS3 Professionalの書出しをFlash Player 8にした場合と比べても)。

したがって、スクリプティングとしては、InfinityNaNになる可能性のある値は、プロパティに設定したり、数値演算で使用したりしないよう注意しましょう。

Column 04 変数宣言と関数定義の初期化時期
変数のvar宣言と関数のfunction定義は、スクリプトが実行される前に初期化されます。つまり、var宣言した変数とfunction定義した関数は、スクリプトのステートメントが処理される前に、予めメモリに読込まれるということです。

スクリプトペイン内の場合
ひとつのスクリプトペイン(ウィンドウ)の中では、変数のvar宣言や関数のfunction定義が後に記述されていたとしても、それらより前のステートメントで変数にアクセスしたり、関数を呼出すことができます。たとえば、つぎのようなテスト用のフレームアクションを試してみます(スクリプト04-011)。

スクリプト04-011■後に定義された関数を呼出してその後に宣言された変数値を[出力]

// フレームアクション
xTest();
function xTest() {
  trace(i);
}
var i:int;

最初のステートメントで、後にfunction定義した関数xTest()を呼出しています。そして、その関数内では、さらに後のステートメントでvar宣言されている変数iの値を、trace()関数により[出力]しています。その結果、[出力]パネルには0が表示されました(図04-018)。

図04-018■後に定義した関数を呼出してさらにその後で宣言した変数値を[出力]

int型データのデフォルト値である0が[出力]される。

第1に、[出力]パネルに値が表示されたのは、関数xTest()が呼出されたことを示します。第2に、出力された値0は、int型で宣言された変数の初期値です。したがって、変数宣言もすでに有効であることがわかります。

Maniac! 04-007■変数の初期化と値の代入
変数のvar宣言がスクリプトのステートメントの処理に先立って初期化されるとしても、その変数への値の代入は通常のステートメントとして、記述された順に処理されます。たとえば、前記スクリプト04-011をつぎのように修正してみます(スクリプト04-012)。

スクリプト04-012■変数に値が代入される前後で変数値を確認

// フレームアクション
xTest();
function xTest() {
  trace(i);
}
var i:int = 1;
xTest();

スクリプト04-011と異なり、変数iをint型で宣言すると同時に、整数1を代入しています。最初のステートメントで関数xTest()を呼出すと、int型のデフォルト値である0が[出力]されます(図04-019)。つまり、int型変数として初期化はされていても、値がまだ代入されていないということです。

変数のvar宣言と代入を行ったステートメントの後に関数xTest()を呼出すと、ようやく代入された値1が[出力]されます(図04-019)。つまり、代入の処理は通常のステートメントとして、記述された順序で実行されていることを意味します。

図04-019■変数に値が代入される前後で変数値が異なる

変数への値代入前と後とで、関数呼出しの結果が異なる。

以上から、変数をvar宣言すると同時に値を代入した場合、型指定を含めた変数の宣言とその変数への値の代入とは、内部的に分離されていることがわかります。スクリプトの実行に先立って初期化されるのは変数の宣言であって、代入式は通常のステートメントと同じく、その記述した順番で処理されます。

同じタイムラインの別フレームの場合
同じタイムライン上で、後のフレームにvar宣言されている変数やfunction定義されている関数も、前のフレームからアクセスすることができます。たとえば、先のテスト用フレームアクション(スクリプト04-011)を、(1)関数を呼出す最初のステートメントと(2)関数xTest()の定義、および(3)変数iの宣言とに分け、この順でそれぞれ第1、第2、第3フレームに設定してみます(図04-019)。

図04-020■関数の呼出しとその定義および変数宣言をその順でフレームに分割

後のフレームに定義した関数や宣言した変数も、前のフレームからアクセスできる(図の3つの[アクション]パネルは画像で合成した)。

[出力]パネルには、やはり0が表示されます。したがって、後のフレームで定義されている関数を呼出し、さらにその後のフレームに宣言されている変数にアクセスできることがわかります。

入れ子になったタイムラインの場合
入れ子になったタイムライン(MovieClip)のフレームアクションは、親から子の順に処理されます。しかし、変数および関数の初期化は、これらに先立って行われます。つまり、子のフレームアクションでvar宣言された変数やfunction定義された関数も、親のフレームアクションからアクセスすることができます。

テストとしてスクリプト04-011と同一の処理内容を、今度は階層化されたインスタンスに分けて設定してみました(スクリプト04-013)。まず、メインタイムラインから、そこに配置した子のMovieClipインスタンスparent_mcに定義された関数xTest()を呼出します。そして、関数xTest()で[出力]する変数iは、さらに入れ子にした孫のインスタンスchild_mcのフレームアクションで宣言しています(図04-021)。

スクリプト04-013■メインタイムラインから子の関数を呼出し孫に変数宣言

// メインタイムライン
// フレームアクション
parent_mc.xTest();


// MovieClip: parent_mc
// フレームアクション
function xTest() {
  trace(child_mc.i);
}


// MovieClip: child_mc
// フレームアクション
var i:int;


図04-021■階層化された子に関数定義しさらに孫で変数宣言

メインタイムラインに子の関数呼出しを記述。

[ムービープレビュー]を試すと、やはりint型変数のデフォルト値0が[出力]パネルに表示されます。

変数宣言と関数定義はどこに記述すべきか
以上より、Flashムービーファイルのフレームアクションでvar宣言された変数やfunction定義された関数は、すべてのステートメントに先立って初期化されることがわかりました。つまり、変数宣言や関数定義は、どこに記述しても、任意のフレームアクションからアクセスすることができるということです。

しかしもちろん、どこにでも書けるということと、そうすることがよいかどうかは別の問題です。スクリプトは可能なかぎりまとめた方が、わかりやすいですし、管理もしやすいでしょう。したがって、本書では基本的に、タイムラインの第1フレームアクションにスクリプトをまとめることにします。また変数宣言は冒頭に、関数定義もフレームアクションの前の部分に記述します。

[Prev/Next]


作成者: 野中文雄
更新日: 2007年12月3日 Maniac! 04-001-2を追加。
作成日: 2007年10月29日


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